From b3440babe32d0aa76f6df45ce0050bf0bae96f17 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 8 Feb 2017 19:21:12 +0100 Subject: [PATCH 001/246] light txq skeleton --- ethcore/light/src/lib.rs | 1 + ethcore/light/src/transaction_queue.rs | 55 ++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 ethcore/light/src/transaction_queue.rs diff --git a/ethcore/light/src/lib.rs b/ethcore/light/src/lib.rs index 6236ba1180..7f1a85cad4 100644 --- a/ethcore/light/src/lib.rs +++ b/ethcore/light/src/lib.rs @@ -36,6 +36,7 @@ pub mod client; pub mod cht; pub mod net; pub mod on_demand; +pub mod transaction_queue; #[cfg(not(feature = "ipc"))] pub mod provider; diff --git a/ethcore/light/src/transaction_queue.rs b/ethcore/light/src/transaction_queue.rs new file mode 100644 index 0000000000..c9afc4a2c3 --- /dev/null +++ b/ethcore/light/src/transaction_queue.rs @@ -0,0 +1,55 @@ +// 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 . + +//! Light Transaction Queue. +//! +//! Manages local transactions, +//! but stores all local transactions, removing only on invalidated nonce. +//! +//! Under the assumption that light nodes will have a relatively limited set of +//! accounts for which they create transactions, this queue is structured in an +//! address-wise manner. + +use ethcore::transaction::PendingTransaction; +use util::{Address, U256}; + +/// Light transaction queue. See module docs for more details. +pub struct TransactionQueue; + +impl TransactionQueue { + /// Insert a pending transaction to be queued. + pub fn insert(&mut self, tx: PendingTransaction) { + unimplemented!() + } + + /// Get the next nonce for a given address based on what's within the queue. + /// If the address has no queued transactions + pub fn next_nonce(&mut self, address: &Address) -> Option { + unimplemented!() + } + + /// Get pending transactions, ready to be propagated. + /// `best_block_number` and `best_block_timestamp` are used to filter out conditionally + /// propagated transactions. + pub fn pending_transactions(&self, best_block_number: u64, best_block_timestamp: u64) -> Vec { + unimplemented!() + } + + /// Cull out all transactions by the given address which are invalidated by the given nonce. + pub fn cull(&mut self, address: Address, last_nonce: U256) { + unimplemented!() + } +} -- GitLab From 33266f78d139ae66b7d5bb11cf1a048efdf7da3a Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 9 Feb 2017 17:36:12 +0100 Subject: [PATCH 002/246] initial transaction queue implementation --- ethcore/light/src/transaction_queue.rs | 224 +++++++++++++++++++++++-- 1 file changed, 212 insertions(+), 12 deletions(-) diff --git a/ethcore/light/src/transaction_queue.rs b/ethcore/light/src/transaction_queue.rs index c9afc4a2c3..e838e24ca5 100644 --- a/ethcore/light/src/transaction_queue.rs +++ b/ethcore/light/src/transaction_queue.rs @@ -23,33 +23,233 @@ //! accounts for which they create transactions, this queue is structured in an //! address-wise manner. -use ethcore::transaction::PendingTransaction; -use util::{Address, U256}; +use std::collections::{BTreeMap, HashMap}; +use std::collections::hash_map::Entry; + +use ethcore::transaction::{Condition, PendingTransaction, SignedTransaction}; +use util::{Address, U256, H256, H256FastMap}; + +// Knowledge of an account's current nonce. +#[derive(Debug, Clone, PartialEq, Eq)] +enum CurrentNonce { + // Assumed current nonce. + Assumed(U256), + // Known current nonce. + Known(U256), +} + +impl CurrentNonce { + // whether this nonce is assumed + fn is_assumed(&self) -> bool { + match *self { + CurrentNonce::Assumed(_) => true, + CurrentNonce::Known(_) => false, + } + } + + // whether this nonce is known for certain from an external source. + fn is_known(&self) -> bool { + !self.is_assumed() + } + + // the current nonce's value. + fn value(&self) -> &U256 { + match *self { + CurrentNonce::Assumed(ref val) => val, + CurrentNonce::Known(ref val) => val, + } + } +} + +// transactions associated with a specific account. +#[derive(Debug, Clone, PartialEq, Eq)] +struct AccountTransactions { + // believed current nonce (gotten from initial given TX or `cull` calls). + cur_nonce: CurrentNonce, + current: Vec, // ordered "current" transactions (cur_nonce onwards) + future: BTreeMap, // "future" transactions. +} + +impl AccountTransactions { + fn is_empty(&self) -> bool { + self.current.is_empty() && self.future.is_empty() + } + + fn next_nonce(&self) -> U256 { + self.current.last().map(|last| last.nonce) + .unwrap_or_else(|| *self.cur_nonce.value()) + 1.into() + } + + // attempt to move transactions from the future queue into the current queue. + fn adjust_future(&mut self) { + let mut next_nonce = self.next_nonce(); + + loop { + match self.future.remove(&next_nonce) { + Some(tx) => self.current.push(tx), + None => break, + } + + next_nonce = next_nonce + 1.into(); + } + } +} /// Light transaction queue. See module docs for more details. -pub struct TransactionQueue; +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct TransactionQueue { + by_account: HashMap, + by_hash: H256FastMap, +} impl TransactionQueue { /// Insert a pending transaction to be queued. pub fn insert(&mut self, tx: PendingTransaction) { - unimplemented!() + let sender = tx.sender(); + let hash = tx.hash(); + let nonce = tx.nonce; + + match self.by_account.entry(sender) { + Entry::Vacant(entry) => { + entry.insert(AccountTransactions { + cur_nonce: CurrentNonce::Assumed(nonce), + current: vec![tx.clone()], + future: BTreeMap::new(), + }); + } + Entry::Occupied(mut entry) => { + let acct_txs = entry.get_mut(); + if &nonce < acct_txs.cur_nonce.value() { + // don't accept txs from before known current nonce. + if acct_txs.cur_nonce.is_known() { return } + + // lower our assumption until corrected later. + acct_txs.cur_nonce = CurrentNonce::Assumed(nonce); + } + + match acct_txs.current.binary_search_by(|x| x.nonce.cmp(&nonce)) { + Ok(idx) => { + trace!(target: "txqueue", "Replacing existing transaction from {} with nonce {}", + sender, nonce); + + acct_txs.current[idx] = tx.clone(); + } + Err(idx) => { + let cur_len = acct_txs.current.len(); + let incr_nonce = nonce + 1.into(); + + // current is sorted with one tx per nonce, + // so if a tx with given nonce wasn't found that means it is either + // earlier in nonce than all other "current" transactions or later. + debug_assert!(idx == 0 || idx == cur_len); + + if idx == 0 && acct_txs.current.first().map_or(false, |f| f.nonce != incr_nonce) { + let old_cur = ::std::mem::replace(&mut acct_txs.current, vec![tx.clone()]); + + trace!(target: "txqueue", "Moving {} transactions with nonce > {} to future", + old_cur.len(), incr_nonce); + + for future in old_cur { + let future_nonce = future.nonce; + acct_txs.future.insert(future_nonce, future); + } + } else if idx == cur_len && acct_txs.current.last().map_or(false, |f| f.nonce + 1.into() != nonce) { + trace!(target: "txqueue", "Queued future transaction for {}, nonce={}", sender, nonce); + let future_nonce = nonce; + acct_txs.future.insert(future_nonce, tx.clone()); + } else { + trace!(target: "txqueue", "Queued current transaction for {}, nonce={}", sender, nonce); + + // insert, then check if we've filled any gaps. + acct_txs.current.insert(idx, tx.clone()); + acct_txs.adjust_future(); + } + } + } + } + } + + self.by_hash.insert(hash, tx); + } + + /// Get pending transaction by hash. + pub fn transaction(&self, hash: &H256) -> Option { + self.by_hash.get(hash).map(|tx| (&**tx).clone()) } /// Get the next nonce for a given address based on what's within the queue. - /// If the address has no queued transactions - pub fn next_nonce(&mut self, address: &Address) -> Option { - unimplemented!() + /// If the address has no queued transactions, then `None` will be returned + /// and the next nonce will have to be deduced via other means. + pub fn next_nonce(&self, address: &Address) -> Option { + self.by_account.get(address).map(AccountTransactions::next_nonce) } - /// Get pending transactions, ready to be propagated. + /// Get all transactions ready to be propagated. /// `best_block_number` and `best_block_timestamp` are used to filter out conditionally /// propagated transactions. - pub fn pending_transactions(&self, best_block_number: u64, best_block_timestamp: u64) -> Vec { - unimplemented!() + pub fn ready_transactions(&self, best_block_number: u64, best_block_timestamp: u64) -> Vec { + self.by_account.values().flat_map(|acct_txs| { + acct_txs.current.iter().take_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, + }).cloned() + }).collect() + } + + /// Addresses for which we store transactions. + pub fn queued_senders(&self) -> Vec
{ + self.by_account.keys().cloned().collect() } /// Cull out all transactions by the given address which are invalidated by the given nonce. - pub fn cull(&mut self, address: Address, last_nonce: U256) { - unimplemented!() + pub fn cull(&mut self, address: Address, cur_nonce: U256) { + let mut removed_hashes = vec![]; + if let Entry::Occupied(mut entry) = self.by_account.entry(address) { + { + let acct_txs = entry.get_mut(); + acct_txs.cur_nonce = CurrentNonce::Known(cur_nonce); + + // cull old "future" keys. + let old_future: Vec<_> = acct_txs.future.keys().take_while(|&&k| k < cur_nonce).cloned().collect(); + + for old in old_future { + let hash = acct_txs.future.remove(&old) + .expect("key extracted from keys iterator; known to exist; qed") + .hash(); + removed_hashes.push(hash); + } + + // then cull from "current". + let valid_pos = acct_txs.current.iter().position(|tx| tx.nonce >= cur_nonce); + match valid_pos { + None => + removed_hashes.extend(acct_txs.current.drain(..).map(|tx| tx.hash())), + Some(valid) => + removed_hashes.extend(acct_txs.current.drain(..valid).map(|tx| tx.hash())), + } + + // now try and move stuff out of future into current. + acct_txs.adjust_future(); + } + + if entry.get_mut().is_empty() { + trace!(target: "txqueue", "No more queued transactions for {} after nonce {}", + address, cur_nonce); + entry.remove(); + } + } + + trace!(target: "txqueue", "Culled {} old transactions from sender {} (nonce={})", + removed_hashes.len(), address, cur_nonce); + + for hash in removed_hashes { + self.by_hash.remove(&hash); + } } } + +#[cfg(test)] +mod tests { + +} -- GitLab From 6a924770beb6c855437ff9a7bb37e0e0f54dbf55 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 9 Feb 2017 18:10:59 +0100 Subject: [PATCH 003/246] add several tests + minor bugfixes --- ethcore/light/src/transaction_queue.rs | 142 ++++++++++++++++++++++++- 1 file changed, 138 insertions(+), 4 deletions(-) diff --git a/ethcore/light/src/transaction_queue.rs b/ethcore/light/src/transaction_queue.rs index e838e24ca5..ff530a4e74 100644 --- a/ethcore/light/src/transaction_queue.rs +++ b/ethcore/light/src/transaction_queue.rs @@ -76,8 +76,8 @@ impl AccountTransactions { } fn next_nonce(&self) -> U256 { - self.current.last().map(|last| last.nonce) - .unwrap_or_else(|| *self.cur_nonce.value()) + 1.into() + self.current.last().map(|last| last.nonce + 1.into()) + .unwrap_or_else(|| *self.cur_nonce.value()) } // attempt to move transactions from the future queue into the current queue. @@ -191,8 +191,8 @@ impl TransactionQueue { self.by_account.values().flat_map(|acct_txs| { acct_txs.current.iter().take_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, + Some(Condition::Number(blk_num)) => blk_num <= best_block_number, + Some(Condition::Timestamp(time)) => time <= best_block_timestamp, }).cloned() }).collect() } @@ -251,5 +251,139 @@ impl TransactionQueue { #[cfg(test)] mod tests { + use super::TransactionQueue; + use util::Address; + use ethcore::transaction::{Transaction, PendingTransaction, Condition}; + #[test] + fn queued_senders() { + let sender = Address::default(); + let mut txq = TransactionQueue::default(); + let tx = Transaction::default().fake_sign(sender); + + txq.insert(tx.into()); + + assert_eq!(txq.queued_senders(), vec![sender]); + + txq.cull(sender, 1.into()); + + assert_eq!(txq.queued_senders(), vec![]); + assert!(txq.by_hash.is_empty()); + } + + #[test] + fn next_nonce() { + let sender = Address::default(); + let mut txq = TransactionQueue::default(); + + for i in (0..5).chain(10..15) { + let mut tx = Transaction::default(); + tx.nonce = i.into(); + + let tx = tx.fake_sign(sender); + + txq.insert(tx.into()); + } + + // current: 0..5, future: 10..15 + assert_eq!(txq.ready_transactions(0, 0).len(), 5); + assert_eq!(txq.next_nonce(&sender).unwrap(), 5.into()); + + txq.cull(sender, 8.into()); + + // current: empty, future: 10..15 + assert_eq!(txq.ready_transactions(0, 0).len(), 0); + assert_eq!(txq.next_nonce(&sender).unwrap(), 8.into()); + + txq.cull(sender, 10.into()); + + // current: 10..15, future: empty + assert_eq!(txq.ready_transactions(0, 0).len(), 5); + assert_eq!(txq.next_nonce(&sender).unwrap(), 15.into()); + } + + #[test] + fn current_to_future() { + let sender = Address::default(); + let mut txq = TransactionQueue::default(); + + for i in 5..10 { + let mut tx = Transaction::default(); + tx.nonce = i.into(); + + let tx = tx.fake_sign(sender); + + txq.insert(tx.into()); + } + + assert_eq!(txq.ready_transactions(0, 0).len(), 5); + assert_eq!(txq.next_nonce(&sender).unwrap(), 10.into()); + + for i in 0..3 { + let mut tx = Transaction::default(); + tx.nonce = i.into(); + + let tx = tx.fake_sign(sender); + + txq.insert(tx.into()); + } + + assert_eq!(txq.ready_transactions(0, 0).len(), 3); + assert_eq!(txq.next_nonce(&sender).unwrap(), 3.into()); + + for i in 3..5 { + let mut tx = Transaction::default(); + tx.nonce = i.into(); + + let tx = tx.fake_sign(sender); + + txq.insert(tx.into()); + } + + assert_eq!(txq.ready_transactions(0, 0).len(), 10); + assert_eq!(txq.next_nonce(&sender).unwrap(), 10.into()); + } + + #[test] + fn conditional() { + let mut txq = TransactionQueue::default(); + let sender = Address::default(); + + for i in 0..5 { + let mut tx = Transaction::default(); + tx.nonce = i.into(); + let tx = tx.fake_sign(sender); + + txq.insert(match i { + 3 => PendingTransaction::new(tx, Some(Condition::Number(100))), + 4 => PendingTransaction::new(tx, Some(Condition::Timestamp(1234))), + _ => tx.into(), + }); + } + + assert_eq!(txq.ready_transactions(0, 0).len(), 3); + assert_eq!(txq.ready_transactions(0, 1234).len(), 3); + assert_eq!(txq.ready_transactions(100, 0).len(), 4); + assert_eq!(txq.ready_transactions(100, 1234).len(), 5); + } + + #[test] + fn cull_from_future() { + 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.insert(tx.into()); + } + + txq.cull(sender, 6.into()); + + assert_eq!(txq.ready_transactions(0, 0).len(), 4); + assert_eq!(txq.next_nonce(&sender).unwrap(), 10.into()); + } } -- GitLab From baf0dbc6bf4fa39e24d6fb25943bf4bbfbdb0291 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 9 Feb 2017 18:42:18 +0100 Subject: [PATCH 004/246] LightProvider struct using light transaction queue --- ethcore/light/src/client/mod.rs | 61 +++++++++++++-------------- ethcore/light/src/provider.rs | 75 ++++++++++++++++++++++++++++++++- sync/src/api.rs | 4 +- sync/src/light_sync/mod.rs | 25 ++++++----- 4 files changed, 119 insertions(+), 46 deletions(-) diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index ea4660abc7..29b812aa65 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -22,19 +22,15 @@ use ethcore::client::ClientReport; use ethcore::ids::BlockId; use ethcore::header::Header; use ethcore::verification::queue::{self, HeaderQueue}; -use ethcore::transaction::{PendingTransaction, Condition as TransactionCondition}; use ethcore::blockchain_info::BlockChainInfo; use ethcore::spec::Spec; use ethcore::service::ClientIoMessage; use ethcore::encoded; use io::IoChannel; -use util::hash::{H256, H256FastMap}; +use util::hash::H256; use util::{Bytes, Mutex, RwLock}; -use provider::Provider; -use request; - use self::header_chain::HeaderChain; pub use self::service::Service; @@ -58,6 +54,9 @@ pub trait LightChainClient: Send + Sync { /// parent queued prior. fn queue_header(&self, header: Header) -> Result; + /// Attempt to get block header by block id. + fn block_header(&self, id: BlockId) -> Option; + /// Query whether a block is known. fn is_known(&self, hash: &H256) -> bool; @@ -74,11 +73,25 @@ pub trait LightChainClient: Send + Sync { fn cht_root(&self, i: usize) -> Option; } +/// Something which can be treated as a `LightChainClient`. +pub trait AsLightClient { + /// The kind of light client this can be treated as. + type Client: LightChainClient; + + /// Access the underlying light client. + fn as_light_client(&self) -> &Self::Client; +} + +impl AsLightClient for T { + type Client = Self; + + fn as_light_client(&self) -> &Self { self } +} + /// Light client implementation. pub struct Client { queue: HeaderQueue, chain: HeaderChain, - tx_pool: Mutex>, report: RwLock, import_lock: Mutex<()>, } @@ -89,7 +102,6 @@ impl Client { Client { queue: HeaderQueue::new(config.queue, spec.engine.clone(), io_channel, true), chain: HeaderChain::new(&::rlp::encode(&spec.genesis_header())), - tx_pool: Mutex::new(Default::default()), report: RwLock::new(ClientReport::default()), import_lock: Mutex::new(()), } @@ -100,25 +112,6 @@ impl Client { self.queue.import(header).map_err(Into::into) } - /// Import a local transaction. - pub fn import_own_transaction(&self, tx: PendingTransaction) { - self.tx_pool.lock().insert(tx.transaction.hash(), tx); - } - - /// Fetch a vector of all pending transactions. - pub fn ready_transactions(&self) -> Vec { - let best = self.chain.best_header(); - self.tx_pool.lock() - .values() - .filter(|t| match t.condition { - Some(TransactionCondition::Number(x)) => x <= best.number(), - Some(TransactionCondition::Timestamp(x)) => x <= best.timestamp(), - None => true, - }) - .cloned() - .collect() - } - /// Inquire about the status of a given header. pub fn status(&self, hash: &H256) -> BlockStatus { match self.queue.status(hash) { @@ -216,6 +209,10 @@ impl LightChainClient for Client { self.import_header(header) } + fn block_header(&self, id: BlockId) -> Option { + Client::block_header(self, id) + } + fn is_known(&self, hash: &H256) -> bool { self.status(hash) == BlockStatus::InChain } @@ -237,8 +234,8 @@ impl LightChainClient for Client { } } -// dummy implementation -- may draw from canonical cache further on. -impl Provider for Client { +// dummy implementation, should be removed when a `TestClient` is added. +impl ::provider::Provider for Client { fn chain_info(&self) -> BlockChainInfo { Client::chain_info(self) } @@ -263,19 +260,19 @@ impl Provider for Client { None } - fn state_proof(&self, _req: request::StateProof) -> Vec { + fn state_proof(&self, _req: ::request::StateProof) -> Vec { Vec::new() } - fn contract_code(&self, _req: request::ContractCode) -> Bytes { + fn contract_code(&self, _req: ::request::ContractCode) -> Bytes { Vec::new() } - fn header_proof(&self, _req: request::HeaderProof) -> Option<(encoded::Header, Vec)> { + fn header_proof(&self, _req: ::request::HeaderProof) -> Option<(encoded::Header, Vec)> { None } - fn ready_transactions(&self) -> Vec { + fn ready_transactions(&self) -> Vec<::ethcore::transaction::PendingTransaction> { Vec::new() } } diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index 4721caa73d..caade3857c 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -17,15 +17,19 @@ //! A provider for the LES protocol. This is typically a full node, who can //! give as much data as necessary to its peers. +use std::sync::Arc; + use ethcore::blockchain_info::BlockChainInfo; use ethcore::client::{BlockChainClient, ProvingBlockChainClient}; use ethcore::transaction::PendingTransaction; use ethcore::ids::BlockId; use ethcore::encoded; +use util::{Bytes, RwLock, H256}; use cht::{self, BlockInfo}; +use client::{LightChainClient, AsLightClient}; +use transaction_queue::TransactionQueue; -use util::{Bytes, H256}; use request; @@ -284,6 +288,75 @@ impl Provider for T { } } +/// The light client "provider" implementation. This wraps a `LightClient` and +/// a light transaction queue. +pub struct LightProvider { + client: Arc, + txqueue: Arc>, +} + +impl LightProvider { + /// Create a new `LightProvider` from the given client and transaction queue. + pub fn new(client: Arc, txqueue: Arc>) -> Self { + LightProvider { + client: client, + txqueue: txqueue, + } + } +} + +// TODO: draw from cache (shared between this and the RPC layer) +impl Provider for LightProvider { + fn chain_info(&self) -> BlockChainInfo { + self.client.as_light_client().chain_info() + } + + fn reorg_depth(&self, _a: &H256, _b: &H256) -> Option { + None + } + + fn earliest_state(&self) -> Option { + None + } + + fn block_header(&self, id: BlockId) -> Option { + self.client.as_light_client().block_header(id) + } + + fn block_body(&self, _id: BlockId) -> Option { + None + } + + fn block_receipts(&self, _hash: &H256) -> Option { + None + } + + fn state_proof(&self, _req: request::StateProof) -> Vec { + Vec::new() + } + + fn contract_code(&self, _req: request::ContractCode) -> Bytes { + Vec::new() + } + + fn header_proof(&self, _req: request::HeaderProof) -> Option<(encoded::Header, Vec)> { + None + } + + fn ready_transactions(&self) -> Vec { + let chain_info = self.chain_info(); + self.txqueue.read().ready_transactions(chain_info.best_block_number, chain_info.best_block_timestamp) + } +} + +impl AsLightClient for LightProvider { + type Client = L::Client; + + fn as_light_client(&self) -> &L::Client { + self.client.as_light_client() + } +} + #[cfg(test)] mod tests { use ethcore::client::{EachBlockWith, TestBlockChainClient}; diff --git a/sync/src/api.rs b/sync/src/api.rs index 5b97bc5665..9b1ace73b5 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -34,7 +34,7 @@ use ipc::{BinaryConvertable, BinaryConvertError, IpcConfig}; use std::str::FromStr; use parking_lot::RwLock; use chain::{ETH_PACKET_COUNT, SNAPSHOT_SYNC_PACKET_COUNT}; -use light::client::LightChainClient; +use light::client::AsLightClient; use light::Provider; use light::net::{self as light_net, LightProtocol, Params as LightParams, Capabilities, Handler as LightHandler, EventContext}; @@ -642,7 +642,7 @@ pub struct LightSync { impl LightSync { /// Create a new light sync service. pub fn new(params: LightSyncParams) -> Result - where L: LightChainClient + Provider + 'static + where L: AsLightClient + Provider + Sync + Send + 'static { use light_sync::LightSync as SyncHandler; diff --git a/sync/src/light_sync/mod.rs b/sync/src/light_sync/mod.rs index 685cb24be0..fba89dd7b1 100644 --- a/sync/src/light_sync/mod.rs +++ b/sync/src/light_sync/mod.rs @@ -36,7 +36,7 @@ use std::collections::HashMap; use std::mem; use std::sync::Arc; -use light::client::LightChainClient; +use light::client::{AsLightClient, LightChainClient}; use light::net::{ Announcement, Handler, BasicContext, EventContext, Capabilities, ReqId, Status, @@ -106,8 +106,9 @@ impl AncestorSearch { } fn process_response(self, ctx: &ResponseContext, client: &L) -> AncestorSearch - where L: LightChainClient + where L: AsLightClient { + let client = client.as_light_client(); let first_num = client.chain_info().first_block_number.unwrap_or(0); match self { AncestorSearch::Awaiting(id, start, req) => { @@ -203,7 +204,7 @@ impl<'a> ResponseContext for ResponseCtx<'a> { } /// Light client synchronization manager. See module docs for more details. -pub struct LightSync { +pub struct LightSync { best_seen: Mutex>, // best seen block on the network. peers: RwLock>>, // peers which are relevant to synchronization. client: Arc, @@ -211,7 +212,7 @@ pub struct LightSync { state: Mutex, } -impl Handler for LightSync { +impl Handler for LightSync { fn on_connect(&self, ctx: &EventContext, status: &Status, capabilities: &Capabilities) { if !capabilities.serve_headers { trace!(target: "sync", "Disconnecting irrelevant peer: {}", ctx.peer()); @@ -344,7 +345,7 @@ impl Handler for LightSync { } // private helpers -impl LightSync { +impl LightSync { // Begins a search for the common ancestor and our best block. // does not lock state, instead has a mutable reference to it passed. fn begin_search(&self, state: &mut SyncState) { @@ -354,8 +355,8 @@ impl LightSync { return; } - self.client.flush_queue(); - let chain_info = self.client.chain_info(); + self.client.as_light_client().flush_queue(); + let chain_info = self.client.as_light_client().chain_info(); trace!(target: "sync", "Beginning search for common ancestor from {:?}", (chain_info.best_block_number, chain_info.best_block_hash)); @@ -366,8 +367,10 @@ impl LightSync { fn maintain_sync(&self, ctx: &BasicContext) { const DRAIN_AMOUNT: usize = 128; + let client = self.client.as_light_client(); + let chain_info = client.chain_info(); + let mut state = self.state.lock(); - let chain_info = self.client.chain_info(); debug!(target: "sync", "Maintaining sync ({:?})", &*state); // drain any pending blocks into the queue. @@ -376,7 +379,7 @@ impl LightSync { 'a: loop { - if self.client.queue_info().is_full() { break } + if client.queue_info().is_full() { break } *state = match mem::replace(&mut *state, SyncState::Idle) { SyncState::Rounds(round) @@ -388,7 +391,7 @@ impl LightSync { trace!(target: "sync", "Drained {} headers to import", sink.len()); for header in sink.drain(..) { - if let Err(e) = self.client.queue_header(header) { + if let Err(e) = client.queue_header(header) { debug!(target: "sync", "Found bad header ({:?}). Reset to search state.", e); self.begin_search(&mut state); @@ -492,7 +495,7 @@ impl LightSync { } // public API -impl LightSync { +impl LightSync { /// Create a new instance of `LightSync`. /// /// This won't do anything until registered as a handler -- GitLab From a559dfe9a1df05bd2e33a89f806bf84b46992460 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 9 Feb 2017 19:17:37 +0100 Subject: [PATCH 005/246] implement send_raw_transaction --- ethcore/light/src/lib.rs | 1 + ethcore/light/src/transaction_queue.rs | 58 ++++++++++++++++++++------ rpc/src/v1/impls/light/eth.rs | 23 ++++++++-- 3 files changed, 65 insertions(+), 17 deletions(-) diff --git a/ethcore/light/src/lib.rs b/ethcore/light/src/lib.rs index 7f1a85cad4..94d267c7aa 100644 --- a/ethcore/light/src/lib.rs +++ b/ethcore/light/src/lib.rs @@ -55,6 +55,7 @@ pub mod remote { mod types; pub use self::provider::Provider; +pub use self::transaction_queue::TransactionQueue; pub use types::les_request as request; #[macro_use] diff --git a/ethcore/light/src/transaction_queue.rs b/ethcore/light/src/transaction_queue.rs index ff530a4e74..b4f6f9ede1 100644 --- a/ethcore/light/src/transaction_queue.rs +++ b/ethcore/light/src/transaction_queue.rs @@ -26,7 +26,9 @@ use std::collections::{BTreeMap, HashMap}; use std::collections::hash_map::Entry; +use ethcore::error::TransactionError; use ethcore::transaction::{Condition, PendingTransaction, SignedTransaction}; +use ethcore::transaction_import::TransactionImportResult; use util::{Address, U256, H256, H256FastMap}; // Knowledge of an account's current nonce. @@ -103,25 +105,29 @@ pub struct TransactionQueue { } impl TransactionQueue { - /// Insert a pending transaction to be queued. - pub fn insert(&mut self, tx: PendingTransaction) { + /// Import a pending transaction to be queued. + pub fn import(&mut self, tx: PendingTransaction) -> Result { let sender = tx.sender(); let hash = tx.hash(); let nonce = tx.nonce; - match self.by_account.entry(sender) { + let res = match self.by_account.entry(sender) { Entry::Vacant(entry) => { entry.insert(AccountTransactions { cur_nonce: CurrentNonce::Assumed(nonce), current: vec![tx.clone()], future: BTreeMap::new(), }); + + TransactionImportResult::Current } Entry::Occupied(mut entry) => { let acct_txs = entry.get_mut(); if &nonce < acct_txs.cur_nonce.value() { // don't accept txs from before known current nonce. - if acct_txs.cur_nonce.is_known() { return } + if acct_txs.cur_nonce.is_known() { + return Err(TransactionError::Old) + } // lower our assumption until corrected later. acct_txs.cur_nonce = CurrentNonce::Assumed(nonce); @@ -133,6 +139,8 @@ impl TransactionQueue { sender, nonce); acct_txs.current[idx] = tx.clone(); + + TransactionImportResult::Current } Err(idx) => { let cur_len = acct_txs.current.len(); @@ -153,23 +161,30 @@ impl TransactionQueue { let future_nonce = future.nonce; acct_txs.future.insert(future_nonce, future); } + + TransactionImportResult::Current } else if idx == cur_len && acct_txs.current.last().map_or(false, |f| f.nonce + 1.into() != nonce) { trace!(target: "txqueue", "Queued future transaction for {}, nonce={}", sender, nonce); let future_nonce = nonce; acct_txs.future.insert(future_nonce, tx.clone()); + + TransactionImportResult::Future } else { trace!(target: "txqueue", "Queued current transaction for {}, nonce={}", sender, nonce); // insert, then check if we've filled any gaps. acct_txs.current.insert(idx, tx.clone()); acct_txs.adjust_future(); + + TransactionImportResult::Current } } } } - } + }; self.by_hash.insert(hash, tx); + Ok(res) } /// Get pending transaction by hash. @@ -261,7 +276,7 @@ mod tests { let mut txq = TransactionQueue::default(); let tx = Transaction::default().fake_sign(sender); - txq.insert(tx.into()); + txq.import(tx.into()).unwrap(); assert_eq!(txq.queued_senders(), vec![sender]); @@ -282,7 +297,7 @@ mod tests { let tx = tx.fake_sign(sender); - txq.insert(tx.into()); + txq.import(tx.into()).unwrap(); } // current: 0..5, future: 10..15 @@ -313,7 +328,7 @@ mod tests { let tx = tx.fake_sign(sender); - txq.insert(tx.into()); + txq.import(tx.into()).unwrap(); } assert_eq!(txq.ready_transactions(0, 0).len(), 5); @@ -325,7 +340,7 @@ mod tests { let tx = tx.fake_sign(sender); - txq.insert(tx.into()); + txq.import(tx.into()).unwrap(); } assert_eq!(txq.ready_transactions(0, 0).len(), 3); @@ -337,7 +352,7 @@ mod tests { let tx = tx.fake_sign(sender); - txq.insert(tx.into()); + txq.import(tx.into()).unwrap(); } assert_eq!(txq.ready_transactions(0, 0).len(), 10); @@ -354,11 +369,11 @@ mod tests { tx.nonce = i.into(); let tx = tx.fake_sign(sender); - txq.insert(match i { + txq.import(match i { 3 => PendingTransaction::new(tx, Some(Condition::Number(100))), 4 => PendingTransaction::new(tx, Some(Condition::Timestamp(1234))), _ => tx.into(), - }); + }).unwrap(); } assert_eq!(txq.ready_transactions(0, 0).len(), 3); @@ -378,7 +393,7 @@ mod tests { let tx = tx.fake_sign(sender); - txq.insert(tx.into()); + txq.import(tx.into()).unwrap(); } txq.cull(sender, 6.into()); @@ -386,4 +401,21 @@ mod tests { assert_eq!(txq.ready_transactions(0, 0).len(), 4); assert_eq!(txq.next_nonce(&sender).unwrap(), 10.into()); } + + #[test] + fn import_old() { + let sender = Address::default(); + let mut txq = TransactionQueue::default(); + + let mut tx_a = Transaction::default(); + tx_a.nonce = 3.into(); + + let mut tx_b = Transaction::default(); + tx_b.nonce = 2.into(); + + txq.import(tx_a.fake_sign(sender).into()).unwrap(); + txq.cull(sender, 3.into()); + + assert!(txq.import(tx_b.fake_sign(sender).into()).is_err()) + } } diff --git a/rpc/src/v1/impls/light/eth.rs b/rpc/src/v1/impls/light/eth.rs index 47765bd412..614174b737 100644 --- a/rpc/src/v1/impls/light/eth.rs +++ b/rpc/src/v1/impls/light/eth.rs @@ -25,16 +25,18 @@ use jsonrpc_core::Error; use jsonrpc_macros::Trailing; use light::client::Client as LightClient; -use light::cht; +use light::{cht, TransactionQueue}; use light::on_demand::{request, OnDemand}; use ethcore::account_provider::{AccountProvider, DappId}; use ethcore::basic_account::BasicAccount; use ethcore::encoded; use ethcore::ids::BlockId; +use ethcore::transaction::SignedTransaction; use ethsync::LightSync; +use rlp::{UntrustedRlp, View}; use util::sha3::{SHA3_NULL_RLP, SHA3_EMPTY_LIST_RLP}; -use util::U256; +use util::{RwLock, U256}; use futures::{future, Future, BoxFuture}; use futures::sync::oneshot; @@ -56,6 +58,7 @@ pub struct EthClient { sync: Arc, client: Arc, on_demand: Arc, + transaction_queue: Arc>, accounts: Arc, } @@ -76,12 +79,14 @@ impl EthClient { sync: Arc, client: Arc, on_demand: Arc, + transaction_queue: Arc>, accounts: Arc, ) -> Self { EthClient { sync: sync, client: client, on_demand: on_demand, + transaction_queue: transaction_queue, accounts: accounts, } } @@ -300,11 +305,21 @@ impl Eth for EthClient { } fn send_raw_transaction(&self, raw: Bytes) -> Result { - Err(errors::unimplemented(None)) + UntrustedRlp::new(&raw.into_vec()).as_val() + .map_err(errors::from_rlp_error) + .and_then(|tx| SignedTransaction::new(tx).map_err(errors::from_transaction_error)) + .and_then(|signed| { + let hash = signed.hash(); + self.transaction_queue.write().import(signed.into()) + .map(|_| hash) + .map_err(Into::into) + .map_err(errors::from_transaction_error) + }) + .map(Into::into) } fn submit_transaction(&self, raw: Bytes) -> Result { - Err(errors::unimplemented(None)) + self.send_raw_transaction(raw) } fn call(&self, req: CallRequest, num: Trailing) -> Result { -- GitLab From 325c6aaf6af793b2c34c4c1ce33ed5de54f4ae4e Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 9 Feb 2017 19:58:29 +0100 Subject: [PATCH 006/246] verify raw transactions against Engine --- ethcore/light/src/client/mod.rs | 10 ++++++++++ rpc/src/v1/impls/light/eth.rs | 11 +++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index 29b812aa65..bb43a9f5b1 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -16,9 +16,12 @@ //! Light client implementation. Stores data from light sync +use std::sync::Arc; + use ethcore::block_import_error::BlockImportError; use ethcore::block_status::BlockStatus; use ethcore::client::ClientReport; +use ethcore::engines::Engine; use ethcore::ids::BlockId; use ethcore::header::Header; use ethcore::verification::queue::{self, HeaderQueue}; @@ -91,6 +94,7 @@ impl AsLightClient for T { /// Light client implementation. pub struct Client { queue: HeaderQueue, + engine: Arc, chain: HeaderChain, report: RwLock, import_lock: Mutex<()>, @@ -101,6 +105,7 @@ impl Client { pub fn new(config: Config, spec: &Spec, io_channel: IoChannel) -> Self { Client { queue: HeaderQueue::new(config.queue, spec.engine.clone(), io_channel, true), + engine: spec.engine.clone(), chain: HeaderChain::new(&::rlp::encode(&spec.genesis_header())), report: RwLock::new(ClientReport::default()), import_lock: Mutex::new(()), @@ -200,6 +205,11 @@ impl Client { self.chain.heap_size_of_children() } + + /// Get a handle to the verification engine. + pub fn engine(&self) -> &Engine { + &*self.engine + } } impl LightChainClient for Client { diff --git a/rpc/src/v1/impls/light/eth.rs b/rpc/src/v1/impls/light/eth.rs index 614174b737..f72a9bb69d 100644 --- a/rpc/src/v1/impls/light/eth.rs +++ b/rpc/src/v1/impls/light/eth.rs @@ -305,11 +305,18 @@ impl Eth for EthClient { } fn send_raw_transaction(&self, raw: Bytes) -> Result { + let best_header = self.client.block_header(BlockId::Latest) + .expect("best block header always stored; qed").decode(); + UntrustedRlp::new(&raw.into_vec()).as_val() .map_err(errors::from_rlp_error) - .and_then(|tx| SignedTransaction::new(tx).map_err(errors::from_transaction_error)) - .and_then(|signed| { + .and_then(|tx| { + self.client.engine().verify_transaction_basic(&tx, &best_header) + .map_err(errors::from_transaction_error)?; + + let signed = SignedTransaction::new(tx).map_err(errors::from_transaction_error)?; let hash = signed.hash(); + self.transaction_queue.write().import(signed.into()) .map(|_| hash) .map_err(Into::into) -- GitLab From 7be2c145deb2461957598fc75c0459cf5726e50c Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 9 Feb 2017 20:22:31 +0100 Subject: [PATCH 007/246] LightDispatcher definition --- rpc/src/v1/helpers/dispatch.rs | 36 ++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index 36d5ad8644..5d36404e23 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -18,10 +18,13 @@ use std::fmt::Debug; use std::ops::Deref; -use std::sync::Weak; +use std::sync::{Arc, Weak}; use futures::{future, Future, BoxFuture}; -use util::{Address, H520, H256, U256, Uint, Bytes}; +use light::client::LightChainClient; +use light::on_demand::{request, OnDemand}; +use light::TransactionQueue as LightTransactionQueue; +use util::{Address, H520, H256, U256, Uint, Bytes, RwLock}; use util::sha3::Hashable; use ethkey::Signature; @@ -151,7 +154,36 @@ impl Dispatcher for FullDispatcher, + client: Arc, + on_demand: Arc, + transaction_queue: Arc>, +} + +impl LightDispatcher { + /// Create a new `LightDispatcher` from its requisite parts. + /// + /// For correct operation, the OnDemand service is assumed to be registered as a network handler, + pub fn new( + sync: Arc, + client: Arc, + on_demand: Arc, + transaction_queue: Arc>, + ) -> Self { + LightDispatcher { + sync: sync, + client: client, + on_demand: on_demand, + transaction_queue, } + } } /// default MAC to use. -- GitLab From e53d023a8accbee859be3ba5fa92b05816fb23e9 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 9 Feb 2017 21:12:28 +0100 Subject: [PATCH 008/246] implement light dispatcher --- rpc/src/v1/helpers/dispatch.rs | 92 ++++++++++++++++++++++++++++-- rpc/src/v1/helpers/errors.rs | 9 +++ rpc/src/v1/impls/personal.rs | 2 +- rpc/src/v1/impls/signer.rs | 4 +- rpc/src/v1/impls/signing.rs | 2 +- rpc/src/v1/impls/signing_unsafe.rs | 2 +- 6 files changed, 100 insertions(+), 11 deletions(-) diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index 4474569920..a27193cfc9 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -29,6 +29,7 @@ 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}; @@ -58,7 +59,7 @@ pub trait Dispatcher: Send + Sync + Clone { -> BoxFuture; /// Sign the given transaction request without dispatching, fetching appropriate nonce. - fn sign(&self, accounts: &AccountProvider, filled: FilledTransactionRequest, password: SignWith) + fn sign(&self, accounts: Arc, filled: FilledTransactionRequest, password: SignWith) -> BoxFuture, Error>; /// "Dispatch" a local transaction. @@ -111,7 +112,7 @@ impl Dispatcher for FullDispatcher, filled: FilledTransactionRequest, password: SignWith) -> BoxFuture, Error> { let (client, miner) = (take_weakf!(self.client), take_weakf!(self.miner)); @@ -133,7 +134,7 @@ impl Dispatcher for FullDispatcher BoxFuture + { + let request = request; + let gas_limit = self.client.block_header(BlockId::Latest) + .expect("Best block header always kept; qed").gas_limit(); + + 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() + } + + fn sign(&self, accounts: Arc, filled: FilledTransactionRequest, password: SignWith) + -> BoxFuture, Error> + { + let network_id = None; // TODO: fetch from client. + let address = filled.from; + let best_header = self.client.block_header(BlockId::Latest) + .expect("Best block header always kept; qed"); + + let with_nonce = move |filled: FilledTransactionRequest, nonce| { + let t = Transaction { + nonce: nonce, + action: filled.to.map_or(Action::Create, Action::Call), + gas: filled.gas, + gas_price: filled.gas_price, + value: filled.value, + data: filled.data, + }; + let hash = t.hash(network_id); + let signature = signature(&accounts, address, hash, password)?; + + Ok(signature.map(|sig| { + SignedTransaction::new(t.with_signature(sig, network_id)) + .expect("Transaction was signed by AccountsProvider; it never produces invalid signatures; qed") + })) + }; + + // 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 { + 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 + .map_err(|_| errors::no_light_peers()) + .and_then(move |acc| with_nonce(filled, acc.nonce + U256::one())) + .boxed() + } + + fn dispatch_transaction(&self, signed_transaction: PendingTransaction) -> Result { + let hash = signed_transaction.transaction.hash(); + + self.transaction_queue.write().import(signed_transaction) + .map_err(Into::into) + .map_err(errors::from_transaction_error) + .map(|_| hash) + } +} + /// default MAC to use. pub const DEFAULT_MAC: [u8; 2] = [0, 0]; @@ -264,7 +344,7 @@ impl From<(T, Option)> for WithToken { /// Execute a confirmation payload. pub fn execute( dispatcher: D, - accounts: &AccountProvider, + accounts: Arc, payload: ConfirmationPayload, pass: SignWith ) -> BoxFuture, Error> { @@ -294,7 +374,7 @@ pub fn execute( format!("\x19Ethereum Signed Message:\n{}", data.len()) .into_bytes(); message_data.append(&mut data); - let res = signature(accounts, address, message_data.sha3(), pass) + let res = signature(&accounts, address, message_data.sha3(), pass) .map(|result| result .map(|rsv| { let mut vrs = [0u8; 65]; @@ -310,7 +390,7 @@ pub fn execute( future::done(res).boxed() }, ConfirmationPayload::Decrypt(address, data) => { - let res = decrypt(accounts, address, data, pass) + let res = decrypt(&accounts, address, data, pass) .map(|result| result .map(RpcBytes) .map(ConfirmationResponse::Decrypt) diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index e1074c5984..b58999f84d 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -49,6 +49,7 @@ mod codes { pub const COMPILATION_ERROR: i64 = -32050; pub const ENCRYPTION_ERROR: i64 = -32055; pub const FETCH_ERROR: i64 = -32060; + pub const NO_LIGHT_PEERS: i64 = -32065; } pub fn unimplemented(details: Option) -> Error { @@ -308,3 +309,11 @@ pub fn unknown_block() -> Error { data: None, } } + +pub fn no_light_peers() -> Error { + Error { + code: ErrorCode::ServerError(codes::NO_LIGHT_PEERS), + message: "No light peers who can serve data".into(), + data: None, + } +} diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 03ce5ffeb6..bf07452599 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -113,7 +113,7 @@ impl Personal for PersonalClient { dispatcher.fill_optional_fields(request.into(), default) .and_then(move |filled| { let condition = filled.condition.clone().map(Into::into); - dispatcher.sign(&accounts, filled, SignWith::Password(password)) + dispatcher.sign(accounts, filled, SignWith::Password(password)) .map(|tx| tx.into_value()) .map(move |tx| PendingTransaction::new(tx, condition)) .map(move |tx| (tx, dispatcher)) diff --git a/rpc/src/v1/impls/signer.rs b/rpc/src/v1/impls/signer.rs index a94db94a00..ffd9f41083 100644 --- a/rpc/src/v1/impls/signer.rs +++ b/rpc/src/v1/impls/signer.rs @@ -52,7 +52,7 @@ impl SignerClient { } fn confirm_internal(&self, id: U256, modification: TransactionModification, f: F) -> BoxFuture, Error> where - F: FnOnce(D, &AccountProvider, ConfirmationPayload) -> T, + F: FnOnce(D, Arc, ConfirmationPayload) -> T, T: IntoFuture, Error=Error>, T::Future: Send + 'static { @@ -87,7 +87,7 @@ impl SignerClient { request.condition = condition.clone().map(Into::into); } } - let fut = f(dispatcher, &*accounts, payload); + let fut = f(dispatcher, accounts, payload); fut.into_future().then(move |result| { // Execute if let Ok(ref response) = result { diff --git a/rpc/src/v1/impls/signing.rs b/rpc/src/v1/impls/signing.rs index 6bf2155ed3..106852d826 100644 --- a/rpc/src/v1/impls/signing.rs +++ b/rpc/src/v1/impls/signing.rs @@ -94,7 +94,7 @@ impl SigningQueueClient { .and_then(move |payload| { let sender = payload.sender(); if accounts.is_unlocked(sender) { - dispatch::execute(dispatcher, &accounts, payload, dispatch::SignWith::Nothing) + dispatch::execute(dispatcher, accounts, payload, dispatch::SignWith::Nothing) .map(|v| v.into_value()) .map(DispatchResult::Value) .boxed() diff --git a/rpc/src/v1/impls/signing_unsafe.rs b/rpc/src/v1/impls/signing_unsafe.rs index 333b823f93..c588313ec9 100644 --- a/rpc/src/v1/impls/signing_unsafe.rs +++ b/rpc/src/v1/impls/signing_unsafe.rs @@ -61,7 +61,7 @@ impl SigningUnsafeClient { let dis = self.dispatcher.clone(); dispatch::from_rpc(payload, default, &dis) .and_then(move |payload| { - dispatch::execute(dis, &accounts, payload, dispatch::SignWith::Nothing) + dispatch::execute(dis, accounts, payload, dispatch::SignWith::Nothing) }) .map(|v| v.into_value()) .boxed() -- GitLab From 5e65081d4f83706e034d11eadea17f930294da8b Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 9 Feb 2017 22:37:56 +0100 Subject: [PATCH 009/246] remove accidental gap in next nonce --- rpc/src/v1/helpers/dispatch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index a27193cfc9..b12f26e361 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -246,7 +246,7 @@ impl Dispatcher for LightDispatcher { nonce_future .map_err(|_| errors::no_light_peers()) - .and_then(move |acc| with_nonce(filled, acc.nonce + U256::one())) + .and_then(move |acc| with_nonce(filled, acc.nonce)) .boxed() } -- GitLab From 4f1afccf975292828ce2b3f78204ab7cd92313ca Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 13 Feb 2017 16:49:01 +0100 Subject: [PATCH 010/246] best_block_header function --- ethcore/light/src/client/mod.rs | 15 +++++++++++++-- rpc/src/v1/helpers/dispatch.rs | 7 ++----- rpc/src/v1/impls/light/eth.rs | 3 +-- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index bb43a9f5b1..a113b43676 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -31,8 +31,7 @@ use ethcore::service::ClientIoMessage; use ethcore::encoded; use io::IoChannel; -use util::hash::H256; -use util::{Bytes, Mutex, RwLock}; +use util::{Bytes, H256, Mutex, RwLock}; use self::header_chain::HeaderChain; @@ -60,6 +59,9 @@ pub trait LightChainClient: Send + Sync { /// Attempt to get block header by block id. fn block_header(&self, id: BlockId) -> Option; + /// Get the best block header. + fn best_block_header(&self) -> encoded::Header; + /// Query whether a block is known. fn is_known(&self, hash: &H256) -> bool; @@ -157,6 +159,11 @@ impl Client { self.chain.block_header(id) } + /// Get the best block header. + pub fn best_block_header(&self) -> encoded::Header { + self.chain.best_header() + } + /// Flush the header queue. pub fn flush_queue(&self) { self.queue.flush() @@ -223,6 +230,10 @@ impl LightChainClient for Client { Client::block_header(self, id) } + fn best_block_header(&self) -> encoded::Header { + Client::best_block_header(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 6174c060ba..de0207d792 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -30,7 +30,6 @@ 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}; @@ -190,8 +189,7 @@ impl Dispatcher for LightDispatcher { -> BoxFuture { let request = request; - let gas_limit = self.client.block_header(BlockId::Latest) - .expect("Best block header always kept; qed").gas_limit(); + let gas_limit = self.client.best_block_header().gas_limit(); future::ok(FilledTransactionRequest { from: request.from.unwrap_or(default_sender), @@ -211,8 +209,7 @@ impl Dispatcher for LightDispatcher { { let network_id = None; // TODO: fetch from client. let address = filled.from; - let best_header = self.client.block_header(BlockId::Latest) - .expect("Best block header always kept; qed"); + let best_header = self.client.best_block_header(); let with_nonce = move |filled: FilledTransactionRequest, nonce| { let t = Transaction { diff --git a/rpc/src/v1/impls/light/eth.rs b/rpc/src/v1/impls/light/eth.rs index f72a9bb69d..18bd9682e4 100644 --- a/rpc/src/v1/impls/light/eth.rs +++ b/rpc/src/v1/impls/light/eth.rs @@ -305,8 +305,7 @@ impl Eth for EthClient { } fn send_raw_transaction(&self, raw: Bytes) -> Result { - let best_header = self.client.block_header(BlockId::Latest) - .expect("best block header always stored; qed").decode(); + let best_header = self.client.best_block_header().decode(); UntrustedRlp::new(&raw.into_vec()).as_val() .map_err(errors::from_rlp_error) -- GitLab From 073ed1a87c0cac653761bb035981786d0e1c1418 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 13 Feb 2017 17:15:25 +0100 Subject: [PATCH 011/246] lower default pruning history and memory (#4528) --pruning-history: 1200 -> 64 --pruning-memory: 150 -> 75 --- parity/cli/config.full.toml | 2 +- parity/cli/mod.rs | 6 +++--- parity/configuration.rs | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index c0914de4ce..5e6bc367a0 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -95,7 +95,7 @@ refuse_service_transactions = false [footprint] tracing = "auto" pruning = "auto" -pruning_history = 1200 +pruning_history = 64 pruning_memory = 500 cache_size_db = 64 cache_size_blocks = 8 diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 6e24f639dd..7ac7f8c8d1 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -251,9 +251,9 @@ usage! { or |c: &Config| otry!(c.footprint).tracing.clone(), flag_pruning: String = "auto", or |c: &Config| otry!(c.footprint).pruning.clone(), - flag_pruning_history: u64 = 1200u64, + flag_pruning_history: u64 = 64u64, or |c: &Config| otry!(c.footprint).pruning_history.clone(), - flag_pruning_memory: usize = 150usize, + flag_pruning_memory: usize = 75usize, or |c: &Config| otry!(c.footprint).pruning_memory.clone(), flag_cache_size_db: u32 = 64u32, or |c: &Config| otry!(c.footprint).cache_size_db.clone(), @@ -670,7 +670,7 @@ mod tests { // -- Footprint Options flag_tracing: "auto".into(), flag_pruning: "auto".into(), - flag_pruning_history: 1200u64, + flag_pruning_history: 64u64, flag_pruning_memory: 500usize, flag_cache_size_db: 64u32, flag_cache_size_blocks: 8u32, diff --git a/parity/configuration.rs b/parity/configuration.rs index c500f045a6..349e576796 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -971,8 +971,8 @@ mod tests { file_path: Some("blockchain.json".into()), format: Default::default(), pruning: Default::default(), - pruning_history: 1200, - pruning_memory: 150, + pruning_history: 64, + pruning_memory: 75, compaction: Default::default(), wal: true, tracing: Default::default(), @@ -994,8 +994,8 @@ mod tests { dirs: Default::default(), file_path: Some("blockchain.json".into()), pruning: Default::default(), - pruning_history: 1200, - pruning_memory: 150, + pruning_history: 64, + pruning_memory: 75, format: Default::default(), compaction: Default::default(), wal: true, @@ -1017,8 +1017,8 @@ mod tests { dirs: Default::default(), file_path: Some("state.json".into()), pruning: Default::default(), - pruning_history: 1200, - pruning_memory: 150, + pruning_history: 64, + pruning_memory: 75, format: Default::default(), compaction: Default::default(), wal: true, @@ -1042,8 +1042,8 @@ mod tests { dirs: Default::default(), file_path: Some("blockchain.json".into()), pruning: Default::default(), - pruning_history: 1200, - pruning_memory: 150, + pruning_history: 64, + pruning_memory: 75, format: Some(DataFormat::Hex), compaction: Default::default(), wal: true, @@ -1078,8 +1078,8 @@ mod tests { dirs: Default::default(), spec: Default::default(), pruning: Default::default(), - pruning_history: 1200, - pruning_memory: 150, + pruning_history: 64, + pruning_memory: 75, daemon: None, logger_config: Default::default(), miner_options: Default::default(), -- GitLab From 83a8ceb09b220d6bb5c77fd50aec46fa5f30d9d6 Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Mon, 13 Feb 2017 19:39:46 +0300 Subject: [PATCH 012/246] Update rust version in readme [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 23a1c6f734..dfd0f533c2 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Be sure to check out [our wiki][wiki-url] for more information. [doc-url]: https://ethcore.github.io/parity/ethcore/index.html [wiki-url]: https://github.com/ethcore/parity/wiki -**Parity requires Rust version 1.14.0 to build** +**Parity requires Rust version 1.15.0 to build** ---- -- GitLab From 78917d728df696401bf29b29ae9dbf7eb132406a Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Mon, 13 Feb 2017 21:34:16 +0300 Subject: [PATCH 013/246] Bump JSON RPC crates versions (#4530) * json rpc bump * some error message deviations --- Cargo.lock | 62 +++++++++++----------- rpc/src/v1/tests/mocked/eth.rs | 2 +- rpc/src/v1/tests/mocked/parity_accounts.rs | 2 +- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 18d4177b6e..ba04e2184c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,7 +27,7 @@ dependencies = [ "fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)", "isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -428,8 +428,8 @@ dependencies = [ "fetch 0.1.0", "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", - "jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)", - "jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "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)", "linked-hash-map 0.3.0 (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)", @@ -601,10 +601,10 @@ dependencies = [ "ethsync 1.6.0", "fetch 0.1.0", "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)", - "jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git)", - "jsonrpc-ipc-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)", - "jsonrpc-macros 0.2.0 (git+https://github.com/ethcore/jsonrpc.git)", + "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)", + "jsonrpc-macros 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "order-stat 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "parity-reactor 0.1.0", @@ -629,7 +629,7 @@ dependencies = [ "ethcore-io 1.6.0", "ethcore-rpc 1.6.0", "ethcore-util 1.6.0", - "jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)", + "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)", "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-ui 1.6.0", @@ -649,9 +649,9 @@ dependencies = [ "ethcore-ipc-nano 1.6.0", "ethcore-util 1.6.0", "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)", - "jsonrpc-macros 0.2.0 (git+https://github.com/ethcore/jsonrpc.git)", - "jsonrpc-tcp-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "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)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.5.1 (git+https://github.com/ethcore/mio?branch=v0.5.x)", @@ -1030,8 +1030,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "jsonrpc-core" -version = "5.1.0" -source = "git+https://github.com/ethcore/jsonrpc.git#cac47f72090c3db78e83d56d333ada52a22dd3a9" +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)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1043,11 +1043,11 @@ dependencies = [ [[package]] name = "jsonrpc-http-server" -version = "7.0.0" -source = "git+https://github.com/ethcore/jsonrpc.git#cac47f72090c3db78e83d56d333ada52a22dd3a9" +version = "6.0.0" +source = "git+https://github.com/ethcore/jsonrpc.git#86d7a89c85f324b5f6671315d9b71010ca995300" dependencies = [ "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", - "jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)", + "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)", "tokio-core 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1055,12 +1055,12 @@ dependencies = [ [[package]] name = "jsonrpc-ipc-server" -version = "1.0.0" -source = "git+https://github.com/ethcore/jsonrpc.git#cac47f72090c3db78e83d56d333ada52a22dd3a9" +version = "6.0.0" +source = "git+https://github.com/ethcore/jsonrpc.git#86d7a89c85f324b5f6671315d9b71010ca995300" dependencies = [ "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1071,20 +1071,20 @@ dependencies = [ [[package]] name = "jsonrpc-macros" -version = "0.2.0" -source = "git+https://github.com/ethcore/jsonrpc.git#cac47f72090c3db78e83d56d333ada52a22dd3a9" +version = "6.0.0" +source = "git+https://github.com/ethcore/jsonrpc.git#86d7a89c85f324b5f6671315d9b71010ca995300" dependencies = [ - "jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "jsonrpc-tcp-server" -version = "1.0.0" -source = "git+https://github.com/ethcore/jsonrpc.git#cac47f72090c3db78e83d56d333ada52a22dd3a9" +version = "6.0.0" +source = "git+https://github.com/ethcore/jsonrpc.git#86d7a89c85f324b5f6671315d9b71010ca995300" dependencies = [ "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "lazy_static 0.2.1 (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)", @@ -1578,7 +1578,7 @@ dependencies = [ "ethcore-signer 1.6.0", "ethcore-util 1.6.0", "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)", + "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)", "serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2503,11 +2503,11 @@ dependencies = [ "checksum isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7408a548dc0e406b7912d9f84c261cc533c1866e047644a811c133c56041ac0c" "checksum itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d95557e7ba6b71377b0f2c3b3ae96c53f1b75a926a6901a500f557a370af730a" "checksum itoa 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "91fd9dc2c587067de817fec4ad355e3818c3d893a78cab32a0a474c7a15bb8d5" -"checksum jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" -"checksum jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" -"checksum jsonrpc-ipc-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" -"checksum jsonrpc-macros 0.2.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" -"checksum jsonrpc-tcp-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" +"checksum jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" +"checksum jsonrpc-http-server 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" +"checksum jsonrpc-ipc-server 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" +"checksum jsonrpc-macros 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" +"checksum jsonrpc-tcp-server 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" "checksum lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "49247ec2a285bb3dcb23cbd9c35193c025e7251bfce77c1d5da97e6362dffe7f" diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index 9e23cf474a..dd5f63347f 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -874,7 +874,7 @@ fn rpc_eth_send_transaction_with_bad_to() { "id": 1 }"#; - let response = r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params","data":null},"id":1}"#; + let response = r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid length.","data":null},"id":1}"#; assert_eq!(tester.io.handle_request_sync(&request), Some(response.into())); } diff --git a/rpc/src/v1/tests/mocked/parity_accounts.rs b/rpc/src/v1/tests/mocked/parity_accounts.rs index 1c3cd2f8e1..0f0a1836a4 100644 --- a/rpc/src/v1/tests/mocked/parity_accounts.rs +++ b/rpc/src/v1/tests/mocked/parity_accounts.rs @@ -191,7 +191,7 @@ fn should_be_able_to_kill_account() { let address = accounts[0]; let request = format!(r#"{{"jsonrpc": "2.0", "method": "parity_killAccount", "params": ["0xf00baba2f00baba2f00baba2f00baba2f00baba2"], "id": 1}}"#); - let response = r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params","data":null},"id":1}"#; + let response = r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"invalid length 1, expected a tuple of size 2","data":null},"id":1}"#; let res = tester.io.handle_request_sync(&request); assert_eq!(res, Some(response.into())); -- GitLab From 63d2cfcbfce9b4a2a8511a2180a6281316212c51 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Tue, 14 Feb 2017 08:29:32 +0100 Subject: [PATCH 014/246] Home landing page (#4178) * Home entry point (basics) * WIP store for web * Add DappUrlInput component * Updated tests * WIP store update * Adjust styling * Add home tab * Collapse first/last without extra divs * Navigation actually navigates * styling * Encoding of ethlink.io URLs * encodedUrl setup * base58 encoded URLs * Added decoding, updated tests to Parity-compliant * Base32 (synced with Rust implementation via tests) * Split URL into 63 character chunks * Fix store test * Cleanups * s/ethlink/dapplink/ * Display app navigation & histroy * Start on /accounts (for now, until expanded fully) * Update tests * ethlink.io -> web3.site * Basic list layout * Store history on navigation * Show Accounts & Dapps * Add skeleton for DappIcon (WIP) * DappIcon WIP * DappIcon in place * Split into maneable sub-components * WIP * Tests for views/Home * Swap default entry-point to /home * Expose registry.get via lookupMeta * Add getEntry interface, fix instance retrieval (with tests) * Add news display component * Add tests for added contracts/registry methods * Fix GHH test refactoring * render news via SectionList * News items store directly * Images * News & Urls has new layout * Convert remainder * First run-through of MVP for SectionList * Update tests * Deploycontract should not override global p styles * Allow styles overrides for head & body * Adjust layout styling * revert Container>flex * Adjust sizes of history items * Cleanups * HistoryStore for tracking relevant routes * Default route is still /accounts * Fix tests * Update 2015-2017 * Add lookupMeta & tests * Add getEntry & tests * Split Dapp icon into ui/DappIcon * Update copyright dates * Encoding for *.web3.site urls * Dapp history retrieval * Grow to only 40% on hover * Update description * Add DappUrlInput component * Update Web views with store * Update spec description * Update spec description * edited url does not allow in-place store edits * Use /web/ urls for iframe * Removed (now) unused _list.css * Mistamtched merge fixed * Tab split (WIP) * Split Tab component * Update tests after merge * typo * Remove background !important * Set item width to parent * Set width, remove overflow-x: hidden * Align hover overlays * Container defaults to some opacity * Display history from listRecentDapps * Override styles for a tags * Open URLs in new window when extension is available * Fix tests after update * AccountCard width 100% * Re-add opening correct url in tab * Cleanup link rendering * Remove hardcoded news URL * pre-merge * Extra padding at Home bottom (Pr grumble) * Match js-vaults stretch * s/Web Apps via URL/Web Apps/ (PR grumble) * Store recent wallets (PR grumble) * Simplify inline style matching (PR comment) * Add store for new retrieval * Add missing observer * Auto-link based on account type * Fix UI overlaps * Extra spacing * Only show account when accountInfo is available * Align timestamp line-heights * Fix tests * Update tests * Really fix failing test (check for Connect(Account)) --- js/package.json | 1 + js/src/api/format/output.js | 10 ++ js/src/api/format/output.spec.js | 10 +- js/src/api/rpc/parity/parity.js | 5 +- js/src/i18n/en/settings.js | 4 + .../ParametersStep/parametersStep.js | 2 +- .../modals/DeployContract/deployContract.css | 6 +- js/src/routes.js | 17 ++- js/src/ui/Container/container.css | 30 +++- js/src/ui/Container/container.js | 16 ++ js/src/ui/Container/container.spec.js | 9 +- js/src/ui/Icons/index.js | 93 ++++-------- js/src/ui/SectionList/sectionList.css | 39 ++--- js/src/ui/SectionList/sectionList.js | 23 +-- js/src/ui/SectionList/sectionList.spec.js | 2 +- js/src/ui/Theme/theme.js | 2 +- js/src/views/Accounts/List/list.css | 35 ++--- js/src/views/Accounts/Summary/summary.js | 6 +- .../views/Application/Extension/extension.js | 2 +- js/src/views/Application/Extension/store.js | 16 ++ js/src/views/Application/TabBar/tabBar.css | 27 ++-- js/src/views/Application/TabBar/tabBar.js | 97 +++++------- js/src/views/Dapps/UrlButton/urlButton.js | 99 ------------- js/src/views/Dapps/dapps.css | 14 +- js/src/views/Dapps/dapps.js | 2 - js/src/views/Home/Accounts/accounts.css | 50 +++++++ js/src/views/Home/Accounts/accounts.js | 139 ++++++++++++++++++ js/src/views/Home/Accounts/accounts.spec.js | 71 +++++++++ .../UrlButton => Home/Accounts}/index.js | 2 +- js/src/views/Home/Dapps/dapp.js | 89 +++++++++++ js/src/views/Home/Dapps/dapp.spec.js | 55 +++++++ js/src/views/Home/Dapps/dapps.css | 48 ++++++ js/src/views/Home/Dapps/dapps.js | 86 +++++++++++ js/src/views/Home/Dapps/dapps.spec.js | 68 +++++++++ js/src/views/Home/Dapps/dapps.test.js | 27 ++++ js/src/views/Home/Dapps/index.js | 17 +++ js/src/views/Home/News/index.js | 17 +++ js/src/views/Home/News/news.css | 73 +++++++++ js/src/views/Home/News/news.js | 92 ++++++++++++ js/src/views/Home/News/news.spec.js | 54 +++++++ js/src/views/Home/News/news.test.js | 54 +++++++ js/src/views/Home/News/renderers.js | 37 +++++ js/src/views/Home/News/store.js | 67 +++++++++ js/src/views/Home/News/store.spec.js | 57 +++++++ js/src/views/Home/Urls/index.js | 17 +++ js/src/views/Home/Urls/urls.css | 80 ++++++++++ js/src/views/Home/Urls/urls.js | 139 ++++++++++++++++++ js/src/views/Home/Urls/urls.spec.js | 124 ++++++++++++++++ .../UrlButton/urlButton.css => Home/home.css} | 24 ++- js/src/views/Home/home.js | 81 ++++++++++ js/src/views/Home/home.spec.js | 96 ++++++++++++ js/src/views/Home/index.js | 17 +++ js/src/views/ParityBar/parityBar.css | 15 +- js/src/views/Settings/Views/defaults.js | 17 +++ js/src/views/Settings/Views/views.css | 6 + js/src/views/Web/store.js | 27 +++- js/src/views/Web/store.spec.js | 33 ++++- js/src/views/historyStore.js | 5 +- js/src/views/index.js | 1 + 59 files changed, 2011 insertions(+), 341 deletions(-) delete mode 100644 js/src/views/Dapps/UrlButton/urlButton.js create mode 100644 js/src/views/Home/Accounts/accounts.css create mode 100644 js/src/views/Home/Accounts/accounts.js create mode 100644 js/src/views/Home/Accounts/accounts.spec.js rename js/src/views/{Dapps/UrlButton => Home/Accounts}/index.js (95%) create mode 100644 js/src/views/Home/Dapps/dapp.js create mode 100644 js/src/views/Home/Dapps/dapp.spec.js create mode 100644 js/src/views/Home/Dapps/dapps.css create mode 100644 js/src/views/Home/Dapps/dapps.js create mode 100644 js/src/views/Home/Dapps/dapps.spec.js create mode 100644 js/src/views/Home/Dapps/dapps.test.js create mode 100644 js/src/views/Home/Dapps/index.js create mode 100644 js/src/views/Home/News/index.js create mode 100644 js/src/views/Home/News/news.css create mode 100644 js/src/views/Home/News/news.js create mode 100644 js/src/views/Home/News/news.spec.js create mode 100644 js/src/views/Home/News/news.test.js create mode 100644 js/src/views/Home/News/renderers.js create mode 100644 js/src/views/Home/News/store.js create mode 100644 js/src/views/Home/News/store.spec.js create mode 100644 js/src/views/Home/Urls/index.js create mode 100644 js/src/views/Home/Urls/urls.css create mode 100644 js/src/views/Home/Urls/urls.js create mode 100644 js/src/views/Home/Urls/urls.spec.js rename js/src/views/{Dapps/UrlButton/urlButton.css => Home/home.css} (74%) create mode 100644 js/src/views/Home/home.js create mode 100644 js/src/views/Home/home.spec.js create mode 100644 js/src/views/Home/index.js diff --git a/js/package.json b/js/package.json index 9097cd18a3..efa3eac9df 100644 --- a/js/package.json +++ b/js/package.json @@ -183,6 +183,7 @@ "react-element-to-jsx-string": "6.0.0", "react-event-listener": "0.4.1", "react-intl": "2.1.5", + "react-markdown": "2.4.4", "react-portal": "3.0.0", "react-redux": "4.4.6", "react-router": "3.0.0", diff --git a/js/src/api/format/output.js b/js/src/api/format/output.js index dc84215163..92a3635660 100644 --- a/js/src/api/format/output.js +++ b/js/src/api/format/output.js @@ -181,6 +181,16 @@ export function outReceipt (receipt) { return receipt; } +export function outRecentDapps (recentDapps) { + if (recentDapps) { + Object.keys(recentDapps).forEach((url) => { + recentDapps[url] = outDate(recentDapps[url]); + }); + } + + return recentDapps; +} + export function outSignerRequest (request) { if (request) { Object.keys(request).forEach((key) => { diff --git a/js/src/api/format/output.spec.js b/js/src/api/format/output.spec.js index 504cc0687a..151353453c 100644 --- a/js/src/api/format/output.spec.js +++ b/js/src/api/format/output.spec.js @@ -16,7 +16,7 @@ import BigNumber from 'bignumber.js'; -import { outBlock, outAccountInfo, outAddress, outChainStatus, outDate, outHistogram, outNumber, outPeer, outPeers, outReceipt, outSyncing, outTransaction, outTrace, outVaultMeta } from './output'; +import { outBlock, outAccountInfo, outAddress, outChainStatus, outDate, outHistogram, outNumber, outPeer, outPeers, outReceipt, outRecentDapps, outSyncing, outTransaction, outTrace, outVaultMeta } from './output'; import { isAddress, isBigNumber, isInstanceOf } from '../../../test/types'; describe('api/format/output', () => { @@ -337,6 +337,14 @@ describe('api/format/output', () => { }); }); + describe('outRecentDapps', () => { + it('formats the URLs with timestamps', () => { + expect(outRecentDapps({ testing: 0x57513668 })).to.deep.equal({ + testing: new Date('2016-06-03T07:48:56.000Z') + }); + }); + }); + describe('outSyncing', () => { ['currentBlock', 'highestBlock', 'startingBlock', 'warpChunksAmount', 'warpChunksProcessed'].forEach((input) => { it(`formats ${input} numbers as a number`, () => { diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index 7001a9c494..d7278d13ab 100644 --- a/js/src/api/rpc/parity/parity.js +++ b/js/src/api/rpc/parity/parity.js @@ -15,7 +15,7 @@ // along with Parity. If not, see . import { inAddress, inAddresses, inData, inHex, inNumber16, inOptions, inBlockNumber } from '../../format/input'; -import { outAccountInfo, outAddress, outAddresses, outChainStatus, outHistogram, outNumber, outPeers, outTransaction, outVaultMeta } from '../../format/output'; +import { outAccountInfo, outAddress, outAddresses, outChainStatus, outHistogram, outNumber, outPeers, outRecentDapps, outTransaction, outVaultMeta } from '../../format/output'; export default class Parity { constructor (transport) { @@ -222,7 +222,8 @@ export default class Parity { listRecentDapps () { return this._transport - .execute('parity_listRecentDapps'); + .execute('parity_listRecentDapps') + .then(outRecentDapps); } listStorageKeys (address, count, hash = null, blockNumber = 'latest') { diff --git a/js/src/i18n/en/settings.js b/js/src/i18n/en/settings.js index c45131da2f..48a656b934 100644 --- a/js/src/i18n/en/settings.js +++ b/js/src/i18n/en/settings.js @@ -48,6 +48,10 @@ export default { label: 'Contracts' }, + home: { + label: 'Home' + }, + status: { label: 'Status' }, diff --git a/js/src/modals/DeployContract/ParametersStep/parametersStep.js b/js/src/modals/DeployContract/ParametersStep/parametersStep.js index 8a2847b2f5..0213bfa013 100644 --- a/js/src/modals/DeployContract/ParametersStep/parametersStep.js +++ b/js/src/modals/DeployContract/ParametersStep/parametersStep.js @@ -91,7 +91,7 @@ export default class ParametersStep extends Component { }); return ( -
+

{ - accountsHistory.add(params.address); + accountsHistory.add(params.address, 'account'); } }, - { path: '/wallet/:address', component: Wallet } + { + path: '/wallet/:address', + component: Wallet, + onEnter: ({ params }) => { + accountsHistory.add(params.address, 'wallet'); + } + } ]; const addressesRoutes = [ @@ -86,8 +92,8 @@ const routes = [ { path: '/address/:address', onEnter: handleDeprecatedRoute }, { path: '/contract/:address', onEnter: handleDeprecatedRoute }, - { path: '/', onEnter: redirectTo('/accounts') }, - { path: '/auth', onEnter: redirectTo('/accounts') }, + { path: '/', onEnter: redirectTo('/home') }, + { path: '/auth', onEnter: redirectTo('/home') }, { path: '/settings', onEnter: redirectTo('/settings/views') } ]; @@ -127,6 +133,7 @@ const childRoutes = [ } }, { path: 'apps', component: Dapps }, + { path: 'home', component: Home }, { path: 'web', component: Web }, { path: 'web/:url', component: Web }, { path: 'signer', component: Signer } diff --git a/js/src/ui/Container/container.css b/js/src/ui/Container/container.css index d329744b66..9f40897e18 100644 --- a/js/src/ui/Container/container.css +++ b/js/src/ui/Container/container.css @@ -14,11 +14,37 @@ /* You should have received a copy of the GNU General Public License /* along with Parity. If not, see . */ + +$background: rgba(18, 18, 18, 0.85); +$backgroundOverlay: rgba(18, 18, 18, 1); + .container { + background: $background; flex: 1; - padding: 0em; - background: rgba(0, 0, 0, 0.8); height: 100%; + padding: 0em; + transition: all 0.75s cubic-bezier(0.23, 1, 0.32, 1); + width: 100%; + + .hoverOverlay { + background: $backgroundOverlay; + display: none; + left: 0; + margin-top: -1.5em; + padding: 0 1.5em 1.5em 1.5em; + position: absolute; + right: 0; + top: 100%; + z-index: 100; + } + + &:hover { + background: $backgroundOverlay; + + .hoverOverlay { + display: block; + } + } } .compact, diff --git a/js/src/ui/Container/container.js b/js/src/ui/Container/container.js index 6956958710..e58d93567e 100644 --- a/js/src/ui/Container/container.js +++ b/js/src/ui/Container/container.js @@ -28,6 +28,7 @@ export default class Container extends Component { children: PropTypes.node, className: PropTypes.string, compact: PropTypes.bool, + hover: PropTypes.node, light: PropTypes.bool, onClick: PropTypes.func, style: PropTypes.object, @@ -68,10 +69,25 @@ export default class Container extends Component { { this.renderTitle() } { children } + { this.renderHover() }

); } + renderHover () { + const { hover } = this.props; + + if (!hover) { + return null; + } + + return ( + + { hover } + + ); + } + renderTitle () { const { title } = this.props; diff --git a/js/src/ui/Container/container.spec.js b/js/src/ui/Container/container.spec.js index 3cefb66c8a..78b625e397 100644 --- a/js/src/ui/Container/container.spec.js +++ b/js/src/ui/Container/container.spec.js @@ -37,10 +37,17 @@ describe('ui/Container', () => { }); describe('sections', () => { - it('renders the Card', () => { + it('renders the default Card', () => { expect(render().find('Card')).to.have.length(1); }); + it('renders Hover Card when available', () => { + const cards = render({ hover:
testingHover
}).find('Card'); + + expect(cards).to.have.length(2); + expect(cards.get(1).props.children.props.children).to.equal('testingHover'); + }); + it('renders the Title', () => { const title = render({ title: 'title' }).find('Title'); diff --git a/js/src/ui/Icons/index.js b/js/src/ui/Icons/index.js index 59cb31de61..62cb02105d 100644 --- a/js/src/ui/Icons/index.js +++ b/js/src/ui/Icons/index.js @@ -14,66 +14,33 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import AddIcon from 'material-ui/svg-icons/content/add'; -import AttachFileIcon from 'material-ui/svg-icons/editor/attach-file'; -import CancelIcon from 'material-ui/svg-icons/content/clear'; -import CheckIcon from 'material-ui/svg-icons/navigation/check'; -import CloseIcon from 'material-ui/svg-icons/navigation/close'; -import CompareIcon from 'material-ui/svg-icons/action/compare-arrows'; -import ComputerIcon from 'material-ui/svg-icons/hardware/desktop-mac'; -import ContractIcon from 'material-ui/svg-icons/action/code'; -import CopyIcon from 'material-ui/svg-icons/content/content-copy'; -import DashboardIcon from 'material-ui/svg-icons/action/dashboard'; -import DeleteIcon from 'material-ui/svg-icons/action/delete'; -import DoneIcon from 'material-ui/svg-icons/action/done-all'; -import EditIcon from 'material-ui/svg-icons/content/create'; -import FingerprintIcon from 'material-ui/svg-icons/action/fingerprint'; -import LinkIcon from 'material-ui/svg-icons/content/link'; -import LockedIcon from 'material-ui/svg-icons/action/lock'; -import MoveIcon from 'material-ui/svg-icons/action/open-with'; -import NextIcon from 'material-ui/svg-icons/navigation/arrow-forward'; -import PrevIcon from 'material-ui/svg-icons/navigation/arrow-back'; -import PrintIcon from 'material-ui/svg-icons/action/print'; -import RefreshIcon from 'material-ui/svg-icons/action/autorenew'; -import SaveIcon from 'material-ui/svg-icons/content/save'; -import SendIcon from 'material-ui/svg-icons/content/send'; -import SnoozeIcon from 'material-ui/svg-icons/av/snooze'; -import StarCircleIcon from 'material-ui/svg-icons/action/stars'; -import StarIcon from 'material-ui/svg-icons/toggle/star'; -import StarOutlineIcon from 'material-ui/svg-icons/toggle/star-border'; -import VerifyIcon from 'material-ui/svg-icons/action/verified-user'; -import VisibleIcon from 'material-ui/svg-icons/image/remove-red-eye'; -import VpnIcon from 'material-ui/svg-icons/notification/vpn-lock'; - -export { - AddIcon, - AttachFileIcon, - CancelIcon, - CheckIcon, - CloseIcon, - CompareIcon, - ComputerIcon, - ContractIcon, - CopyIcon, - DashboardIcon, - DeleteIcon, - DoneIcon, - EditIcon, - FingerprintIcon, - LinkIcon, - LockedIcon, - MoveIcon, - NextIcon, - PrevIcon, - PrintIcon, - RefreshIcon, - SaveIcon, - SendIcon, - SnoozeIcon, - StarIcon, - StarCircleIcon, - StarOutlineIcon, - VerifyIcon, - VisibleIcon, - VpnIcon -}; +export AddIcon from 'material-ui/svg-icons/content/add'; +export AttachFileIcon from 'material-ui/svg-icons/editor/attach-file'; +export CancelIcon from 'material-ui/svg-icons/content/clear'; +export CheckIcon from 'material-ui/svg-icons/navigation/check'; +export CloseIcon from 'material-ui/svg-icons/navigation/close'; +export CompareIcon from 'material-ui/svg-icons/action/compare-arrows'; +export ComputerIcon from 'material-ui/svg-icons/hardware/desktop-mac'; +export ContractIcon from 'material-ui/svg-icons/action/code'; +export CopyIcon from 'material-ui/svg-icons/content/content-copy'; +export DashboardIcon from 'material-ui/svg-icons/action/dashboard'; +export DeleteIcon from 'material-ui/svg-icons/action/delete'; +export DoneIcon from 'material-ui/svg-icons/action/done-all'; +export EditIcon from 'material-ui/svg-icons/content/create'; +export FingerprintIcon from 'material-ui/svg-icons/action/fingerprint'; +export LinkIcon from 'material-ui/svg-icons/content/link'; +export LockedIcon from 'material-ui/svg-icons/action/lock'; +export MoveIcon from 'material-ui/svg-icons/action/open-with'; +export NextIcon from 'material-ui/svg-icons/navigation/arrow-forward'; +export PrevIcon from 'material-ui/svg-icons/navigation/arrow-back'; +export PrintIcon from 'material-ui/svg-icons/action/print'; +export RefreshIcon from 'material-ui/svg-icons/action/autorenew'; +export SaveIcon from 'material-ui/svg-icons/content/save'; +export SendIcon from 'material-ui/svg-icons/content/send'; +export SnoozeIcon from 'material-ui/svg-icons/av/snooze'; +export StarCircleIcon from 'material-ui/svg-icons/action/stars'; +export StarIcon from 'material-ui/svg-icons/toggle/star'; +export StarOutlineIcon from 'material-ui/svg-icons/toggle/star-border'; +export VerifyIcon from 'material-ui/svg-icons/action/verified-user'; +export VisibleIcon from 'material-ui/svg-icons/image/remove-red-eye'; +export VpnIcon from 'material-ui/svg-icons/notification/vpn-lock'; diff --git a/js/src/ui/SectionList/sectionList.css b/js/src/ui/SectionList/sectionList.css index 02340fd550..b338f981d7 100644 --- a/js/src/ui/SectionList/sectionList.css +++ b/js/src/ui/SectionList/sectionList.css @@ -16,8 +16,8 @@ */ .section { - overflow-x: hidden; position: relative; + width: 100%; .overlay { background: rgba(0, 0, 0, 0.85); @@ -33,7 +33,6 @@ .row { display: flex; justify-content: center; - overflow-x: hidden; /* TODO: As per JS comments, the flex-base could be adjusted in the future to allow for */ /* case where <> 3 columns are required should the need arrise from a UI pov. */ @@ -42,38 +41,28 @@ cursor: pointer; display: flex; flex: 0 1 33.33%; - opacity: 0.75; - overflow-x: hidden; + max-width: 33.33%; + opacity: 0.85; padding: 0.25em; transition: all 0.75s cubic-bezier(0.23, 1, 0.32, 1); - /* TODO: The hover and no-hover states can be improved to not "just appear" */ - &:not(:hover) { - & [data-hover="hide"] { - } - - & [data-hover="show"] { - display: none; - } - } - &:hover { opacity: 1; z-index: 100; - - & [data-hover="hide"] { - display: none; - } - - & [data-hover="show"] { - } } + } - &.stretch-on:hover { - flex: 0 0 50%; - } + &:hover { + .item { + &.stretchOn { + flex: 0 1 29%; + max-width: 29%; - &.stretch-off:hover { + &:hover { + flex: 0 0 42%; + max-width: 42%; + } + } } } } diff --git a/js/src/ui/SectionList/sectionList.js b/js/src/ui/SectionList/sectionList.js index 5e9106549e..b326b9ed83 100644 --- a/js/src/ui/SectionList/sectionList.js +++ b/js/src/ui/SectionList/sectionList.js @@ -74,29 +74,34 @@ export default class SectionList extends Component { className={ styles.row } key={ `row_${index}` } > - { row.map(this.renderItem) } + { + row + .map(this.renderItem) + .filter((item) => item) + }
); } renderItem = (item, index) => { const { noStretch, renderItem } = this.props; + const itemRendered = renderItem(item, index); + + if (!itemRendered) { + return null; + } - // NOTE: Any children that is to be showed or hidden (depending on hover state) - // should have the data-hover="show|hide" attributes. For the current implementation - // this does the trick, however there may be a case for adding a hover attribute - // to an item (mouseEnter/mouseLeave events) and then adjusting the styling with - // :root[hover]/:root:not[hover] for the tragetted elements. Currently it is a - // CSS-only solution to let the browser do all the work via selectors. return (
- { renderItem(item, index) } + { itemRendered }
); } diff --git a/js/src/ui/SectionList/sectionList.spec.js b/js/src/ui/SectionList/sectionList.spec.js index 80d66a04ae..9022db79dd 100644 --- a/js/src/ui/SectionList/sectionList.spec.js +++ b/js/src/ui/SectionList/sectionList.spec.js @@ -27,7 +27,7 @@ let instance; let renderItem; function render (props = {}) { - renderItem = sinon.stub(); + renderItem = sinon.stub().returns('someThing'); component = shallow( . */ + .list { display: flex; flex-wrap: wrap; -} -.item { - flex: 0 1 50%; - width: 50%; - position: relative; - padding-bottom: 0.25em; - box-sizing: border-box; -} + .item { + box-sizing: border-box; + flex: 0 1 50%; + padding-bottom: 0.25em; + position: relative; + width: 50%; -.item:nth-child(odd) { - padding-right: 0.125em; -} + &:nth-child(odd) { + padding-right: 0.125em; + } -.item:nth-child(even) { - padding-left: 0.125em; + &:nth-child(even) { + padding-left: 0.125em; + } + } } .empty { - width: 100%; display: block; -} + width: 100%; -.empty div { - color: #aaa; + div { + color: #aaa; + } } diff --git a/js/src/views/Accounts/Summary/summary.js b/js/src/views/Accounts/Summary/summary.js index 6829b8914c..3717c18953 100644 --- a/js/src/views/Accounts/Summary/summary.js +++ b/js/src/views/Accounts/Summary/summary.js @@ -193,7 +193,11 @@ export default class Summary extends Component { const viewLink = `/${baseLink}/${address}`; const content = ( - + ); if (noLink) { diff --git a/js/src/views/Application/Extension/extension.js b/js/src/views/Application/Extension/extension.js index aff332f9ae..0c033225b7 100644 --- a/js/src/views/Application/Extension/extension.js +++ b/js/src/views/Application/Extension/extension.js @@ -26,7 +26,7 @@ import styles from './extension.css'; @observer export default class Extension extends Component { - store = new Store(); + store = Store.get(); render () { const { showWarning } = this.store; diff --git a/js/src/views/Application/Extension/store.js b/js/src/views/Application/Extension/store.js index 965598f032..d1df2eb5b6 100644 --- a/js/src/views/Application/Extension/store.js +++ b/js/src/views/Application/Extension/store.js @@ -29,7 +29,10 @@ const NEXT_DISPLAY = '_parity::extensionWarning::nextDisplay'; // 'https://chrome.google.com/webstore/detail/parity-ethereum-integrati/himekenlppkgeaoeddcliojfddemadig'; const EXTENSION_PAGE = 'https://chrome.google.com/webstore/detail/himekenlppkgeaoeddcliojfddemadig'; +let instance; + export default class Store { + @observable hasExtension = false; @observable isInstalling = false; @observable nextDisplay = 0; @observable shouldInstall = false; @@ -43,6 +46,10 @@ export default class Store { return !this.isInstalling && this.shouldInstall && (Date.now() > this.nextDisplay); } + @action setExtensionActive = () => { + this.hasExtension = true; + } + @action setInstalling = (isInstalling) => { this.isInstalling = isInstalling; } @@ -61,6 +68,7 @@ export default class Store { const ua = browser.analyze(navigator.userAgent || ''); if (hasExtension) { + this.setExtensionActive(); return false; } @@ -97,4 +105,12 @@ export default class Store { } }); } + + static get () { + if (!instance) { + instance = new Store(); + } + + return instance; + } } diff --git a/js/src/views/Application/TabBar/tabBar.css b/js/src/views/Application/TabBar/tabBar.css index e9a1900347..4ac00b8486 100644 --- a/js/src/views/Application/TabBar/tabBar.css +++ b/js/src/views/Application/TabBar/tabBar.css @@ -14,6 +14,7 @@ /* You should have received a copy of the GNU General Public License /* along with Parity. If not, see . */ + .toolbar { background: none !important; height: 72px !important; @@ -53,7 +54,7 @@ .tabLink, .settings, -.logo, +.first, .last { background: rgba(0, 0, 0, 0.5) !important; /* rgba(0, 0, 0, 0.25) !important; */ } @@ -73,27 +74,17 @@ right: -12px; } -.logo { - margin: 0 0 0 -24px; - padding: 20px 24px; +.first, +.last { + margin: 0; + padding: 36px 12px; white-space: nowrap; } -.logo img { - height: 28px; - margin-right: 0.75em; -} - -.logo div { - display: inline-block; - text-transform: uppercase; - line-height: 32px; - vertical-align: top; - letter-spacing: 0.2em; +.first { + margin-left: -24px; } .last { - margin: 0 -24px 0 0; - padding: 36px 12px; - white-space: nowrap; + margin-right: -24px; } diff --git a/js/src/views/Application/TabBar/tabBar.js b/js/src/views/Application/TabBar/tabBar.js index b2347f8541..939e1b298a 100644 --- a/js/src/views/Application/TabBar/tabBar.js +++ b/js/src/views/Application/TabBar/tabBar.js @@ -20,13 +20,16 @@ import { Link } from 'react-router'; import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar'; import { isEqual } from 'lodash'; -import imagesEthcoreBlock from '~/../assets/images/parity-logo-white-no-text.svg'; import { Tooltip } from '~/ui'; import Tab from './Tab'; import styles from './tabBar.css'; class TabBar extends Component { + static contextTypes = { + router: PropTypes.object.isRequired + }; + static propTypes = { isTest: PropTypes.bool, netChain: PropTypes.string, @@ -41,72 +44,48 @@ class TabBar extends Component { render () { return ( - { this.renderLogo() } - { this.renderTabs() } - { this.renderLast() } - - ); - } - - renderLogo () { - return ( - -
- -
-
- ); - } - - renderLast () { - return ( - -
+
+ +
+ { this.renderTabItems() }
- + +
+ + ); } - renderTabs () { + renderTabItems () { const { views, pending } = this.props; - const items = views - .map((view, index) => { - const body = (view.id === 'accounts') - ? ( - - ) - : null; - - return ( - { + const body = (view.id === 'accounts') + ? ( + + ) + : null; + + return ( + + - - { body } - - - ); - }); - - return ( -
- { items } -
- ); + { body } +
+ + ); + }); } } diff --git a/js/src/views/Dapps/UrlButton/urlButton.js b/js/src/views/Dapps/UrlButton/urlButton.js deleted file mode 100644 index 5f46225a9c..0000000000 --- a/js/src/views/Dapps/UrlButton/urlButton.js +++ /dev/null @@ -1,99 +0,0 @@ -// 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 . - -import React, { Component, PropTypes } from 'react'; -import { FormattedMessage } from 'react-intl'; -import { withRouter } from 'react-router'; - -import Button from '~/ui/Button'; -import { LinkIcon } from '~/ui/Icons'; -import Input from '~/ui/Form/Input'; - -import styles from './urlButton.css'; - -const INPUT_STYLE = { display: 'inline-block', width: '20em' }; - -class UrlButton extends Component { - static propTypes = { - router: PropTypes.object.isRequired // injected by withRouter - }; - - state = { - inputShown: false - }; - - render () { - const { inputShown } = this.state; - - return ( -
- { inputShown ? this.renderInput() : null } -
- ); - } - - renderInput () { - return ( - - } - onBlur={ this.hideInput } - onFocus={ this.showInput } - onSubmit={ this.inputOnSubmit } - style={ INPUT_STYLE } - /> - ); - } - - toggleInput = () => { - const { inputShown } = this.state; - - this.setState({ - inputShown: !inputShown - }); - } - - hideInput = () => { - this.setState({ inputShown: false }); - } - - showInput = () => { - this.setState({ inputShown: true }); - } - - inputOnSubmit = (url) => { - const { router } = this.props; - - router.push(`/web/${encodeURIComponent(url)}`); - } -} - -export default withRouter(UrlButton); diff --git a/js/src/views/Dapps/dapps.css b/js/src/views/Dapps/dapps.css index d16317d607..6e3bdc14f2 100644 --- a/js/src/views/Dapps/dapps.css +++ b/js/src/views/Dapps/dapps.css @@ -19,18 +19,20 @@ flex-wrap: wrap; margin: -0.125em; position: relative; + + .item { + box-sizing: border-box; + flex: 0 1 50%; + opacity: 0.85; + padding: 0.125em; + } + } .list+.list { margin-top: -0.25em; } -.item { - padding: 0.125em; - flex: 0 1 50%; - box-sizing: border-box; -} - .overlay { background: rgba(0, 0, 0, 0.85); bottom: 0.5em; diff --git a/js/src/views/Dapps/dapps.js b/js/src/views/Dapps/dapps.js index fc196fad89..cfdb2e7169 100644 --- a/js/src/views/Dapps/dapps.js +++ b/js/src/views/Dapps/dapps.js @@ -26,7 +26,6 @@ import PermissionStore from '~/modals/DappPermissions/store'; import { Actionbar, Button, DappCard, Page } from '~/ui'; import { LockedIcon, VisibleIcon } from '~/ui/Icons'; -import UrlButton from './UrlButton'; import DappsStore from './dappsStore'; import styles from './dapps.css'; @@ -92,7 +91,6 @@ class Dapps extends Component { /> } buttons={ [ - ,
+ } + > + + + + + + ); + } +} + +function mapStateToProps (state) { + const { accountsInfo } = state.personal; + + return { + accountsInfo + }; +} + +export default connect( + mapStateToProps, + null +)(Accounts); diff --git a/js/src/views/Home/Accounts/accounts.spec.js b/js/src/views/Home/Accounts/accounts.spec.js new file mode 100644 index 0000000000..368ebcedba --- /dev/null +++ b/js/src/views/Home/Accounts/accounts.spec.js @@ -0,0 +1,71 @@ +// 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 . + +import { shallow } from 'enzyme'; +import React from 'react'; +import sinon from 'sinon'; + +import Accounts from './'; + +let component; +let store; + +function createRedux () { + store = { + dispatch: sinon.stub(), + subscribe: sinon.stub(), + getState: () => { + return { + personal: { + accountsInfo: { '0x123': {} } + } + }; + } + }; + + return store; +} + +function render (history = []) { + component = shallow( + , + { + context: { + store: createRedux() + } + } + ).find('Accounts').shallow(); + + return component; +} + +describe('views/Home/Accounts', () => { + it('renders defaults', () => { + expect(render()).to.be.ok; + }); + + describe('no history', () => { + beforeEach(() => { + render(); + }); + + it('renders empty message', () => { + expect(component.find('FormattedMessage').props().id).to.equal('home.accounts.none'); + }); + }); +}); diff --git a/js/src/views/Dapps/UrlButton/index.js b/js/src/views/Home/Accounts/index.js similarity index 95% rename from js/src/views/Dapps/UrlButton/index.js rename to js/src/views/Home/Accounts/index.js index 173beaadd3..027387e709 100644 --- a/js/src/views/Dapps/UrlButton/index.js +++ b/js/src/views/Home/Accounts/index.js @@ -14,4 +14,4 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -export default from './urlButton'; +export default from './accounts'; diff --git a/js/src/views/Home/Dapps/dapp.js b/js/src/views/Home/Dapps/dapp.js new file mode 100644 index 0000000000..51e5c21220 --- /dev/null +++ b/js/src/views/Home/Dapps/dapp.js @@ -0,0 +1,89 @@ +// 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 . + +import moment from 'moment'; +import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { Link } from 'react-router'; + +import { Container, DappIcon } from '~/ui'; + +import styles from './dapps.css'; + +export default class Dapp extends Component { + static propTypes = { + id: PropTypes.string.isRequired, + store: PropTypes.object.isRequired, + timestamp: PropTypes.number.isRequired + } + + state = { + dapp: null + } + + componentWillMount () { + return this.loadApp(); + } + + render () { + const { id, timestamp } = this.props; + const { dapp } = this.state; + + if (!dapp) { + return null; + } + + return ( + + +
+ } + > + + + + { dapp.name } + + + + ); + } + + loadApp = () => { + const { id, store } = this.props; + + return store + .loadApp(id) + .then((dapp) => { + this.setState({ dapp }); + }); + } +} diff --git a/js/src/views/Home/Dapps/dapp.spec.js b/js/src/views/Home/Dapps/dapp.spec.js new file mode 100644 index 0000000000..d7c5b09bdd --- /dev/null +++ b/js/src/views/Home/Dapps/dapp.spec.js @@ -0,0 +1,55 @@ +// 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 . + +import { shallow } from 'enzyme'; +import React from 'react'; + +import Dapp from './dapp'; + +import { createStore } from './dapps.test.js'; + +let component; +let instance; +let store; + +function render () { + store = createStore(); + component = shallow( + + ); + instance = component.instance(); + + return component; +} + +describe('views/Home/Dapp', () => { + beforeEach(() => { + render(); + return instance.componentWillMount(); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + it('loads the dapp on mount', () => { + expect(store.loadApp).to.have.been.calledWith('testId'); + }); +}); diff --git a/js/src/views/Home/Dapps/dapps.css b/js/src/views/Home/Dapps/dapps.css new file mode 100644 index 0000000000..b8e9c01df9 --- /dev/null +++ b/js/src/views/Home/Dapps/dapps.css @@ -0,0 +1,48 @@ +/* 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 . +*/ + +.dapps { + margin-top: 1.5em; + text-align: center; + + .dapp { + position: relative; + line-height: 2em; + vertical-align: middle; + + .icon { + margin: 0; + } + + .link, .name { + display: block; + overflow: hidden; + text-overflow: ellipsis; + } + + .name { + white-space: nowrap; + } + + .timestamp { + color: #aaa; + font-size: 0.75em; + line-height: 1em; + padding-top: 0; + } + } +} diff --git a/js/src/views/Home/Dapps/dapps.js b/js/src/views/Home/Dapps/dapps.js new file mode 100644 index 0000000000..608d4435b3 --- /dev/null +++ b/js/src/views/Home/Dapps/dapps.js @@ -0,0 +1,86 @@ +// 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 . + +import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; + +import { ContainerTitle, SectionList } from '~/ui'; +import { arrayOrObjectProptype } from '~/util/proptypes'; + +import Dapp from './dapp'; +import styles from './dapps.css'; + +export default class Dapps extends Component { + static propTypes = { + history: arrayOrObjectProptype().isRequired, + store: PropTypes.object.isRequired + } + + render () { + return ( +
+ + } + /> + { this.renderHistory() } +
+ ); + } + + renderHistory () { + const { history } = this.props; + + if (!history.length) { + return ( +
+ +
+ ); + } + + return ( + + ); + } + + renderHistoryItem = (history) => { + if (!history || !history.entry) { + return null; + } + + const { store } = this.props; + + return ( + + ); + } +} diff --git a/js/src/views/Home/Dapps/dapps.spec.js b/js/src/views/Home/Dapps/dapps.spec.js new file mode 100644 index 0000000000..8dcb938f47 --- /dev/null +++ b/js/src/views/Home/Dapps/dapps.spec.js @@ -0,0 +1,68 @@ +// 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 . + +import { shallow } from 'enzyme'; +import React from 'react'; + +import Dapps from './'; + +import { createStore } from './dapps.test.js'; + +let component; +let store; + +function render (history = []) { + store = createStore(); + component = shallow( + + ); + + return component; +} + +describe('views/Home/Dapps', () => { + it('renders defaults', () => { + expect(render()).to.be.ok; + }); + + describe('no history', () => { + beforeEach(() => { + render(); + }); + + it('renders empty message', () => { + expect(component.find('FormattedMessage').props().id).to.equal('home.dapps.none'); + }); + }); + + describe('with history', () => { + const HISTORY = [ + { timestamp: 1, entry: 'testABC' }, + { timestamp: 2, entry: 'testDEF' } + ]; + + beforeEach(() => { + render(HISTORY); + }); + + it('renders SectionList', () => { + expect(component.find('SectionList').length).to.equal(1); + }); + }); +}); diff --git a/js/src/views/Home/Dapps/dapps.test.js b/js/src/views/Home/Dapps/dapps.test.js new file mode 100644 index 0000000000..3593d7f3e8 --- /dev/null +++ b/js/src/views/Home/Dapps/dapps.test.js @@ -0,0 +1,27 @@ +// 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 . + +import sinon from 'sinon'; + +function createStore () { + return { + loadApp: sinon.stub().resolves({ name: 'testName' }) + }; +} + +export { + createStore +}; diff --git a/js/src/views/Home/Dapps/index.js b/js/src/views/Home/Dapps/index.js new file mode 100644 index 0000000000..9e6dddb632 --- /dev/null +++ b/js/src/views/Home/Dapps/index.js @@ -0,0 +1,17 @@ +// 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 from './dapps'; diff --git a/js/src/views/Home/News/index.js b/js/src/views/Home/News/index.js new file mode 100644 index 0000000000..437e53a6e9 --- /dev/null +++ b/js/src/views/Home/News/index.js @@ -0,0 +1,17 @@ +// 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 from './news'; diff --git a/js/src/views/Home/News/news.css b/js/src/views/Home/News/news.css new file mode 100644 index 0000000000..f6a19affa2 --- /dev/null +++ b/js/src/views/Home/News/news.css @@ -0,0 +1,73 @@ +/* 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 . +*/ + +.news { +} + +.markdown { + line-height: 1.2em; +} + +.item { + height: 240px; + opacity: 0.85; + position: relative; + width: 100%; + + .background { + background-repeat: no-repeat; + background-position: 50% 50%; + background-size: cover; + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; + width: auto; + } + + .overlay { + background: white; + color: #333; + display: none; + left: 0; + position: absolute; + right: 0; + top: 100%; + padding: 0 1.5em 1em 1.5em; + } + + .title { + background: rgba(255, 255, 255, 0.85); + bottom: 0; + color: #333; + font-size: 1.17em; + left: 0; + padding: 1rem 1.5rem; + position: absolute; + right: 0; + text-transform: uppercase; + } + + &:hover { + opacity: 1; + + .overlay { + display: block; + } + } +} diff --git a/js/src/views/Home/News/news.js b/js/src/views/Home/News/news.js new file mode 100644 index 0000000000..8af395a6b7 --- /dev/null +++ b/js/src/views/Home/News/news.js @@ -0,0 +1,92 @@ +// 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 . + +import { observer } from 'mobx-react'; +import React, { Component } from 'react'; +import ReactMarkdown from 'react-markdown'; + +import { SectionList } from '~/ui'; + +import { createRenderers } from './renderers'; +import Store from './store'; +import styles from './news.css'; + +const VERSION_ID = '1'; + +@observer +export default class News extends Component { + store = Store.get(); + + componentWillMount () { + return this.store.retrieveNews(VERSION_ID); + } + + render () { + const { newsItems } = this.store; + + if (!newsItems || !newsItems.length) { + return null; + } + + return ( + + ); + } + + renderItem = (item) => { + if (!item) { + return null; + } + + const inlineStyles = item.style || {}; + + return ( +
+
+
+ { item.title } +
+
+ +
+
+ ); + } +} + +export { + VERSION_ID +}; diff --git a/js/src/views/Home/News/news.spec.js b/js/src/views/Home/News/news.spec.js new file mode 100644 index 0000000000..6da5c2919f --- /dev/null +++ b/js/src/views/Home/News/news.spec.js @@ -0,0 +1,54 @@ +// 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 . + +import { shallow } from 'enzyme'; +import React from 'react'; + +import News from './news'; +import { restoreGlobals, stubGlobals } from './news.test.js'; + +let component; +let instance; + +function render () { + component = shallow( + + ); + instance = component.instance(); + + return component; +} + +describe('views/Home/News', () => { + beforeEach(() => { + stubGlobals(); + render(); + + return instance.componentWillMount(); + }); + + afterEach(() => { + restoreGlobals(); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + it('retrieves the content meta on mount', () => { + expect(instance.store.newsItems).to.equal('testContent'); + }); +}); diff --git a/js/src/views/Home/News/news.test.js b/js/src/views/Home/News/news.test.js new file mode 100644 index 0000000000..4b45502a78 --- /dev/null +++ b/js/src/views/Home/News/news.test.js @@ -0,0 +1,54 @@ +// 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 . + +import sinon from 'sinon'; + +import Contracts from '~/contracts'; + +import { VERSION_ID } from './news'; + +let contracts; +let globalContractsGet; +let globalFetch; + +export function stubGlobals () { + contracts = { + githubHint: { + getEntry: sinon.stub().resolves(['testUrl', 'testOwner', 'testCommit']) + }, + registry: { + lookupMeta: sinon.stub().resolves('testMeta') + } + }; + + globalContractsGet = Contracts.get; + globalFetch = global.fetch; + + sinon.stub(Contracts, 'get', () => contracts); + sinon.stub(global, 'fetch').resolves({ + ok: true, + json: sinon.stub().resolves({ + [VERSION_ID]: { + items: 'testContent' + } + }) + }); +} + +export function restoreGlobals () { + Contracts.get = globalContractsGet; + global.fetch = globalFetch; +} diff --git a/js/src/views/Home/News/renderers.js b/js/src/views/Home/News/renderers.js new file mode 100644 index 0000000000..fde4760296 --- /dev/null +++ b/js/src/views/Home/News/renderers.js @@ -0,0 +1,37 @@ +// 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 . + +import { createElement } from 'react'; + +export function createRenderers (tagStyles = {}) { + return Object + .keys(tagStyles) + .reduce((renderers, tag) => { + switch (tag) { + case 'a': + case 'link': + renderers['link'] = (mdProps) => { + const { children, href, title } = mdProps; + const style = tagStyles[tag]; + + return createElement('a', { href, title, style }, children); + }; + break; + } + + return renderers; + }, {}); +} diff --git a/js/src/views/Home/News/store.js b/js/src/views/Home/News/store.js new file mode 100644 index 0000000000..dd07d3b80b --- /dev/null +++ b/js/src/views/Home/News/store.js @@ -0,0 +1,67 @@ +// 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 . + +import { action, observable } from 'mobx'; +import Contracts from '~/contracts'; + +let instance = null; + +export default class Store { + @observable newsItems = null; + + @action setNewsItems = (newsItems) => { + this.newsItems = newsItems; + } + + retrieveNews (versionId) { + const contracts = Contracts.get(); + + return contracts.registry + .lookupMeta('paritynews', 'CONTENT') + .then((contentId) => { + return contracts.githubHint.getEntry(contentId); + }) + .then(([url, owner, commit]) => { + if (!url) { + return null; + } + + return fetch(url).then((response) => { + if (!response.ok) { + return null; + } + + return response.json(); + }); + }) + .then((news) => { + if (news && news[versionId]) { + this.setNewsItems(news[versionId].items); + } + }) + .catch((error) => { + console.warn('retrieveNews', error); + }); + } + + static get () { + if (!instance) { + instance = new Store(); + } + + return instance; + } +} diff --git a/js/src/views/Home/News/store.spec.js b/js/src/views/Home/News/store.spec.js new file mode 100644 index 0000000000..6e77e0ee88 --- /dev/null +++ b/js/src/views/Home/News/store.spec.js @@ -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 . + +import { VERSION_ID } from './news'; +import { restoreGlobals, stubGlobals } from './news.test.js'; +import Store from './store'; + +let store; + +function create () { + store = new Store(); + + return store; +} + +describe('views/Home/News/Store', () => { + beforeEach(() => { + stubGlobals(); + create(); + }); + + afterEach(() => { + restoreGlobals(); + }); + + describe('@action', () => { + describe('setNewsItems', () => { + it('sets the items', () => { + store.setNewsItems('testing'); + expect(store.newsItems).to.equal('testing'); + }); + }); + }); + + describe('operations', () => { + describe('retrieveNews', () => { + it('retrieves the items', () => { + return store.retrieveNews(VERSION_ID).then(() => { + expect(store.newsItems).to.equal('testContent'); + }); + }); + }); + }); +}); diff --git a/js/src/views/Home/Urls/index.js b/js/src/views/Home/Urls/index.js new file mode 100644 index 0000000000..d28700edbf --- /dev/null +++ b/js/src/views/Home/Urls/index.js @@ -0,0 +1,17 @@ +// 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 from './urls'; diff --git a/js/src/views/Home/Urls/urls.css b/js/src/views/Home/Urls/urls.css new file mode 100644 index 0000000000..5b5deeb5b7 --- /dev/null +++ b/js/src/views/Home/Urls/urls.css @@ -0,0 +1,80 @@ +/* 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 . +*/ + +.urls { + text-align: center; + + .layout { + box-sizing: border-box; + margin: 1.5em auto; + padding: 0 1em; + width: 50%; + + .empty { + margin-top: 0.5em; + opacity: 0.75; + } + + .historyItem { + height: auto; + margin-top: 1em; + position: relative; + width: 100%; + + .linkIcon { + opacity: 0; + position: absolute; + right: 0.5em; + top: 0.5em; + } + + .url { + display: block; + color: rgb(0, 151, 167); + overflow: hidden; + text-overflow: ellipsis; + } + + .timestamp { + color: #aaa; + font-size: 0.75em; + line-height: 1em; + padding-top: 0.5rem; + } + + &:hover { + opacity: 1; + + .linkIcon { + opacity: 1; + } + } + } + + .input { + background: rgba(255, 255, 255, 0.25); + border: 1px solid rgba(255, 255, 255, 0.5); + border-radius: 0.25em; + box-sizing: border-box; + color: white; + display: block; + font-size: 1.25em; + padding: 0.5em; + width: 100%; + } + } +} diff --git a/js/src/views/Home/Urls/urls.js b/js/src/views/Home/Urls/urls.js new file mode 100644 index 0000000000..7ded1ecdaf --- /dev/null +++ b/js/src/views/Home/Urls/urls.js @@ -0,0 +1,139 @@ +// 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 . + +import { observer } from 'mobx-react'; +import moment from 'moment'; +import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; + +import { Container, ContainerTitle, DappUrlInput, SectionList } from '~/ui'; +import { LinkIcon } from '~/ui/Icons'; + +import styles from './urls.css'; + +@observer +export default class Urls extends Component { + static contextTypes = { + router: PropTypes.object.isRequired + }; + + static propTypes = { + extensionStore: PropTypes.object.isRequired, + store: PropTypes.object.isRequired + } + + render () { + const { nextUrl } = this.props.store; + + return ( +
+
+ + } + /> + + { this.renderHistory() } +
+
+ ); + } + + renderHistory () { + const { history } = this.props.store; + + if (!history.length) { + return ( +
+ +
+ ); + } + + return ( + + ); + } + + renderHistoryItem = (history) => { + if (!history || !history.url) { + return null; + } + + const onNavigate = () => this.onGotoUrl(history.url); + + return ( + + +
+ } + key={ history.timestamp } + onClick={ onNavigate } + > + +
+ { history.hostname } +
+ + ); + } + + onChangeUrl = (url) => { + this.props.store.setNextUrl(url); + } + + onGotoUrl = (url) => { + const { router } = this.context; + const { extensionStore } = this.props; + + this.props.store.gotoUrl(url); + + if (extensionStore.hasExtension) { + window.open(this.props.store.currentUrl, '_blank'); + } else { + router.push('/web'); + } + } + + onRestoreUrl = () => { + this.props.store.restoreUrl(); + } +} diff --git a/js/src/views/Home/Urls/urls.spec.js b/js/src/views/Home/Urls/urls.spec.js new file mode 100644 index 0000000000..6c1438fe0b --- /dev/null +++ b/js/src/views/Home/Urls/urls.spec.js @@ -0,0 +1,124 @@ +// 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 . + +import { shallow } from 'enzyme'; +import React from 'react'; +import sinon from 'sinon'; + +import Urls from './'; + +const NEXT_URL = 'http://somewhere.next'; + +let component; +let instance; +let router; +let store; + +function createRouter () { + router = { + push: sinon.stub() + }; + + return router; +} + +function createStore () { + store = { + history: [], + gotoUrl: sinon.stub(), + restoreUrl: sinon.stub(), + setNextUrl: sinon.stub(), + nextUrl: NEXT_URL + }; + + return store; +} + +function render () { + component = shallow( + , + { + context: { + router: createRouter() + } + } + ); + instance = component.instance(); + + return component; +} + +describe('views/Home/Urls', () => { + beforeEach(() => { + render(); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + describe('input', () => { + let input; + + beforeEach(() => { + input = component.find('DappUrlInput'); + }); + + it('renders the input cmponent', () => { + expect(input.length).to.equal(1); + }); + + it('passes nextUrl as url', () => { + expect(input.props().url).to.equal(NEXT_URL); + }); + }); + + describe('events', () => { + describe('onChangeUrl', () => { + it('performs setNextUrl on store', () => { + instance.onChangeUrl('123'); + expect(store.setNextUrl).to.have.been.calledWith('123'); + }); + }); + + describe('onGotoUrl', () => { + it('performs gotoUrl on store', () => { + instance.onGotoUrl(); + expect(store.gotoUrl).to.have.been.called; + }); + + it('passed the URL when provided', () => { + instance.onGotoUrl('http://example.com'); + expect(store.gotoUrl).to.have.been.calledWith('http://example.com'); + }); + + it('does route navigation when executed', () => { + instance.onGotoUrl(); + expect(router.push).to.have.been.calledWith('/web'); + }); + }); + + describe('onRestoreUrl', () => { + it('performs restoreUrl on store', () => { + instance.onRestoreUrl(); + expect(store.restoreUrl).to.have.been.called; + }); + }); + }); +}); diff --git a/js/src/views/Dapps/UrlButton/urlButton.css b/js/src/views/Home/home.css similarity index 74% rename from js/src/views/Dapps/UrlButton/urlButton.css rename to js/src/views/Home/home.css index ce2f786932..c607e4993b 100644 --- a/js/src/views/Dapps/UrlButton/urlButton.css +++ b/js/src/views/Home/home.css @@ -15,6 +15,26 @@ /* along with Parity. If not, see . */ -.button { - vertical-align: middle; +.accounts { + margin-top: 1.5em; +} + +.body { + padding-bottom: 3em; +} + +.empty { + margin-top: 1.5em; + opacity: 0.5; +} + +.row { + display: flex; + + .column { + box-sizing: border-box; + flex: 0 1 50%; + padding: 0 1.5em; + width: 50%; + } } diff --git a/js/src/views/Home/home.js b/js/src/views/Home/home.js new file mode 100644 index 0000000000..e1ebd37a59 --- /dev/null +++ b/js/src/views/Home/home.js @@ -0,0 +1,81 @@ +// 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 . + +import { observer } from 'mobx-react'; +import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; + +import { Page } from '~/ui'; + +import DappsStore from '../Dapps/dappsStore'; +import ExtensionStore from '../Application/Extension/store'; +import HistoryStore from '../historyStore'; +import WebStore from '../Web/store'; + +import Accounts from './Accounts'; +import Dapps from './Dapps'; +import News from './News'; +import Urls from './Urls'; +import styles from './home.css'; + +@observer +export default class Home extends Component { + static contextTypes = { + api: PropTypes.object.isRequired + }; + + dappsStore = DappsStore.get(this.context.api); + extensionStore = ExtensionStore.get(); + webStore = WebStore.get(this.context.api); + + accountsHistory = HistoryStore.get('accounts'); + dappsHistory = HistoryStore.get('dapps'); + + componentWillMount () { + return this.webStore.loadHistory(); + } + + render () { + return ( + + } + > + + +
+
+ +
+
+ +
+
+
+ ); + } +} diff --git a/js/src/views/Home/home.spec.js b/js/src/views/Home/home.spec.js new file mode 100644 index 0000000000..2fccccee12 --- /dev/null +++ b/js/src/views/Home/home.spec.js @@ -0,0 +1,96 @@ +// 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 . + +import { shallow } from 'enzyme'; +import React from 'react'; +import sinon from 'sinon'; + +import Home from './'; + +const TEST_APP_HISTORY = []; + +let api; +let component; +let instance; + +function createApi () { + api = { + parity: { + listRecentDapps: sinon.stub().resolves(TEST_APP_HISTORY) + } + }; + + return api; +} + +function render () { + component = shallow( + , + { + context: { + api: createApi() + } + } + ); + instance = component.instance(); + + return component; +} + +describe('views/Home', () => { + beforeEach(() => { + render(); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + describe('lifecycle', () => { + describe('componentWillMount', () => { + beforeEach(() => { + sinon.stub(instance.webStore, 'loadHistory'); + return instance.componentWillMount(); + }); + + afterEach(() => { + instance.webStore.loadHistory.restore(); + }); + + it('calls into webStore loadHistory', () => { + expect(instance.webStore.loadHistory).to.have.been.called; + }); + }); + }); + + describe('components', () => { + it('renders Accounts', () => { + expect(component.find('Connect(Accounts)').length).to.equal(1); + }); + + it('renders Dapps', () => { + expect(component.find('Dapps').length).to.equal(1); + }); + + it('renders News', () => { + expect(component.find('News').length).to.equal(1); + }); + + it('renders Urls', () => { + expect(component.find('Urls').length).to.equal(1); + }); + }); +}); diff --git a/js/src/views/Home/index.js b/js/src/views/Home/index.js new file mode 100644 index 0000000000..12e31704a7 --- /dev/null +++ b/js/src/views/Home/index.js @@ -0,0 +1,17 @@ +// 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 from './home'; diff --git a/js/src/views/ParityBar/parityBar.css b/js/src/views/ParityBar/parityBar.css index 6693f5727c..64b0b35212 100644 --- a/js/src/views/ParityBar/parityBar.css +++ b/js/src/views/ParityBar/parityBar.css @@ -19,20 +19,7 @@ $overlayZ: 10000; $modalZ: 10001; .account { - display: flex; - flex: 1; - overflow: hidden; - position: relative; - - .accountOverlay { - position: absolute; - right: 0.5em; - top: 0.5em; - } - - .iconDisabled { - opacity: 0.15; - } + width: 100%; .selected, .unselected { diff --git a/js/src/views/Settings/Views/defaults.js b/js/src/views/Settings/Views/defaults.js index 5de5dc5f57..ef2bc910d2 100644 --- a/js/src/views/Settings/Views/defaults.js +++ b/js/src/views/Settings/Views/defaults.js @@ -23,7 +23,24 @@ import CommunicationContacts from 'material-ui/svg-icons/communication/contacts' import ImageGridOn from 'material-ui/svg-icons/image/grid-on'; import NavigationApps from 'material-ui/svg-icons/navigation/apps'; +import imagesEthcoreBlock from '~/../assets/images/parity-logo-white-no-text.svg'; + +import styles from './views.css'; + const defaultViews = { + home: { + active: true, + fixed: true, + icon: ( + + ), + route: '/home', + value: 'home' + }, + accounts: { active: true, fixed: true, diff --git a/js/src/views/Settings/Views/views.css b/js/src/views/Settings/Views/views.css index a04d04783c..de4079608c 100644 --- a/js/src/views/Settings/Views/views.css +++ b/js/src/views/Settings/Views/views.css @@ -43,6 +43,12 @@ color: white !important; } +.logoIcon { + height: 24px; + margin-bottom: 5px; + opacity: 0.5; +} + .info { color: #aaa; padding-left: 4.75em; diff --git a/js/src/views/Web/store.js b/js/src/views/Web/store.js index 542b47a7bb..e62effb894 100644 --- a/js/src/views/Web/store.js +++ b/js/src/views/Web/store.js @@ -82,8 +82,31 @@ export default class Store { this.setNextUrl(this.currentUrl); } - @action setHistory = (history) => { - this.history = history; + @action setHistory = (urls) => { + this.history = Object + .keys(urls) + .filter((url) => url && !url.startsWith(this._api.dappsUrl) && url.indexOf('127.0.0.1') === -1) + .sort((urlA, urlB) => { + const timeA = urls[urlA].getTime(); + const timeB = urls[urlB].getTime(); + + if (timeA > timeB) { + return -1; + } else if (timeA < timeB) { + return 1; + } + + return 0; + }) + .map((url) => { + const hostname = url.replace(/^http[s]?:\/\//, '').split('/')[0]; + + return { + hostname, + timestamp: urls[url], + url + }; + }); } @action setLoading = (isLoading) => { diff --git a/js/src/views/Web/store.spec.js b/js/src/views/Web/store.spec.js index 8a8dd268c9..58b2f1b3c6 100644 --- a/js/src/views/Web/store.spec.js +++ b/js/src/views/Web/store.spec.js @@ -18,7 +18,13 @@ import sinon from 'sinon'; import Store from './store'; -const TEST_HISTORY = ['somethingA', 'somethingB']; +const TEST_HISTORY_URLA = 'http://testingA'; +const TEST_HISTORY_URLB = 'http://testingB'; +const TEST_HISTORY = { + '': new Date(678), + [TEST_HISTORY_URLA]: new Date(123), + [TEST_HISTORY_URLB]: new Date(456) +}; const TEST_TOKEN = 'testing-123'; const TEST_URL1 = 'http://some.test.domain.com'; const TEST_URL2 = 'http://something.different.com'; @@ -89,9 +95,28 @@ describe('views/Web/Store', () => { }); describe('setHistory', () => { - it('sets the history', () => { + let history; + + beforeEach(() => { store.setHistory(TEST_HISTORY); - expect(store.history.peek()).to.deep.equal(TEST_HISTORY); + history = store.history.peek(); + }); + + it('sets the history', () => { + expect(history.length).to.equal(2); + }); + + it('adds hostname to entries', () => { + expect(history[1].hostname).to.be.ok; + }); + + it('removes hostname http prefixes', () => { + expect(history[1].hostname.indexOf('http')).to.equal(-1); + }); + + it('sorts the entries according to recently accessed', () => { + expect(history[0].url).to.equal(TEST_HISTORY_URLB); + expect(history[1].url).to.equal(TEST_HISTORY_URLA); }); }); @@ -195,7 +220,7 @@ describe('views/Web/Store', () => { }); it('sets the history as retrieved', () => { - expect(store.history.peek()).to.deep.equal(TEST_HISTORY); + expect(store.history.peek().length).not.to.equal(0); }); }); }); diff --git a/js/src/views/historyStore.js b/js/src/views/historyStore.js index e28829b540..fd18fd931e 100644 --- a/js/src/views/historyStore.js +++ b/js/src/views/historyStore.js @@ -30,10 +30,11 @@ export default class Store { this.load(); } - @action add = (entry) => { + @action add = (entry, type) => { this.history = [{ + entry, timestamp: Date.now(), - entry + type }].concat(this.history.filter((h) => h.entry !== entry)).slice(0, MAX_ENTRIES); this.save(); } diff --git a/js/src/views/index.js b/js/src/views/index.js index 69b5d25f95..4701a73560 100644 --- a/js/src/views/index.js +++ b/js/src/views/index.js @@ -24,6 +24,7 @@ export Contracts from './Contracts'; export Dapp from './Dapp'; export Dapps from './Dapps'; export HistoryStore from './historyStore'; +export Home from './Home'; export ParityBar from './ParityBar'; export Settings, { SettingsBackground, SettingsParity, SettingsProxy, SettingsViews } from './Settings'; export Signer from './Signer'; -- GitLab From dd0ef6b5ecf91c9507a91ba4ef4b30f6cf56dd30 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Tue, 14 Feb 2017 07:40:43 +0000 Subject: [PATCH 015/246] [ci skip] js-precompiled 20170214-073623 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba04e2184c..87bfe50256 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1607,7 +1607,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#6f37aa3a80d2a6250302cd12eaae05632810aa23" +source = "git+https://github.com/ethcore/js-precompiled.git#5f9a06d9fb417fcf6fd8ec074919f51d772130d4" 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 efa3eac9df..eef9eaed06 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.81", + "version": "0.3.82", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", -- GitLab From bccd0991f67999128ba95538e4677a251b58b5f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 14 Feb 2017 11:03:23 +0100 Subject: [PATCH 016/246] Fixing namespace of couple methods in console. (#4538) --- js/src/dapps/static/console.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/js/src/dapps/static/console.js b/js/src/dapps/static/console.js index d228a91670..b647860e73 100755 --- a/js/src/dapps/static/console.js +++ b/js/src/dapps/static/console.js @@ -394,7 +394,7 @@ web3._extend({ methods: [ new web3._extend.Method({ name: 'setMode', - call: 'ethcore_setMode', + call: 'parity_setMode', params: 1, }) ] @@ -405,7 +405,7 @@ web3._extend({ methods: [ new web3._extend.Method({ name: 'mode', - call: 'ethcore_mode', + call: 'parity_mode', params: 0, }) ] @@ -428,7 +428,7 @@ web3._extend({ methods: [ new web3._extend.Method({ name: 'gasPriceStatistics', - call: 'ethcore_gasPriceStatistics', + call: 'parity_gasPriceStatistics', params: 0, outputFormatter: function(a) { return a.map(web3.toBigNumber); } }) @@ -440,7 +440,7 @@ web3._extend({ methods: [ new web3._extend.Method({ name: 'registryAddress', - call: 'ethcore_registryAddress', + call: 'parity_registryAddress', params: 0 }) ] @@ -514,7 +514,7 @@ web3._extend({ methods: [ new web3._extend.Method({ name: 'encryptMessage', - call: 'ethcore_encryptMessage', + call: 'parity_encryptMessage', params: 2 }) ] @@ -536,7 +536,7 @@ web3._extend({ methods: [ new web3._extend.Method({ name: 'listAccounts', - call: 'ethcore_listAccounts', + call: 'parity_listAccounts', params: 0 }) ] -- GitLab From fefd53d4f4590d55dd75d37be2a890ff663cef55 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Tue, 14 Feb 2017 10:15:36 +0000 Subject: [PATCH 017/246] [ci skip] js-precompiled 20170214-101112 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 87bfe50256..92a1f3a83e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1607,7 +1607,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#5f9a06d9fb417fcf6fd8ec074919f51d772130d4" +source = "git+https://github.com/ethcore/js-precompiled.git#966b7ab4cf0c02450f14948b2a26cd326c395dc0" 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 eef9eaed06..7d21ef6d51 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.82", + "version": "0.3.83", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", -- GitLab From e911fc2db9fe2cc721a537ca9ec1dfb95a782917 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 14 Feb 2017 12:05:24 +0100 Subject: [PATCH 018/246] address grumbles --- ethcore/light/src/transaction_queue.rs | 27 ++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/ethcore/light/src/transaction_queue.rs b/ethcore/light/src/transaction_queue.rs index b4f6f9ede1..9ebf3827c7 100644 --- a/ethcore/light/src/transaction_queue.rs +++ b/ethcore/light/src/transaction_queue.rs @@ -111,6 +111,8 @@ impl TransactionQueue { let hash = tx.hash(); let nonce = tx.nonce; + if self.by_hash.contains_key(&hash) { return Err(TransactionError::AlreadyImported) } + let res = match self.by_account.entry(sender) { Entry::Vacant(entry) => { entry.insert(AccountTransactions { @@ -138,7 +140,8 @@ impl TransactionQueue { trace!(target: "txqueue", "Replacing existing transaction from {} with nonce {}", sender, nonce); - acct_txs.current[idx] = tx.clone(); + let old = ::std::mem::replace(&mut acct_txs.current[idx], tx.clone()); + self.by_hash.remove(&old.hash()); TransactionImportResult::Current } @@ -149,7 +152,7 @@ impl TransactionQueue { // current is sorted with one tx per nonce, // so if a tx with given nonce wasn't found that means it is either // earlier in nonce than all other "current" transactions or later. - debug_assert!(idx == 0 || idx == cur_len); + assert!(idx == 0 || idx == cur_len); if idx == 0 && acct_txs.current.first().map_or(false, |f| f.nonce != incr_nonce) { let old_cur = ::std::mem::replace(&mut acct_txs.current, vec![tx.clone()]); @@ -418,4 +421,24 @@ mod tests { assert!(txq.import(tx_b.fake_sign(sender).into()).is_err()) } + + #[test] + fn replace_is_removed() { + let sender = Address::default(); + let mut txq = TransactionQueue::default(); + + let tx_b: PendingTransaction = Transaction::default().fake_sign(sender).into(); + let tx_a: PendingTransaction = { + let mut tx_a = Transaction::default(); + tx_a.gas_price = tx_b.gas_price + 1.into(); + tx_a.fake_sign(sender).into() + }; + + let hash = tx_a.hash(); + + txq.import(tx_a).unwrap(); + txq.import(tx_b).unwrap(); + + assert!(txq.transaction(&hash).is_none()); + } } -- GitLab From 63ad8cb08688b6da19d484bc7d8198e2e4541279 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 14 Feb 2017 12:12:26 +0100 Subject: [PATCH 019/246] store pending transactions only once --- ethcore/light/src/transaction_queue.rs | 58 ++++++++++++++++++-------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/ethcore/light/src/transaction_queue.rs b/ethcore/light/src/transaction_queue.rs index 9ebf3827c7..088e724091 100644 --- a/ethcore/light/src/transaction_queue.rs +++ b/ethcore/light/src/transaction_queue.rs @@ -63,13 +63,30 @@ impl CurrentNonce { } } +#[derive(Debug, Clone, PartialEq, Eq)] +struct TransactionInfo { + hash: H256, + nonce: U256, + condition: Option, +} + +impl<'a> From<&'a PendingTransaction> for TransactionInfo { + fn from(tx: &'a PendingTransaction) -> Self { + TransactionInfo { + hash: tx.hash(), + nonce: tx.nonce.clone(), + condition: tx.condition.clone(), + } + } +} + // transactions associated with a specific account. #[derive(Debug, Clone, PartialEq, Eq)] struct AccountTransactions { // believed current nonce (gotten from initial given TX or `cull` calls). cur_nonce: CurrentNonce, - current: Vec, // ordered "current" transactions (cur_nonce onwards) - future: BTreeMap, // "future" transactions. + current: Vec, // ordered "current" transactions (cur_nonce onwards) + future: BTreeMap, // "future" transactions. } impl AccountTransactions { @@ -110,6 +127,7 @@ impl TransactionQueue { let sender = tx.sender(); let hash = tx.hash(); let nonce = tx.nonce; + let tx_info = TransactionInfo::from(&tx); if self.by_hash.contains_key(&hash) { return Err(TransactionError::AlreadyImported) } @@ -117,7 +135,7 @@ impl TransactionQueue { Entry::Vacant(entry) => { entry.insert(AccountTransactions { cur_nonce: CurrentNonce::Assumed(nonce), - current: vec![tx.clone()], + current: vec![tx_info], future: BTreeMap::new(), }); @@ -140,8 +158,8 @@ impl TransactionQueue { trace!(target: "txqueue", "Replacing existing transaction from {} with nonce {}", sender, nonce); - let old = ::std::mem::replace(&mut acct_txs.current[idx], tx.clone()); - self.by_hash.remove(&old.hash()); + let old = ::std::mem::replace(&mut acct_txs.current[idx], tx_info); + self.by_hash.remove(&old.hash); TransactionImportResult::Current } @@ -155,7 +173,7 @@ impl TransactionQueue { assert!(idx == 0 || idx == cur_len); if idx == 0 && acct_txs.current.first().map_or(false, |f| f.nonce != incr_nonce) { - let old_cur = ::std::mem::replace(&mut acct_txs.current, vec![tx.clone()]); + let old_cur = ::std::mem::replace(&mut acct_txs.current, vec![tx_info]); trace!(target: "txqueue", "Moving {} transactions with nonce > {} to future", old_cur.len(), incr_nonce); @@ -169,14 +187,14 @@ impl TransactionQueue { } else if idx == cur_len && acct_txs.current.last().map_or(false, |f| f.nonce + 1.into() != nonce) { trace!(target: "txqueue", "Queued future transaction for {}, nonce={}", sender, nonce); let future_nonce = nonce; - acct_txs.future.insert(future_nonce, tx.clone()); + acct_txs.future.insert(future_nonce, tx_info); TransactionImportResult::Future } else { trace!(target: "txqueue", "Queued current transaction for {}, nonce={}", sender, nonce); // insert, then check if we've filled any gaps. - acct_txs.current.insert(idx, tx.clone()); + acct_txs.current.insert(idx, tx_info); acct_txs.adjust_future(); TransactionImportResult::Current @@ -206,13 +224,17 @@ impl TransactionQueue { /// `best_block_number` and `best_block_timestamp` are used to filter out conditionally /// propagated transactions. pub fn ready_transactions(&self, best_block_number: u64, best_block_timestamp: u64) -> Vec { - self.by_account.values().flat_map(|acct_txs| { - acct_txs.current.iter().take_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, - }).cloned() - }).collect() + self.by_account.values() + .flat_map(|acct_txs| { + acct_txs.current.iter().take_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, + }).map(|info| info.hash) + }) + .filter_map(|hash| self.by_hash.get(&hash)) + .cloned() + .collect() } /// Addresses for which we store transactions. @@ -234,7 +256,7 @@ impl TransactionQueue { for old in old_future { let hash = acct_txs.future.remove(&old) .expect("key extracted from keys iterator; known to exist; qed") - .hash(); + .hash; removed_hashes.push(hash); } @@ -242,9 +264,9 @@ impl TransactionQueue { let valid_pos = acct_txs.current.iter().position(|tx| tx.nonce >= cur_nonce); match valid_pos { None => - removed_hashes.extend(acct_txs.current.drain(..).map(|tx| tx.hash())), + removed_hashes.extend(acct_txs.current.drain(..).map(|tx| tx.hash)), Some(valid) => - removed_hashes.extend(acct_txs.current.drain(..valid).map(|tx| tx.hash())), + removed_hashes.extend(acct_txs.current.drain(..valid).map(|tx| tx.hash)), } // now try and move stuff out of future into current. -- GitLab From 1d9db578ff568d56247057c4ec280c6ada32468e Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 14 Feb 2017 12:14:02 +0100 Subject: [PATCH 020/246] ready transactions order documentation --- ethcore/light/src/transaction_queue.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ethcore/light/src/transaction_queue.rs b/ethcore/light/src/transaction_queue.rs index 088e724091..50d6a918e4 100644 --- a/ethcore/light/src/transaction_queue.rs +++ b/ethcore/light/src/transaction_queue.rs @@ -223,6 +223,8 @@ impl TransactionQueue { /// Get all transactions 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 ready_transactions(&self, best_block_number: u64, best_block_timestamp: u64) -> Vec { self.by_account.values() .flat_map(|acct_txs| { -- GitLab From e8597e2e9123f77ff204afdb99bcf25f22b3c91f Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Tue, 14 Feb 2017 13:08:38 +0100 Subject: [PATCH 021/246] Fix contract queries bug (#4534) * Fix contract queries and multiple Address Selector issues * Linting * Use standard new Error --- js/src/api/contract/contract.js | 33 +++++++++-- .../ui/Form/AddressSelect/addressSelect.css | 4 ++ js/src/ui/Form/AddressSelect/addressSelect.js | 21 ++++--- .../Form/AddressSelect/addressSelectStore.js | 4 ++ js/src/ui/Form/TypedInput/typedInput.js | 2 +- js/src/views/Contract/Queries/inputQuery.js | 58 +++++++++++++------ js/src/views/Contract/Queries/queries.js | 25 ++++++-- 7 files changed, 109 insertions(+), 38 deletions(-) diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index 3869bddcad..570c362874 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -241,13 +241,32 @@ export default class Contract { _bindFunction = (func) => { func.contract = this; - func.call = (options, values = []) => { - const callParams = this._encodeOptions(func, this._addOptionsTo(options), values); + func.call = (_options = {}, values = []) => { + const rawTokens = !!_options.rawTokens; + const options = { + ..._options + }; + + delete options.rawTokens; + + let callParams; + + try { + callParams = this._encodeOptions(func, this._addOptionsTo(options), values); + } catch (error) { + return Promise.reject(error); + } return this._api.eth .call(callParams) .then((encoded) => func.decodeOutput(encoded)) - .then((tokens) => tokens.map((token) => token.value)) + .then((tokens) => { + if (rawTokens) { + return tokens; + } + + return tokens.map((token) => token.value); + }) .then((returns) => returns.length === 1 ? returns[0] : returns) .catch((error) => { console.warn(`${func.name}.call`, values, error); @@ -257,7 +276,13 @@ export default class Contract { if (!func.constant) { func.postTransaction = (options, values = []) => { - const _options = this._encodeOptions(func, this._addOptionsTo(options), values); + let _options; + + try { + _options = this._encodeOptions(func, this._addOptionsTo(options), values); + } catch (error) { + return Promise.reject(error); + } return this._api.parity .postTransaction(_options) diff --git a/js/src/ui/Form/AddressSelect/addressSelect.css b/js/src/ui/Form/AddressSelect/addressSelect.css index 43d6a7075f..839c025b54 100644 --- a/js/src/ui/Form/AddressSelect/addressSelect.css +++ b/js/src/ui/Form/AddressSelect/addressSelect.css @@ -107,6 +107,10 @@ } } +.container { + margin-bottom: 1em; +} + .categories { display: flex; flex: 1; diff --git a/js/src/ui/Form/AddressSelect/addressSelect.js b/js/src/ui/Form/AddressSelect/addressSelect.js index d3c7462c3c..2f47484b2c 100644 --- a/js/src/ui/Form/AddressSelect/addressSelect.js +++ b/js/src/ui/Form/AddressSelect/addressSelect.js @@ -206,12 +206,11 @@ class AddressSelect extends Component { style={ BOTTOM_BORDER_STYLE } />
- - { this.renderCurrentInput() } - { this.renderRegistryValues() } } > + { this.renderCurrentInput() } + { this.renderRegistryValues() } { this.renderAccounts() } ); @@ -245,8 +244,8 @@ class AddressSelect extends Component { } return ( -
- { this.renderAccountCard({ address }) } +
+ { this.renderAccountCard({ address, index: 'currentInput_0' }) }
); } @@ -266,7 +265,7 @@ class AddressSelect extends Component { }); return ( -
+
{ accounts }
); @@ -361,7 +360,7 @@ class AddressSelect extends Component { validateCustomInput = () => { const { allowInput } = this.props; - const { inputValue } = this.store; + const { inputValue } = this.state; const { values } = this.store; // If input is HEX and allowInput === true, send it @@ -587,7 +586,13 @@ class AddressSelect extends Component { this.handleDOMAction('inputAddress', 'focus'); } - this.setState({ expanded: false }); + this.store.resetRegistryValues(); + + this.setState({ + expanded: false, + focusedItem: null, + inputValue: '' + }); } handleInputBlur = () => { diff --git a/js/src/ui/Form/AddressSelect/addressSelectStore.js b/js/src/ui/Form/AddressSelect/addressSelectStore.js index 1e2c6e9646..7fc1db4802 100644 --- a/js/src/ui/Form/AddressSelect/addressSelectStore.js +++ b/js/src/ui/Form/AddressSelect/addressSelectStore.js @@ -204,6 +204,10 @@ export default class AddressSelectStore { this.handleChange(); } + @action resetRegistryValues = () => { + this.registryValues = []; + } + @action handleChange = (value = '') => { let index = 0; diff --git a/js/src/ui/Form/TypedInput/typedInput.js b/js/src/ui/Form/TypedInput/typedInput.js index 79365faeb9..16b3b0ca1e 100644 --- a/js/src/ui/Form/TypedInput/typedInput.js +++ b/js/src/ui/Form/TypedInput/typedInput.js @@ -125,7 +125,7 @@ export default class TypedInput extends Component { return (
- { fixedLength ? null : this.renderLength() } + { fixedLength || readOnly ? null : this.renderLength() } { inputs }
); diff --git a/js/src/views/Contract/Queries/inputQuery.js b/js/src/views/Contract/Queries/inputQuery.js index 43bb467bd8..1418aa6b72 100644 --- a/js/src/views/Contract/Queries/inputQuery.js +++ b/js/src/views/Contract/Queries/inputQuery.js @@ -14,17 +14,19 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import BigNumber from 'bignumber.js'; import React, { Component, PropTypes } from 'react'; import LinearProgress from 'material-ui/LinearProgress'; import { Card, CardActions, CardTitle, CardText } from 'material-ui/Card'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import { newError } from '~/redux/actions'; import { Button, TypedInput } from '~/ui'; import { arrayOrObjectProptype } from '~/util/proptypes'; import styles from './queries.css'; -export default class InputQuery extends Component { +class InputQuery extends Component { static contextTypes = { api: PropTypes.object }; @@ -35,6 +37,7 @@ export default class InputQuery extends Component { inputs: arrayOrObjectProptype().isRequired, outputs: arrayOrObjectProptype().isRequired, name: PropTypes.string.isRequired, + newError: PropTypes.func.isRequired, signature: PropTypes.string.isRequired, className: PropTypes.string }; @@ -65,7 +68,7 @@ export default class InputQuery extends Component { const { isValid } = this.state; const inputsFields = inputs - .map(input => this.renderInput(input)); + .map((input, index) => this.renderInput(input, index)); return (
@@ -119,7 +122,7 @@ export default class InputQuery extends Component { ); return ( -
+
{ out.name }
@@ -129,7 +132,7 @@ export default class InputQuery extends Component { }); } - renderInput (input) { + renderInput (input, index) { const { values } = this.state; const { name, type } = input; const label = `${name ? `${name}: ` : ''}${type}`; @@ -140,41 +143,42 @@ export default class InputQuery extends Component { this.setState({ values: { ...values, - [ name ]: value + [ index ]: value } }); }; return ( -
+
); } - renderValue (value) { + renderValue (token) { + const { api } = this.context; + const { type, value } = token; + if (value === null || value === undefined) { return 'no data'; } - const { api } = this.context; - - if (api.util.isInstanceOf(value, BigNumber)) { - return value.toFormat(0); + if (type === 'array' || type === 'fixedArray') { + return value.map((tok) => this.renderValue(tok)); } - if (api.util.isArray(value)) { + if (Array.isArray(value)) { return api.util.bytesToHex(value); } - return value.toString(); + return value; } onClick = () => { @@ -186,11 +190,11 @@ export default class InputQuery extends Component { results: [] }); - const inputValues = inputs.map(input => values[input.name]); + const inputValues = inputs.map((input, index) => values[index] || ''); contract .instance[signature] - .call({}, inputValues) + .call({ rawTokens: true }, inputValues) .then(results => { if (outputs.length === 1) { results = [ results ]; @@ -201,8 +205,24 @@ export default class InputQuery extends Component { results }); }) - .catch(e => { - console.error(`sending ${name} with params`, inputValues, e); + .catch((error) => { + console.error(`sending ${name} with params`, inputValues, error.message); + + this.props.newError(error); + this.setState({ + isLoading: false + }); }); }; } + +function mapDispatchToProps (dispatch) { + return bindActionCreators({ + newError + }, dispatch); +} + +export default connect( + null, + mapDispatchToProps +)(InputQuery); diff --git a/js/src/views/Contract/Queries/queries.js b/js/src/views/Contract/Queries/queries.js index 71b4b2443d..dd84109fbc 100644 --- a/js/src/views/Contract/Queries/queries.js +++ b/js/src/views/Contract/Queries/queries.js @@ -61,12 +61,25 @@ export default class Queries extends Component { return (
-
- { noInputQueries } -
-
- { withInputQueries } -
+ { + noInputQueries.length > 0 + ? ( +
+ { noInputQueries } +
+ ) + : null + } + + { + withInputQueries.length > 0 + ? ( +
+ { withInputQueries } +
+ ) + : null + }
); -- GitLab From 7d12e383b2be6a2863fec48e946b5eb65d2741cb Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Tue, 14 Feb 2017 16:09:19 +0400 Subject: [PATCH 022/246] windows build switch to RUST stable --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 618764d5ea..31f15c3061 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -422,7 +422,7 @@ windows: - set LIB=C:\vs2015\VC\lib;C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10240.0\ucrt\x64 - set RUST_BACKTRACE=1 - set RUSTFLAGS=%RUSTFLAGS% - - rustup default 1.14.0-x86_64-pc-windows-msvc + - rustup default stable-x86_64-pc-windows-msvc - cargo build --features final --release #%CARGOFLAGS% - signtool sign /f %keyfile% /p %certpass% target\release\parity.exe - target\release\parity.exe tools hash target\release\parity.exe > parity.sha3 -- GitLab From 71c0cc867a39bd4bea7916f6731f326d2c3a9490 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Tue, 14 Feb 2017 13:16:39 +0100 Subject: [PATCH 023/246] Extract i18n string into i18n/_defaults (base of translations) (#4514) * Build script to pull i18n into i18n/_default * Fix header * Current strings as extracted * details_windows without prefix * clean before build * Alwasy extract babel strings * clean & run build before extraction * Update settings messages * Put back template string (PR comment) * PR comment cleanups & logging * Less complicated string type check (PR comment) * Remove node cache to extract all keys (Thanks @ngotchac) * Merge in defaults from i18n/en (Comment by @h3ll0fr13nd) * Unique index keys only * Update with latest master strings * _.defaultsDeep (Thanks to @dehurst) * Use to-source for string formatting * Sync with toSource output on latest master * Updated to use/output sorted objects --- js/.babelrc | 4 +- js/package.json | 4 +- js/scripts/build-i18n.js | 154 ++++++++++++++++++++++ js/src/i18n/_default/account.js | 35 +++++ js/src/i18n/_default/accounts.js | 21 +++ js/src/i18n/_default/addAddress.js | 37 ++++++ js/src/i18n/_default/addContract.js | 60 +++++++++ js/src/i18n/_default/addressSelect.js | 26 ++++ js/src/i18n/_default/application.js | 27 ++++ js/src/i18n/_default/connection.js | 26 ++++ js/src/i18n/_default/contract.js | 19 +++ js/src/i18n/_default/createAccount.js | 163 ++++++++++++++++++++++++ js/src/i18n/_default/createWallet.js | 105 +++++++++++++++ js/src/i18n/_default/dapp.js | 20 +++ js/src/i18n/_default/dapps.js | 46 +++++++ js/src/i18n/_default/deleteAccount.js | 24 ++++ js/src/i18n/_default/deployContract.js | 81 ++++++++++++ js/src/i18n/_default/details_windows.js | 17 +++ js/src/i18n/_default/editMeta.js | 34 +++++ js/src/i18n/_default/executeContract.js | 58 +++++++++ js/src/i18n/_default/extension.js | 20 +++ js/src/i18n/_default/firstRun.js | 32 +++++ js/src/i18n/_default/home.js | 38 ++++++ js/src/i18n/_default/index.js | 46 +++++++ js/src/i18n/_default/loadContract.js | 43 +++++++ js/src/i18n/_default/parityBar.js | 29 +++++ js/src/i18n/_default/passwordChange.js | 53 ++++++++ js/src/i18n/_default/settings.js | 89 +++++++++++++ js/src/i18n/_default/shapeshift.js | 66 ++++++++++ js/src/i18n/_default/transfer.js | 27 ++++ js/src/i18n/_default/txEditor.js | 39 ++++++ js/src/i18n/_default/ui.js | 77 +++++++++++ js/src/i18n/_default/upgradeParity.js | 44 +++++++ js/src/i18n/_default/walletSettings.js | 58 +++++++++ js/src/i18n/_default/web.js | 19 +++ js/src/views/Settings/Proxy/proxy.js | 2 +- 36 files changed, 1639 insertions(+), 4 deletions(-) create mode 100644 js/scripts/build-i18n.js create mode 100644 js/src/i18n/_default/account.js create mode 100644 js/src/i18n/_default/accounts.js create mode 100644 js/src/i18n/_default/addAddress.js create mode 100644 js/src/i18n/_default/addContract.js create mode 100644 js/src/i18n/_default/addressSelect.js create mode 100644 js/src/i18n/_default/application.js create mode 100644 js/src/i18n/_default/connection.js create mode 100644 js/src/i18n/_default/contract.js create mode 100644 js/src/i18n/_default/createAccount.js create mode 100644 js/src/i18n/_default/createWallet.js create mode 100644 js/src/i18n/_default/dapp.js create mode 100644 js/src/i18n/_default/dapps.js create mode 100644 js/src/i18n/_default/deleteAccount.js create mode 100644 js/src/i18n/_default/deployContract.js create mode 100644 js/src/i18n/_default/details_windows.js create mode 100644 js/src/i18n/_default/editMeta.js create mode 100644 js/src/i18n/_default/executeContract.js create mode 100644 js/src/i18n/_default/extension.js create mode 100644 js/src/i18n/_default/firstRun.js create mode 100644 js/src/i18n/_default/home.js create mode 100644 js/src/i18n/_default/index.js create mode 100644 js/src/i18n/_default/loadContract.js create mode 100644 js/src/i18n/_default/parityBar.js create mode 100644 js/src/i18n/_default/passwordChange.js create mode 100644 js/src/i18n/_default/settings.js create mode 100644 js/src/i18n/_default/shapeshift.js create mode 100644 js/src/i18n/_default/transfer.js create mode 100644 js/src/i18n/_default/txEditor.js create mode 100644 js/src/i18n/_default/ui.js create mode 100644 js/src/i18n/_default/upgradeParity.js create mode 100644 js/src/i18n/_default/walletSettings.js create mode 100644 js/src/i18n/_default/web.js diff --git a/js/.babelrc b/js/.babelrc index 6fea8d2861..5087af80dc 100644 --- a/js/.babelrc +++ b/js/.babelrc @@ -19,8 +19,8 @@ }, "development": { "plugins": [ - "react-hot-loader/babel", - ["react-intl", { "messagesDir": "./.build/i18n/" }] + [ "react-intl", { "messagesDir": "./.build/i18n/" } ], + "react-hot-loader/babel" ] }, "test": { diff --git a/js/package.json b/js/package.json index 7d21ef6d51..842ea412f2 100644 --- a/js/package.json +++ b/js/package.json @@ -32,6 +32,7 @@ "build:markdown": "babel-node ./scripts/build-rpc-markdown.js", "build:json": "babel-node ./scripts/build-rpc-json.js", "build:embed": "EMBED=1 node webpack/embed", + "build:i18n": "npm run clean && npm run build && babel-node ./scripts/build-i18n.js", "ci:build": "npm run ci:build:lib && npm run ci:build:dll && npm run ci:build:app && npm run ci:build:embed", "ci:build:app": "NODE_ENV=production webpack --config webpack/app", "ci:build:lib": "NODE_ENV=production webpack --config webpack/libraries", @@ -41,7 +42,7 @@ "ci:build:embed": "NODE_ENV=production EMBED=1 node webpack/embed", "start": "npm install && npm run build:lib && npm run build:dll && npm run start:app", "start:app": "node webpack/dev.server", - "clean": "rm -rf ./.build ./.coverage ./.happypack ./.npmjs ./build", + "clean": "rm -rf ./.build ./.coverage ./.happypack ./.npmjs ./build ./node_modules/.cache", "coveralls": "npm run testCoverage && coveralls < coverage/lcov.info", "lint": "npm run lint:css && npm run lint:js", "lint:cached": "npm run lint:css && npm run lint:js:cached", @@ -134,6 +135,7 @@ "style-loader": "0.13.1", "stylelint": "7.7.0", "stylelint-config-standard": "15.0.1", + "to-source": "2.0.3", "url-loader": "0.5.7", "webpack": "2.2.1", "webpack-dev-middleware": "1.9.0", diff --git a/js/scripts/build-i18n.js b/js/scripts/build-i18n.js new file mode 100644 index 0000000000..1acf2c852f --- /dev/null +++ b/js/scripts/build-i18n.js @@ -0,0 +1,154 @@ +// 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 . + +const fs = require('fs'); +const _ = require('lodash'); +const path = require('path'); +const toSource = require('to-source'); + +const FILE_HEADER = `// 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 .\n\n`; +const SECTION_HEADER = 'export default '; +const SECTION_FOOTER = ';\n'; +const INDENT = ' '; +const DESTPATH = path.join(__dirname, '../src/i18n/_default'); +const ENPATH = path.join(__dirname, '../src/i18n/en'); +const SRCPATH = path.join(__dirname, '../.build/i18n/i18n/en.json'); + +// main entry point +(function main () { + const { sections, sectionNames } = createSectionMap(); + + sectionNames.forEach((name) => outputSection(name, sections[name])); + outputIndex(sectionNames); +})(); + +// sort an object based on its keys +function sortObject (object) { + return Object + .keys(object) + .sort() + .reduce((sorted, key) => { + if (typeof object[key] === 'object') { + sorted[key] = sortObject(object[key]); + } else { + sorted[key] = object[key]; + } + + return sorted; + }, {}); +} + +// create an object map of the actual inputs +function createSectionMap () { + console.log(`Reading strings from ${SRCPATH}`); + + const i18nstrings = require(SRCPATH); + const sections = sortObject( + Object + .keys(i18nstrings) + .reduce((sections, fullKey) => { + const defaultMessage = i18nstrings[fullKey].defaultMessage; + const keys = fullKey.split('.'); + let outputs = sections; + + keys.forEach((key, index) => { + if (index === keys.length - 1) { + outputs[key] = defaultMessage; + } else { + if (!outputs[key]) { + outputs[key] = {}; + } + + outputs = outputs[key]; + } + }); + + return sections; + }, {}) + ); + const sectionNames = Object.keys(sections); + + console.log(`Found ${sectionNames.length} sections`); + + return { + sections, + sectionNames + }; +} + +// load the available deafults (non-exported strings) for a section +function readDefaults (sectionName) { + let defaults = {}; + + try { + defaults = require(path.join(ENPATH, `${sectionName}.js`)).default; + } catch (error) { + defaults = {}; + } + + return defaults; +} + +// create the index.js file +function outputIndex (sectionNames) { + console.log(`Writing index.js to ${DESTPATH}`); + + const defaults = readDefaults('index'); + const dest = path.join(DESTPATH, 'index.js'); + const exports = _.uniq(Object.keys(defaults).concat(sectionNames)) + .sort() + .map((name) => `export ${name} from './${name}';`) + .join('\n'); + + fs.writeFileSync(dest, `${FILE_HEADER}${exports}\n`, 'utf8'); +} + +// export a section as a flatenned JS export string +function createJSSection (section) { + const source = toSource(section, { + enclose: true, + quoteChar: '`', + tabChar: INDENT, + tabDepth: 0 + }); + + return `${SECTION_HEADER}${source}${SECTION_FOOTER}`; +} + +// create the individual section files +function outputSection (sectionName, section) { + console.log(`Writing ${sectionName}.js to ${DESTPATH}`); + + const defaults = readDefaults(sectionName); + const dest = path.join(DESTPATH, `${sectionName}.js`); + const sectionText = createJSSection(_.defaultsDeep(section, defaults)); + + fs.writeFileSync(dest, `${FILE_HEADER}${sectionText}`, 'utf8'); +} diff --git a/js/src/i18n/_default/account.js b/js/src/i18n/_default/account.js new file mode 100644 index 0000000000..ce5d277ddb --- /dev/null +++ b/js/src/i18n/_default/account.js @@ -0,0 +1,35 @@ +// 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 { + button: { + delete: `delete account`, + edit: `edit`, + password: `password`, + shapeshift: `shapeshift`, + transfer: `transfer`, + verify: `verify` + }, + header: { + outgoingTransactions: `{count} outgoing transactions`, + uuid: `uuid: {uuid}` + }, + title: `Account Management`, + transactions: { + poweredBy: `Transaction list powered by {etherscan}`, + title: `transactions` + } +}; diff --git a/js/src/i18n/_default/accounts.js b/js/src/i18n/_default/accounts.js new file mode 100644 index 0000000000..5cafa1054c --- /dev/null +++ b/js/src/i18n/_default/accounts.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 { + summary: { + minedBlock: `Mined at block #{blockNumber}` + } +}; diff --git a/js/src/i18n/_default/addAddress.js b/js/src/i18n/_default/addAddress.js new file mode 100644 index 0000000000..38fd7e3612 --- /dev/null +++ b/js/src/i18n/_default/addAddress.js @@ -0,0 +1,37 @@ +// 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 { + button: { + add: `Save Address`, + close: `Cancel` + }, + input: { + address: { + hint: `the network address for the entry`, + label: `network address` + }, + description: { + hint: `an expanded description for the entry`, + label: `(optional) address description` + }, + name: { + hint: `a descriptive name for the entry`, + label: `address name` + } + }, + label: `add saved address` +}; diff --git a/js/src/i18n/_default/addContract.js b/js/src/i18n/_default/addContract.js new file mode 100644 index 0000000000..edbecf1183 --- /dev/null +++ b/js/src/i18n/_default/addContract.js @@ -0,0 +1,60 @@ +// 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 { + abi: { + hint: `the abi for the contract`, + label: `contract abi` + }, + abiType: { + custom: { + description: `Contract created from custom ABI`, + label: `Custom Contract` + }, + multisigWallet: { + description: `Ethereum Multisig contract {link}`, + label: `Multisig Wallet`, + link: `see contract code` + }, + token: { + description: `A standard {erc20} token`, + erc20: `ERC 20`, + label: `Token` + } + }, + address: { + hint: `the network address for the contract`, + label: `network address` + }, + button: { + add: `Add Contract`, + cancel: `Cancel`, + next: `Next`, + prev: `Back` + }, + description: { + hint: `an expanded description for the entry`, + label: `(optional) contract description` + }, + name: { + hint: `a descriptive name for the contract`, + label: `contract name` + }, + title: { + details: `enter contract details`, + type: `choose a contract type` + } +}; diff --git a/js/src/i18n/_default/addressSelect.js b/js/src/i18n/_default/addressSelect.js new file mode 100644 index 0000000000..108ac80f56 --- /dev/null +++ b/js/src/i18n/_default/addressSelect.js @@ -0,0 +1,26 @@ +// 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 { + fromEmail: `Verified using email {email}`, + fromRegistry: `{name} (from registry)`, + labels: { + accounts: `accounts`, + contacts: `contacts`, + contracts: `contracts` + }, + noAccount: `No account matches this query...` +}; diff --git a/js/src/i18n/_default/application.js b/js/src/i18n/_default/application.js new file mode 100644 index 0000000000..1edafff6df --- /dev/null +++ b/js/src/i18n/_default/application.js @@ -0,0 +1,27 @@ +// 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 { + status: { + consensus: { + capable: `Capable`, + capableUntil: `Capable until #{blockNumber}`, + incapableSince: `Incapable since #{blockNumber}`, + unknown: `Unknown capability` + }, + upgrade: `Upgrade` + } +}; diff --git a/js/src/i18n/_default/connection.js b/js/src/i18n/_default/connection.js new file mode 100644 index 0000000000..10f330618f --- /dev/null +++ b/js/src/i18n/_default/connection.js @@ -0,0 +1,26 @@ +// 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 { + connectingAPI: `Connecting to the Parity Secure API.`, + connectingNode: `Connecting to the Parity Node. If this informational message persists, please ensure that your Parity node is running and reachable on the network.`, + invalidToken: `invalid signer token`, + noConnection: `Unable to make a connection to the Parity Secure API. To update your secure token or to generate a new one, run {newToken} and supply the token below`, + token: { + hint: `a generated token from Parity`, + label: `secure token` + } +}; diff --git a/js/src/i18n/_default/contract.js b/js/src/i18n/_default/contract.js new file mode 100644 index 0000000000..e6a2a110bf --- /dev/null +++ b/js/src/i18n/_default/contract.js @@ -0,0 +1,19 @@ +// 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 { + minedBlock: `Mined at block #{blockNumber}` +}; diff --git a/js/src/i18n/_default/createAccount.js b/js/src/i18n/_default/createAccount.js new file mode 100644 index 0000000000..562f1ff0cc --- /dev/null +++ b/js/src/i18n/_default/createAccount.js @@ -0,0 +1,163 @@ +// 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 { + accountDetails: { + address: { + hint: `the network address for the account`, + label: `address` + }, + name: { + hint: `a descriptive name for the account`, + label: `account name` + }, + phrase: { + hint: `the account recovery phrase`, + label: `owner recovery phrase (keep private and secure, it allows full and unlimited access to the account)` + } + }, + accountDetailsGeth: { + imported: `You have imported {number} addresses from the Geth keystore:` + }, + button: { + back: `Back`, + cancel: `Cancel`, + close: `Close`, + create: `Create`, + import: `Import`, + next: `Next`, + print: `Print Phrase` + }, + creationType: { + fromGeth: { + label: `Import accounts from Geth keystore` + }, + fromJSON: { + label: `Import account from a backup JSON file` + }, + fromNew: { + label: `Create new account manually` + }, + fromPhrase: { + label: `Recover account from recovery phrase` + }, + fromPresale: { + label: `Import account from an Ethereum pre-sale wallet` + }, + fromRaw: { + 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`, + label: `password hint` + }, + name: { + hint: `a descriptive name for the account`, + label: `account name` + }, + password: { + hint: `a strong, unique password`, + label: `password` + }, + password2: { + hint: `verify your password`, + label: `password (repeat)` + } + }, + newGeth: { + noKeys: `There are currently no importable keys available from the Geth keystore, which are not already available on your Parity instance` + }, + newImport: { + file: { + hint: `the wallet file for import`, + label: `wallet file` + }, + hint: { + hint: `(optional) a hint to help with remembering the password`, + label: `password hint` + }, + name: { + hint: `a descriptive name for the account`, + label: `account name` + }, + password: { + hint: `the password to unlock the wallet`, + label: `password` + } + }, + rawKey: { + hint: { + hint: `(optional) a hint to help with remembering the password`, + label: `password hint` + }, + name: { + hint: `a descriptive name for the account`, + label: `account name` + }, + password: { + hint: `a strong, unique password`, + label: `password` + }, + password2: { + hint: `verify your password`, + label: `password (repeat)` + }, + private: { + hint: `the raw hex encoded private key`, + label: `private key` + } + }, + recoveryPhrase: { + hint: { + hint: `(optional) a hint to help with remembering the password`, + label: `password hint` + }, + name: { + hint: `a descriptive name for the account`, + label: `account name` + }, + password: { + hint: `a strong, unique password`, + label: `password` + }, + password2: { + hint: `verify your password`, + label: `password (repeat)` + }, + phrase: { + hint: `the account recovery phrase`, + label: `account recovery phrase` + }, + windowsKey: { + label: `Key was created with Parity <1.4.5 on Windows` + } + }, + title: { + accountInfo: `account information`, + createAccount: `create account`, + createType: `creation type`, + importWallet: `import wallet` + } +}; diff --git a/js/src/i18n/_default/createWallet.js b/js/src/i18n/_default/createWallet.js new file mode 100644 index 0000000000..9c7d8df3e5 --- /dev/null +++ b/js/src/i18n/_default/createWallet.js @@ -0,0 +1,105 @@ +// 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 { + button: { + add: `Add`, + cancel: `Cancel`, + close: `Close`, + create: `Create`, + done: `Done`, + next: `Next`, + sending: `Sending...` + }, + deployment: { + message: `The deployment is currently in progress` + }, + details: { + address: { + hint: `the wallet contract address`, + label: `wallet address` + }, + dayLimitMulti: { + hint: `amount of ETH spendable without confirmations`, + label: `wallet day limit` + }, + description: { + hint: `the local description for this wallet`, + label: `wallet description (optional)` + }, + descriptionMulti: { + hint: `the local description for this wallet`, + label: `wallet description (optional)` + }, + name: { + hint: `the local name for this wallet`, + label: `wallet name` + }, + nameMulti: { + hint: `the local name for this wallet`, + label: `wallet name` + }, + ownerMulti: { + hint: `the owner account for this contract`, + label: `from account (contract owner)` + }, + ownersMulti: { + label: `other wallet owners` + }, + ownersMultiReq: { + hint: `number of required owners to accept a transaction`, + label: `required owners` + } + }, + info: { + added: `added`, + copyAddress: `copy address to clipboard`, + created: `{name} has been {deployedOrAdded} at`, + dayLimit: `The daily limit is set to {dayLimit} ETH.`, + deployed: `deployed`, + numOwners: `{numOwners} owners are required to confirm a transaction.`, + owners: `The following are wallet owners` + }, + rejected: { + message: `The deployment has been rejected`, + state: `The wallet will not be created. You can safely close this window.`, + title: `rejected` + }, + states: { + completed: `The contract deployment has been completed`, + preparing: `Preparing transaction for network transmission`, + validatingCode: `Validating the deployed contract code`, + waitingConfirm: `Waiting for confirmation of the transaction in the Parity Secure Signer`, + waitingReceipt: `Waiting for the contract deployment transaction receipt` + }, + steps: { + deployment: `wallet deployment`, + details: `wallet details`, + info: `wallet informaton`, + type: `wallet type` + }, + type: { + multisig: { + description: `Create/Deploy a {link} Wallet`, + label: `Multi-Sig wallet`, + link: `standard multi-signature` + }, + watch: { + description: `Add an existing wallet to your accounts`, + label: `Watch a wallet` + } + } +}; diff --git a/js/src/i18n/_default/dapp.js b/js/src/i18n/_default/dapp.js new file mode 100644 index 0000000000..29dd2fd16b --- /dev/null +++ b/js/src/i18n/_default/dapp.js @@ -0,0 +1,20 @@ +// 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 { + loading: `Loading`, + unavailable: `The dapp cannot be reached` +}; diff --git a/js/src/i18n/_default/dapps.js b/js/src/i18n/_default/dapps.js new file mode 100644 index 0000000000..d13caa46d5 --- /dev/null +++ b/js/src/i18n/_default/dapps.js @@ -0,0 +1,46 @@ +// 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 { + add: { + builtin: { + desc: `Experimental applications developed by the Parity team to show off dapp capabilities, integration, experimental features and to control certain network-wide client behaviour.`, + label: `Applications bundled with Parity` + }, + label: `visible applications`, + local: { + desc: `All applications installed locally on the machine by the user for access by the Parity client.`, + label: `Applications locally available` + }, + network: { + desc: `These applications are not affiliated with Parity nor are they published by Parity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each application before interacting.`, + label: `Applications on the global network` + } + }, + button: { + edit: `edit`, + permissions: `permissions` + }, + external: { + accept: `I understand that these applications are not affiliated with Parity`, + warning: `Applications made available on the network by 3rd-party authors are not affiliated with Parity nor are they published by Parity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each before interacting.` + }, + label: `Decentralized Applications`, + permissions: { + description: `{activeIcon} account is available to application, {defaultIcon} account is the default account`, + label: `visible dapp accounts` + } +}; diff --git a/js/src/i18n/_default/deleteAccount.js b/js/src/i18n/_default/deleteAccount.js new file mode 100644 index 0000000000..4818a785ab --- /dev/null +++ b/js/src/i18n/_default/deleteAccount.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 { + password: { + hint: `provide the account password to confirm the account deletion`, + label: `account password` + }, + question: `Are you sure you want to permanently delete the following account?`, + title: `confirm removal` +}; diff --git a/js/src/i18n/_default/deployContract.js b/js/src/i18n/_default/deployContract.js new file mode 100644 index 0000000000..fe7c9a69e5 --- /dev/null +++ b/js/src/i18n/_default/deployContract.js @@ -0,0 +1,81 @@ +// 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 { + busy: { + title: `The deployment is currently in progress` + }, + button: { + cancel: `Cancel`, + close: `Close`, + create: `Create`, + done: `Done`, + next: `Next` + }, + completed: { + description: `Your contract has been deployed at` + }, + details: { + abi: { + hint: `the abi of the contract to deploy or solc combined-output`, + label: `abi / solc combined-output` + }, + address: { + hint: `the owner account for this contract`, + label: `from account (contract owner)` + }, + code: { + hint: `the compiled code of the contract to deploy`, + label: `code` + }, + contract: { + label: `select a contract` + }, + description: { + hint: `a description for the contract`, + label: `contract description (optional)` + }, + name: { + hint: `a name for the deployed contract`, + label: `contract name` + } + }, + owner: { + noneSelected: `a valid account as the contract owner needs to be selected` + }, + parameters: { + choose: `Choose the contract parameters` + }, + rejected: { + description: `You can safely close this window, the contract deployment will not occur.`, + title: `The deployment has been rejected` + }, + state: { + completed: `The contract deployment has been completed`, + preparing: `Preparing transaction for network transmission`, + validatingCode: `Validating the deployed contract code`, + waitReceipt: `Waiting for the contract deployment transaction receipt`, + waitSigner: `Waiting for confirmation of the transaction in the Parity Secure Signer` + }, + title: { + completed: `completed`, + deployment: `deployment`, + details: `contract details`, + failed: `deployment failed`, + parameters: `contract parameters`, + rejected: `rejected` + } +}; diff --git a/js/src/i18n/_default/details_windows.js b/js/src/i18n/_default/details_windows.js new file mode 100644 index 0000000000..fcc5700665 --- /dev/null +++ b/js/src/i18n/_default/details_windows.js @@ -0,0 +1,17 @@ +// 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 `Windows`; diff --git a/js/src/i18n/_default/editMeta.js b/js/src/i18n/_default/editMeta.js new file mode 100644 index 0000000000..b5b5213417 --- /dev/null +++ b/js/src/i18n/_default/editMeta.js @@ -0,0 +1,34 @@ +// 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 { + description: { + hint: `description for this address`, + label: `address description` + }, + name: { + label: `name` + }, + passwordHint: { + hint: `a hint to allow password recovery`, + label: `(optional) password hint` + }, + tags: { + hint: `press to add a tag`, + label: `(optional) tags` + }, + title: `edit metadata` +}; diff --git a/js/src/i18n/_default/executeContract.js b/js/src/i18n/_default/executeContract.js new file mode 100644 index 0000000000..011264d3fe --- /dev/null +++ b/js/src/i18n/_default/executeContract.js @@ -0,0 +1,58 @@ +// 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 { + busy: { + posted: `Your transaction has been posted to the network`, + title: `The function execution is in progress`, + waitAuth: `Waiting for authorization in the Parity Signer` + }, + button: { + cancel: `cancel`, + done: `done`, + next: `next`, + post: `post transaction`, + prev: `prev` + }, + details: { + address: { + hint: `from account`, + label: `the account to transact with` + }, + advancedCheck: { + label: `advanced sending options` + }, + amount: { + hint: `the amount to send to with the transaction`, + label: `transaction value (in ETH)` + }, + function: { + hint: `the function to call on the contract`, + label: `function to execute` + } + }, + rejected: { + state: `You can safely close this window, the function execution will not occur.`, + title: `The execution has been rejected` + }, + steps: { + advanced: `advanced options`, + complete: `complete`, + rejected: `rejected`, + sending: `sending`, + transfer: `function details` + } +}; diff --git a/js/src/i18n/_default/extension.js b/js/src/i18n/_default/extension.js new file mode 100644 index 0000000000..88ba336748 --- /dev/null +++ b/js/src/i18n/_default/extension.js @@ -0,0 +1,20 @@ +// 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 { + install: `Install the extension now`, + intro: `Parity now has an extension available for Chrome that allows safe browsing of Ethereum-enabled distributed applications. It is highly recommended that you install this extension to further enhance your Parity experience.` +}; diff --git a/js/src/i18n/_default/firstRun.js b/js/src/i18n/_default/firstRun.js new file mode 100644 index 0000000000..8439d04798 --- /dev/null +++ b/js/src/i18n/_default/firstRun.js @@ -0,0 +1,32 @@ +// 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 { + button: { + close: `Close`, + create: `Create`, + next: `Next`, + print: `Print Phrase`, + skip: `Skip` + }, + title: { + completed: `completed`, + newAccount: `new account`, + recovery: `recovery`, + terms: `terms`, + welcome: `welcome` + } +}; diff --git a/js/src/i18n/_default/home.js b/js/src/i18n/_default/home.js new file mode 100644 index 0000000000..0b5e68c04e --- /dev/null +++ b/js/src/i18n/_default/home.js @@ -0,0 +1,38 @@ +// 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 { + account: { + visited: `accessed {when}` + }, + accounts: { + none: `No recent accounts history available`, + title: `Recent Accounts` + }, + dapp: { + visited: `accessed {when}` + }, + dapps: { + none: `No recent Applications history available`, + title: `Recent Dapps` + }, + title: `Parity Home`, + url: { + none: `No recent URL history available`, + title: `Web Applications`, + visited: `visited {when}` + } +}; diff --git a/js/src/i18n/_default/index.js b/js/src/i18n/_default/index.js new file mode 100644 index 0000000000..59fea4b059 --- /dev/null +++ b/js/src/i18n/_default/index.js @@ -0,0 +1,46 @@ +// 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 account from './account'; +export accounts from './accounts'; +export addAddress from './addAddress'; +export addContract from './addContract'; +export addressSelect from './addressSelect'; +export application from './application'; +export connection from './connection'; +export contract from './contract'; +export createAccount from './createAccount'; +export createWallet from './createWallet'; +export dapp from './dapp'; +export dapps from './dapps'; +export deleteAccount from './deleteAccount'; +export deployContract from './deployContract'; +export editMeta from './editMeta'; +export executeContract from './executeContract'; +export extension from './extension'; +export firstRun from './firstRun'; +export home from './home'; +export loadContract from './loadContract'; +export parityBar from './parityBar'; +export passwordChange from './passwordChange'; +export settings from './settings'; +export shapeshift from './shapeshift'; +export transfer from './transfer'; +export txEditor from './txEditor'; +export ui from './ui'; +export upgradeParity from './upgradeParity'; +export walletSettings from './walletSettings'; +export web from './web'; diff --git a/js/src/i18n/_default/loadContract.js b/js/src/i18n/_default/loadContract.js new file mode 100644 index 0000000000..d4edb3a81d --- /dev/null +++ b/js/src/i18n/_default/loadContract.js @@ -0,0 +1,43 @@ +// 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 { + button: { + cancel: `Cancel`, + load: `Load`, + no: `No`, + yes: `Yes` + }, + contract: { + savedAt: `Saved {when}` + }, + header: { + saved: `Saved Contracts`, + snippets: `Contract Snippets` + }, + removal: { + confirm: `Are you sure you want to remove the following contract from your saved contracts?`, + savedAt: `Saved {when}` + }, + tab: { + local: `Local`, + snippets: `Snippets` + }, + title: { + remove: `confirm removal`, + view: `view contracts` + } +}; diff --git a/js/src/i18n/_default/parityBar.js b/js/src/i18n/_default/parityBar.js new file mode 100644 index 0000000000..94090a9612 --- /dev/null +++ b/js/src/i18n/_default/parityBar.js @@ -0,0 +1,29 @@ +// 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 { + button: { + close: `Close` + }, + label: { + parity: `Parity`, + signer: `Signer` + }, + title: { + accounts: `Default Account`, + signer: `Parity Signer: Pending` + } +}; diff --git a/js/src/i18n/_default/passwordChange.js b/js/src/i18n/_default/passwordChange.js new file mode 100644 index 0000000000..fc05792509 --- /dev/null +++ b/js/src/i18n/_default/passwordChange.js @@ -0,0 +1,53 @@ +// 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 { + button: { + cancel: `Cancel`, + change: `Change`, + test: `Test`, + wait: `Wait...` + }, + currentPassword: { + hint: `your current password for this account`, + label: `current password` + }, + newPassword: { + hint: `the new password for this account`, + label: `new password` + }, + passwordHint: { + hint: `hint for the new password`, + label: `(optional) new password hint` + }, + repeatPassword: { + error: `the supplied passwords do not match`, + hint: `repeat the new password for this account`, + label: `repeat new password` + }, + success: `Your password has been successfully changed`, + tabChange: { + label: `Change Password` + }, + tabTest: { + label: `Test Password` + }, + testPassword: { + hint: `your account password`, + label: `password` + }, + title: `Password Manager` +}; diff --git a/js/src/i18n/_default/settings.js b/js/src/i18n/_default/settings.js new file mode 100644 index 0000000000..7825660d2d --- /dev/null +++ b/js/src/i18n/_default/settings.js @@ -0,0 +1,89 @@ +// 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 { + background: { + button_more: `generate more`, + overview_0: `The background pattern you can see right now is unique to your Parity installation. It will change every time you create a new Signer token. This is so that decentralized applications cannot pretend to be trustworthy.`, + overview_1: `Pick a pattern you like and memorize it. This Pattern will always be shown from now on, unless you clear your browser cache or use a new Signer token.`, + label: `background` + }, + parity: { + languages: { + hint: `the language this interface is displayed with`, + label: `UI language` + }, + loglevels: `Choose the different logs level.`, + modes: { + hint: `the syning mode for the Parity node`, + label: `mode of operation`, + mode_active: `Parity continuously syncs the chain`, + mode_dark: `Parity syncs only when the RPC is active`, + mode_offline: `Parity doesn't sync`, + mode_passive: `Parity syncs initially, then sleeps and wakes regularly to resync` + }, + overview_0: `Control the Parity node settings and mode of operation via this interface.`, + label: `parity` + }, + proxy: { + details_0: `Instead of accessing Parity via the IP address and port, you will be able to access it via the .parity subdomain, by visiting {homeProxy}. To setup subdomain-based routing, you need to add the relevant proxy entries to your browser,`, + details_1: `To learn how to configure the proxy, instructions are provided for {windowsLink}, {macOSLink} or {ubuntuLink}.`, + details_macos: `macOS`, + details_ubuntu: `Ubuntu`, + details_windows: `Windows`, + overview_0: `The proxy setup allows you to access Parity and all associated decentralized applications via memorable addresses.`, + label: `proxy` + }, + views: { + accounts: { + description: `A list of all the accounts associated to and imported into this Parity instance. Send transactions, receive incoming values, manage your balances and fund your accounts.`, + label: `Accounts` + }, + addresses: { + description: `A list of all contacts and address book entries that is managed by this Parity instance. Watch accounts and have the details available at the click of a button when transacting.`, + label: `Addressbook` + }, + apps: { + description: `Distributed applications that interact with the underlying network. Add applications, manage you application portfolio and interact with application from around the network.`, + label: `Applications` + }, + contracts: { + description: `Watch and interact with specific contracts that have been deployed on the network. This is a more technically-focused environment, specifically for advanced users that understand the inner working of certain contracts.`, + label: `Contracts` + }, + overview_0: `Manage the available application views, using only the parts of the application that is applicable to you.`, + overview_1: `Are you an end-user? The defaults are setups for both beginner and advanced users alike.`, + overview_2: `Are you a developer? Add some features to manage contracts are interact with application deployments.`, + overview_3: `Are you a miner or run a large-scale node? Add the features to give you all the information needed to watch the node operation.`, + settings: { + description: `This view. Allows you to customize the application in term of options, operation and look and feel.`, + label: `Settings` + }, + signer: { + description: `The secure transaction management area of the application where you can approve any outgoing transactions made from the application as well as those placed into the queue by distributed applications.`, + label: `Signer` + }, + status: { + description: `See how the Parity node is performing in terms of connections to the network, logs from the actual running instance and details of mining (if enabled and configured).`, + label: `Status` + }, + label: `views`, + home: { + label: `Home` + } + }, + label: `settings` +}; diff --git a/js/src/i18n/_default/shapeshift.js b/js/src/i18n/_default/shapeshift.js new file mode 100644 index 0000000000..2479278349 --- /dev/null +++ b/js/src/i18n/_default/shapeshift.js @@ -0,0 +1,66 @@ +// 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 { + awaitingDepositStep: { + awaitingConfirmation: `Awaiting confirmation of the deposit address for your {typeSymbol} funds exchange`, + awaitingDeposit: `{shapeshiftLink} is awaiting a {typeSymbol} deposit. Send the funds from your {typeSymbol} network client to -`, + minimumMaximum: `{minimum} minimum, {maximum} maximum` + }, + awaitingExchangeStep: { + awaitingCompletion: `Awaiting the completion of the funds exchange and transfer of funds to your Parity account.`, + receivedInfo: `{shapeshiftLink} has received a deposit of -` + }, + button: { + cancel: `Cancel`, + done: `Close`, + shift: `Shift Funds` + }, + completedStep: { + completed: `{shapeshiftLink} has completed the funds exchange.`, + parityFunds: `The change in funds will be reflected in your Parity account shortly.` + }, + errorStep: { + info: `The funds shifting via {shapeshiftLink} failed with a fatal error on the exchange. The error message received from the exchange is as follow:` + }, + optionsStep: { + noPairs: `There are currently no exchange pairs/coins available to fund with.`, + returnAddr: { + hint: `the return address for send failures`, + label: `(optional) {coinSymbol} return address` + }, + terms: { + label: `I understand that ShapeShift.io is a 3rd-party service and by using the service any transfer of information and/or funds is completely out of the control of Parity` + }, + typeSelect: { + hint: `the type of crypto conversion to do`, + label: `fund account from` + } + }, + price: { + minMax: `({minimum} minimum, {maximum} maximum)` + }, + title: { + completed: `completed`, + deposit: `awaiting deposit`, + details: `details`, + error: `exchange failed`, + exchange: `awaiting exchange` + }, + warning: { + noPrice: `No price match was found for the selected type` + } +}; diff --git a/js/src/i18n/_default/transfer.js b/js/src/i18n/_default/transfer.js new file mode 100644 index 0000000000..742e816f17 --- /dev/null +++ b/js/src/i18n/_default/transfer.js @@ -0,0 +1,27 @@ +// 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 { + advanced: { + data: { + hint: `the data to pass through with the transaction`, + label: `transaction data` + } + }, + warning: { + wallet_spent_limit: `This transaction value is above the remaining daily limit. It will need to be confirmed by other owners.` + } +}; diff --git a/js/src/i18n/_default/txEditor.js b/js/src/i18n/_default/txEditor.js new file mode 100644 index 0000000000..7dab159a8a --- /dev/null +++ b/js/src/i18n/_default/txEditor.js @@ -0,0 +1,39 @@ +// 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 { + condition: { + block: { + hint: `The minimum block to send from`, + label: `Transaction send block` + }, + blocknumber: `Send after BlockNumber`, + date: { + hint: `The minimum date to send from`, + label: `Transaction send date` + }, + datetime: `Send after Date & Time`, + label: `Condition where transaction activates`, + none: `No conditions`, + time: { + hint: `The minimum time to send from`, + label: `Transaction send time` + } + }, + gas: { + info: `You can choose the gas price based on the distribution of recent included transaction gas prices. The lower the gas price is, the cheaper the transaction will be. The higher the gas price is, the faster it should get mined by the network.` + } +}; diff --git a/js/src/i18n/_default/ui.js b/js/src/i18n/_default/ui.js new file mode 100644 index 0000000000..9bed497f5d --- /dev/null +++ b/js/src/i18n/_default/ui.js @@ -0,0 +1,77 @@ +// 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 { + balance: { + none: `There are no balances associated with this account` + }, + blockStatus: { + bestBlock: `{blockNumber} best block`, + syncStatus: `{currentBlock}/{highestBlock} syncing`, + warpRestore: `{percentage}% warp restore`, + warpStatus: `, {percentage}% historic` + }, + confirmDialog: { + no: `no`, + yes: `yes` + }, + identityName: { + null: `NULL`, + unnamed: `UNNAMED` + }, + passwordStrength: { + label: `password strength` + }, + txHash: { + confirmations: `{count} {value, plural, one {confirmation} other {confirmations}}`, + oog: `The transaction might have gone out of gas. Try again with more gas.`, + posted: `The transaction has been posted to the network with a hash of {hashLink}`, + waiting: `waiting for confirmations` + }, + verification: { + gatherData: { + accountHasRequested: { + false: `You did not request verification from this account yet.`, + pending: `Checking if you requested verification…`, + true: `You already requested verification from this account.` + }, + accountIsVerified: { + false: `Your account is not verified yet.`, + pending: `Checking if your account is verified…`, + true: `Your account is already verified.` + }, + email: { + hint: `the code will be sent to this address`, + label: `e-mail address` + }, + fee: `The additional fee is {amount} ETH.`, + isAbleToRequest: { + pending: `Validating your input…` + }, + isServerRunning: { + false: `The verification server is not running.`, + pending: `Checking if the verification server is running…`, + true: `The verification server is running.` + }, + nofee: `There is no additional fee.`, + phoneNumber: { + hint: `the SMS will be sent to this number`, + label: `phone number in international format` + }, + termsOfService: `I agree to the terms and conditions below.` + } + } +}; diff --git a/js/src/i18n/_default/upgradeParity.js b/js/src/i18n/_default/upgradeParity.js new file mode 100644 index 0000000000..cca634b88c --- /dev/null +++ b/js/src/i18n/_default/upgradeParity.js @@ -0,0 +1,44 @@ +// 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 { + busy: `Your upgrade to Parity {newversion} is currently in progress`, + button: { + close: `close`, + done: `done`, + upgrade: `upgrade now` + }, + completed: `Your upgrade to Parity {newversion} has been successfully completed.`, + consensus: { + capable: `Your current Parity version is capable of handling the network requirements.`, + capableUntil: `Your current Parity version is capable of handling the network requirements until block {blockNumber}`, + incapableSince: `Your current Parity version is incapable of handling the network requirements since block {blockNumber}`, + unknown: `Your current Parity version is capable of handling the network requirements.` + }, + failed: `Your upgrade to Parity {newversion} has failed with an error.`, + info: { + upgrade: `A new version of Parity, version {newversion} is available as an upgrade from your current version {currentversion}` + }, + step: { + completed: `upgrade completed`, + error: `error`, + info: `upgrade available`, + updating: `upgrading parity` + }, + version: { + unknown: `unknown` + } +}; diff --git a/js/src/i18n/_default/walletSettings.js b/js/src/i18n/_default/walletSettings.js new file mode 100644 index 0000000000..57dc1a169d --- /dev/null +++ b/js/src/i18n/_default/walletSettings.js @@ -0,0 +1,58 @@ +// 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 { + changes: { + modificationString: `For your modifications to be taken into account, + other owners have to send the same modifications. They can paste + this string to make it easier:`, + none: `No modifications have been made to the Wallet settings.`, + overview: `You are about to make the following modifications` + }, + edit: { + message: `In order to edit this contract's settings, at + least {owners, number} {owners, plural, one {owner } other {owners }} have to + send the very same modifications. You can paste a stringified version + of the modifications here.` + }, + modifications: { + daylimit: { + hint: `amount of ETH spendable without confirmations`, + label: `wallet day limit` + }, + fromString: { + label: `modifications` + }, + owners: { + label: `other wallet owners` + }, + required: { + hint: `number of required owners to accept a transaction`, + label: `required owners` + }, + sender: { + hint: `send modifications as this owner`, + label: `from account (wallet owner)` + } + }, + rejected: { + busyStep: { + state: `The wallet settings will not be modified. You can safely close this window.`, + title: `The modifications have been rejected` + }, + title: `rejected` + } +}; diff --git a/js/src/i18n/_default/web.js b/js/src/i18n/_default/web.js new file mode 100644 index 0000000000..6136d387f6 --- /dev/null +++ b/js/src/i18n/_default/web.js @@ -0,0 +1,19 @@ +// 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 { + requestToken: `Requesting access token...` +}; diff --git a/js/src/views/Settings/Proxy/proxy.js b/js/src/views/Settings/Proxy/proxy.js index 1b8ba49aff..d5142e4a49 100644 --- a/js/src/views/Settings/Proxy/proxy.js +++ b/js/src/views/Settings/Proxy/proxy.js @@ -65,7 +65,7 @@ export default class Proxy extends Component { id='settings.proxy.details_1' defaultMessage='To learn how to configure the proxy, instructions are provided for {windowsLink}, {macOSLink} or {ubuntuLink}.' values={ { - windowsLink: , + windowsLink: , macOSLink: , ubuntuLink: } } -- GitLab From e9eed5206ecc8d04fbd92722aede830249c514b5 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Tue, 14 Feb 2017 12:41:22 +0000 Subject: [PATCH 024/246] [ci skip] js-precompiled 20170214-123602 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 92a1f3a83e..a01351fbdc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1607,7 +1607,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#966b7ab4cf0c02450f14948b2a26cd326c395dc0" +source = "git+https://github.com/ethcore/js-precompiled.git#ec5da356aeb6b7598a27bc07b535e5795f0c82be" 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 842ea412f2..d09483c0e3 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.83", + "version": "0.3.84", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", -- GitLab From e591b4481b85398ab543bc311de5797fb1bc7786 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 14 Feb 2017 19:16:46 +0100 Subject: [PATCH 025/246] warning on detected inconsistency --- ethcore/light/src/transaction_queue.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ethcore/light/src/transaction_queue.rs b/ethcore/light/src/transaction_queue.rs index 50d6a918e4..8ca6a64f6f 100644 --- a/ethcore/light/src/transaction_queue.rs +++ b/ethcore/light/src/transaction_queue.rs @@ -234,8 +234,14 @@ impl TransactionQueue { Some(Condition::Timestamp(time)) => time <= best_block_timestamp, }).map(|info| info.hash) }) - .filter_map(|hash| self.by_hash.get(&hash)) - .cloned() + .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() } -- GitLab From 3c634701ddc6d6694f2760548c20ae4df869f4e9 Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Tue, 14 Feb 2017 19:30:37 +0100 Subject: [PATCH 026/246] Squashed --- Cargo.lock | 72 +++++++++++++++++++++++++ Cargo.toml | 1 + ipfs/Cargo.toml | 14 +++++ ipfs/src/error.rs | 86 +++++++++++++++++++++++++++++ ipfs/src/handler.rs | 129 ++++++++++++++++++++++++++++++++++++++++++++ ipfs/src/lib.rs | 123 ++++++++++++++++++++++++++++++++++++++++++ parity/main.rs | 1 + parity/run.rs | 7 ++- 8 files changed, 432 insertions(+), 1 deletion(-) create mode 100644 ipfs/Cargo.toml create mode 100644 ipfs/src/error.rs create mode 100644 ipfs/src/handler.rs create mode 100644 ipfs/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index a01351fbdc..7fc7cbc41c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,6 +33,7 @@ dependencies = [ "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "parity-hash-fetch 1.6.0", + "parity-ipfs 1.6.0", "parity-reactor 0.1.0", "parity-rpc-client 1.4.0", "parity-updater 1.6.0", @@ -104,6 +105,11 @@ dependencies = [ "syntex_syntax 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "base-x" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "base32" version = "0.3.1" @@ -189,6 +195,16 @@ name = "cfg-if" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "cid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "multibase 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "multihash 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "varmint 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "clippy" version = "0.0.103" @@ -422,6 +438,7 @@ dependencies = [ "base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore 1.6.0", "ethcore-devtools 1.6.0", "ethcore-rpc 1.6.0", "ethcore-util 1.6.0", @@ -1306,6 +1323,23 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "multibase" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base-x 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "multihash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ring 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nanomsg" version = "0.5.1" @@ -1562,6 +1596,18 @@ dependencies = [ "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parity-ipfs" +version = "1.6.0" +dependencies = [ + "cid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore 1.6.0", + "ethcore-util 1.6.0", + "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", + "multihash 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rlp 0.1.0", +] + [[package]] name = "parity-reactor" version = "0.1.0" @@ -1817,6 +1863,15 @@ dependencies = [ "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ring" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rlp" version = "0.1.0" @@ -2326,6 +2381,11 @@ name = "unicode-xid" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "untrusted" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "url" version = "1.2.0" @@ -2353,6 +2413,11 @@ name = "utf8-ranges" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "varmint" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "vecio" version = "0.1.0" @@ -2450,6 +2515,7 @@ dependencies = [ "checksum app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7d1c0d48a81bbb13043847f957971f4d87c81542d80ece5e84ba3cba4058fd4" "checksum arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "16e3bdb2f54b3ace0285975d59a97cf8ed3855294b2b6bc651fcf22a9c352975" "checksum aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07d344974f0a155f091948aa389fb1b912d3a58414fbdb9c8d446d193ee3496a" +"checksum base-x 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2f59103b47307f76e03bef1633aec7fa9e29bfb5aa6daf5a334f94233c71f6c1" "checksum base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1b9605ba46d61df0410d8ac686b0007add8172eba90e8e909c347856fe794d8c" "checksum bigint 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2311bcd71b281e142a095311c22509f0d6bcd87b3000d7dbaa810929b9d6f6ae" "checksum bit-set 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e6e1e6fb1c9e3d6fcdec57216a74eaa03e41f52a22f13a16438251d8e88b89da" @@ -2464,6 +2530,7 @@ dependencies = [ "checksum bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27" "checksum bytes 0.4.0-dev (git+https://github.com/carllerche/bytes)" = "" "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" +"checksum cid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0ad0fdcfbfdfa789a0cf941dd19f7f1d3a377522f6e4c2a760d246ac56b4780" "checksum clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "5b4fabf979ddf6419a313c1c0ada4a5b95cfd2049c56e8418d622d27b4b6ff32" "checksum clippy_lints 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "ce96ec05bfe018a0d5d43da115e54850ea2217981ff0f2e462780ab9d594651a" "checksum cookie 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90266f45846f14a1e986c77d1e9c2626b8c342ed806fe60241ec38cc8697b245" @@ -2531,6 +2598,8 @@ dependencies = [ "checksum mio 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "410a1a0ff76f5a226f1e4e3ff1756128e65cd30166e39c3892283e2ac09d5b67" "checksum miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d5bfc6782530ac8ace97af10a540054a37126b63b0702ddaaa243b73b5745b9a" "checksum msdos_time 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c04b68cc63a8480fb2550343695f7be72effdec953a9d4508161c3e69041c7d8" +"checksum multibase 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b9c35dac080fd6e16a99924c8dfdef0af89d797dd851adab25feaffacf7850d6" +"checksum multihash 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "755d5a39bee3faaf649437e873beab334990221b2faf1f2e56ca10a9e4600235" "checksum nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)" = "" "checksum nanomsg-sys 0.5.0 (git+https://github.com/ethcore/nanomsg.rs.git)" = "" "checksum native-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4e52995154bb6f0b41e4379a279482c9387c1632e3798ba4e511ef8c54ee09" @@ -2581,6 +2650,7 @@ dependencies = [ "checksum regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)" = "b4329b8928a284580a1c63ec9d846b12f6d3472317243ff7077aff11f23f2b29" "checksum regex-syntax 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "841591b1e05609a643e3b4d0045fce04f701daba7151ddcd3ad47b080693d5a9" "checksum reqwest 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bef9ed8fdfcc30947d6b774938dc0c3f369a474efe440df2c7f278180b2d2e6" +"checksum ring 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "87ac4fce2ee4bb10dd106788e90fdfa4c5a7f3f9f6aae29824db77dc57e2767d" "checksum rocksdb 0.4.5 (git+https://github.com/ethcore/rust-rocksdb)" = "" "checksum rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)" = "" "checksum rotor 0.6.3 (git+https://github.com/ethcore/rotor)" = "" @@ -2642,9 +2712,11 @@ dependencies = [ "checksum unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "26643a2f83bac55f1976fb716c10234485f9202dcd65cfbdf9da49867b271172" "checksum unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "36dff09cafb4ec7c8cf0023eb0b686cb6ce65499116a12201c9e11840ca01beb" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +"checksum untrusted 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "193df64312e3515fd983ded55ad5bcaa7647a035804828ed757e832ce6029ef3" "checksum url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afe9ec54bc4db14bc8744b7fed060d785ac756791450959b2248443319d5b119" "checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47" "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" +"checksum varmint 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5211976e8f86adc9920dd7621777bf8974c7812e48eb2aeb97fb1c26cd55ae84" "checksum vecio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0795a11576d29ae80525a3fda315bf7b534f8feb9d34101e5fe63fb95bb2fd24" "checksum vergen 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "56b639f935488eb40f06d17c3e3bcc3054f6f75d264e187b1107c8d1cba8d31c" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" diff --git a/Cargo.toml b/Cargo.toml index f304c917ba..520a6c4c5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ rlp = { path = "util/rlp" } rpc-cli = { path = "rpc_cli" } parity-rpc-client = { path = "rpc_client" } parity-hash-fetch = { path = "hash-fetch" } +parity-ipfs = { path = "ipfs" } parity-updater = { path = "updater" } parity-reactor = { path = "util/reactor" } ethcore-dapps = { path = "dapps", optional = true } diff --git a/ipfs/Cargo.toml b/ipfs/Cargo.toml new file mode 100644 index 0000000000..46a1dd3aac --- /dev/null +++ b/ipfs/Cargo.toml @@ -0,0 +1,14 @@ +[package] +description = "Parity IPFS crate" +name = "parity-ipfs" +version = "1.6.0" +license = "GPL-3.0" +authors = ["Parity Technologies "] + +[dependencies] +ethcore = { path = "../ethcore" } +ethcore-util = { path = "../util" } +rlp = { path = "../util/rlp" } +hyper = { default-features = false, git = "https://github.com/ethcore/hyper" } +cid = "~0.2.0" +multihash = "~0.5.0" diff --git a/ipfs/src/error.rs b/ipfs/src/error.rs new file mode 100644 index 0000000000..6513c6da0c --- /dev/null +++ b/ipfs/src/error.rs @@ -0,0 +1,86 @@ +// 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 . + +use {multihash, cid, hyper}; +use handler::Out; + +pub type Result = ::std::result::Result; + +/// IPFS server error +#[derive(Debug)] +pub enum ServerError { + /// Wrapped `std::io::Error` + IoError(::std::io::Error), + /// Other `hyper` error + Other(hyper::error::Error), +} + +pub enum Error { + CidParsingFailed, + UnsupportedHash, + UnsupportedCid, + BlockNotFound, + TransactionNotFound, + StateRootNotFound, +} + +impl From for Out { + fn from(err: Error) -> Out { + use self::Error::*; + + match err { + UnsupportedHash => Out::Bad("Hash must be Keccak-256"), + UnsupportedCid => Out::Bad("CID codec not supported"), + CidParsingFailed => Out::Bad("CID parsing failed"), + BlockNotFound => Out::NotFound("Block not found"), + TransactionNotFound => Out::NotFound("Transaction not found"), + StateRootNotFound => Out::NotFound("State root not found"), + } + } +} + +impl From for Error { + fn from(_: cid::Error) -> Error { + Error::CidParsingFailed + } +} + +impl From for Error { + fn from(_: multihash::Error) -> Error { + Error::CidParsingFailed + } +} + +impl From<::std::io::Error> for ServerError { + fn from(err: ::std::io::Error) -> ServerError { + ServerError::IoError(err) + } +} + +impl From for ServerError { + fn from(err: hyper::error::Error) -> ServerError { + ServerError::Other(err) + } +} + +impl From for String { + fn from(err: ServerError) -> String { + match err { + ServerError::IoError(err) => err.to_string(), + ServerError::Other(err) => err.to_string(), + } + } +} diff --git a/ipfs/src/handler.rs b/ipfs/src/handler.rs new file mode 100644 index 0000000000..e8a8549d68 --- /dev/null +++ b/ipfs/src/handler.rs @@ -0,0 +1,129 @@ +use {rlp, multihash}; +use error::{Error, Result}; +use cid::{ToCid, Codec}; + +use std::sync::Arc; +use std::ops::Deref; +use multihash::Hash; +use hyper::Next; +use util::{Bytes, H256}; +use ethcore::client::{BlockId, TransactionId, BlockChainClient}; + +type Reason = &'static str; + +pub enum Out { + OctetStream(Bytes), + NotFound(Reason), + Bad(Reason), +} + +pub struct IpfsHandler { + client: Arc, + out: Out, +} + +impl IpfsHandler { + pub fn new(client: Arc) -> Self { + IpfsHandler { + client: client, + out: Out::NotFound("Route not found") + } + } + + pub fn out(&self) -> &Out { + &self.out + } + + pub fn route(&mut self, path: &str, query: Option<&str>) -> Next { + let result = match path { + "/api/v0/block/get" => self.route_cid(query), + _ => return Next::write(), + }; + + match result { + Ok(_) => Next::write(), + Err(err) => { + self.out = err.into(); + + Next::write() + } + } + } + + fn route_cid(&mut self, query: Option<&str>) -> Result<()> { + let query = query.unwrap_or(""); + + let cid = get_param(&query, "arg").ok_or(Error::CidParsingFailed)?.to_cid()?; + + let mh = multihash::decode(&cid.hash)?; + + if mh.alg != Hash::Keccak256 { return Err(Error::UnsupportedHash); } + + let hash: H256 = mh.digest.into(); + + match cid.codec { + Codec::EthereumBlock => self.get_block(hash), + Codec::EthereumBlockList => self.get_block_list(hash), + Codec::EthereumTx => self.get_transaction(hash), + Codec::EthereumStateTrie => self.get_state_trie(hash), + _ => return Err(Error::UnsupportedCid), + } + } + + fn get_block(&mut self, hash: H256) -> Result<()> { + let block_id = BlockId::Hash(hash); + let block = self.client.block_header(block_id).ok_or(Error::BlockNotFound)?; + + self.out = Out::OctetStream(block.into_inner()); + + Ok(()) + } + + fn get_block_list(&mut self, hash: H256) -> Result<()> { + let ommers = self.client.find_uncles(&hash).ok_or(Error::BlockNotFound)?; + + self.out = Out::OctetStream(rlp::encode(&ommers).to_vec()); + + Ok(()) + } + + fn get_transaction(&mut self, hash: H256) -> Result<()> { + let tx_id = TransactionId::Hash(hash); + let tx = self.client.transaction(tx_id).ok_or(Error::TransactionNotFound)?; + + self.out = Out::OctetStream(rlp::encode(tx.deref()).to_vec()); + + Ok(()) + } + + fn get_state_trie(&mut self, hash: H256) -> Result<()> { + let data = self.client.state_data(&hash).ok_or(Error::StateRootNotFound)?; + + self.out = Out::OctetStream(data); + + Ok(()) + } +} + +/// Get a query parameter's value by name. +pub fn get_param<'a>(query: &'a str, name: &str) -> Option<&'a str> { + query.split('&') + .find(|part| part.starts_with(name) && part[name.len()..].starts_with("=")) + .map(|part| &part[name.len() + 1..]) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_param() { + let query = "foo=100&bar=200&qux=300"; + + assert_eq!(get_param(query, "foo"), Some("100")); + assert_eq!(get_param(query, "bar"), Some("200")); + assert_eq!(get_param(query, "qux"), Some("300")); + assert_eq!(get_param(query, "bar="), None); + assert_eq!(get_param(query, "200"), None); + } +} diff --git a/ipfs/src/lib.rs b/ipfs/src/lib.rs new file mode 100644 index 0000000000..341d80ab8c --- /dev/null +++ b/ipfs/src/lib.rs @@ -0,0 +1,123 @@ +// 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 . + +extern crate hyper; +extern crate multihash; +extern crate cid; + +extern crate rlp; +extern crate ethcore; +extern crate ethcore_util as util; + +mod error; +mod handler; + +use std::sync::Arc; +use error::ServerError; +use handler::{IpfsHandler, Out}; +use hyper::server::{Listening, Handler, Request, Response}; +use hyper::net::HttpStream; +use hyper::header::{ContentLength, ContentType}; +use hyper::{Next, Encoder, Decoder, Method, RequestUri, StatusCode}; +use ethcore::client::BlockChainClient; + +impl Handler for IpfsHandler { + fn on_request(&mut self, req: Request) -> Next { + if *req.method() != Method::Get { + return Next::write(); + } + + let (path, query) = match *req.uri() { + RequestUri::AbsolutePath { ref path, ref query } => (path, query.as_ref().map(AsRef::as_ref)), + _ => return Next::write(), + }; + + self.route(path, query) + } + + fn on_request_readable(&mut self, _decoder: &mut Decoder) -> Next { + Next::write() + } + + fn on_response(&mut self, res: &mut Response) -> Next { + use Out::*; + + match *self.out() { + OctetStream(ref bytes) => { + let headers = res.headers_mut(); + + headers.set(ContentLength(bytes.len() as u64)); + headers.set(ContentType("application/octet-stream".parse() + .expect("Static content type; qed"))); + + Next::write() + }, + NotFound(reason) => { + res.set_status(StatusCode::NotFound); + + res.headers_mut().set(ContentLength(reason.len() as u64)); + res.headers_mut().set(ContentType("text/plain".parse() + .expect("Static content type; qed"))); + + Next::write() + }, + Bad(reason) => { + res.set_status(StatusCode::BadRequest); + + res.headers_mut().set(ContentLength(reason.len() as u64)); + res.headers_mut().set(ContentType("text/plain".parse() + .expect("Static content type; qed"))); + + Next::write() + } + } + } + + fn on_response_writable(&mut self, transport: &mut Encoder) -> Next { + use Out::*; + + match *self.out() { + OctetStream(ref bytes) => { + // Nothing to do here + let _ = transport.write(&bytes); + + Next::end() + }, + NotFound(reason) | Bad(reason) => { + // Nothing to do here + let _ = transport.write(reason.as_bytes()); + + Next::end() + } + } + } +} + +pub fn start_server(client: Arc) -> Result { + let addr = "0.0.0.0:5001".parse().expect("can't fail on static input; qed"); + + hyper::Server::http(&addr)? + .handle(move |_| IpfsHandler::new(client.clone())) + .map(|(listening, srv)| { + + ::std::thread::spawn(move || { + srv.run(); + }); + + listening + }) + .map_err(Into::into) +} diff --git a/parity/main.rs b/parity/main.rs index 63d59d5fa1..199dff4b47 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -56,6 +56,7 @@ extern crate ethcore_signer; extern crate ethcore_util as util; extern crate ethsync; extern crate parity_hash_fetch as hash_fetch; +extern crate parity_ipfs as ipfs; extern crate parity_reactor; extern crate parity_updater as updater; extern crate rpc_cli; diff --git a/parity/run.rs b/parity/run.rs index 56cd26ea0f..728505068a 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -47,6 +47,7 @@ use dir::Directories; use cache::CacheConfig; use user_defaults::UserDefaults; use dapps; +use ipfs; use signer; use modules; use rpc_apis; @@ -420,6 +421,10 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R }; let signer_server = signer::start(cmd.signer_conf.clone(), signer_deps)?; + + // the ipfs server + let ipfs_server = ipfs::start_server(client.clone())?; + // the informant let informant = Arc::new(Informant::new( service.client(), @@ -476,7 +481,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R let restart = wait_for_exit(panic_handler, Some(updater), can_restart); // drop this stuff as soon as exit detected. - drop((http_server, ipc_server, dapps_server, signer_server, event_loop)); + drop((http_server, ipc_server, dapps_server, signer_server, ipfs_server, event_loop)); info!("Finishing work, please wait..."); -- GitLab From 9256aa766b5af2b28c4b940e92b55c0fe79ce31b Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Tue, 14 Feb 2017 19:58:46 +0100 Subject: [PATCH 027/246] Lock file --- Cargo.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 7fc7cbc41c..1b9be74ed7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -438,7 +438,6 @@ dependencies = [ "base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore 1.6.0", "ethcore-devtools 1.6.0", "ethcore-rpc 1.6.0", "ethcore-util 1.6.0", -- GitLab From 2ee2d2ea4520e28c001f9afa2667f7f19d92b345 Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Tue, 14 Feb 2017 20:03:25 +0100 Subject: [PATCH 028/246] Added missing GPL header --- ipfs/src/handler.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ipfs/src/handler.rs b/ipfs/src/handler.rs index e8a8549d68..0673826c25 100644 --- a/ipfs/src/handler.rs +++ b/ipfs/src/handler.rs @@ -1,3 +1,19 @@ +// 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 . + use {rlp, multihash}; use error::{Error, Result}; use cid::{ToCid, Codec}; -- GitLab From d925cc05da1814b0be5f148fa3f8e7c6beb44e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 14 Feb 2017 22:45:29 +0100 Subject: [PATCH 029/246] Fix console dapp (#4544) * Fixing linting issues. Better support for console as secure app * Fixing linting issues --- dapps/src/lib.rs | 2 +- dapps/src/rpc.rs | 3 +- js/src/dapps/static/console.js | 966 ++++++++++++++++++--------------- js/src/util/web3.extensions.js | 67 +-- 4 files changed, 560 insertions(+), 478 deletions(-) diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index bda5b305ce..3f26e82a9f 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -301,7 +301,7 @@ impl Server { let special = Arc::new({ let mut special = HashMap::new(); - special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, panic_handler.clone())); + special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, cors_domains.clone(), panic_handler.clone())); special.insert(router::SpecialEndpoint::Utils, apps::utils()); special.insert( router::SpecialEndpoint::Api, diff --git a/dapps/src/rpc.rs b/dapps/src/rpc.rs index bf1b1dc933..c30fd1d49f 100644 --- a/dapps/src/rpc.rs +++ b/dapps/src/rpc.rs @@ -25,13 +25,14 @@ use endpoint::{Endpoint, EndpointPath, Handler}; pub fn rpc>( handler: RpcHandler, + cors_domains: Vec, panic_handler: Arc () + Send>>>>, ) -> Box { Box::new(RpcEndpoint { handler: handler, meta_extractor: Arc::new(MetadataExtractor), panic_handler: panic_handler, - cors_domain: None, + cors_domain: Some(cors_domains.into_iter().map(AccessControlAllowOrigin::Value).collect()), // NOTE [ToDr] We don't need to do any hosts validation here. It's already done in router. allowed_hosts: None, }) diff --git a/js/src/dapps/static/console.js b/js/src/dapps/static/console.js index b647860e73..b6faee7f04 100755 --- a/js/src/dapps/static/console.js +++ b/js/src/dapps/static/console.js @@ -14,53 +14,62 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -/* eslint-disable */ -// TODO: Fix linting issues - -if (typeof(window.parent.secureApi) == 'object') - window.api = window.parent.secureApi; -else if (typeof(window.parity) == 'object') +/* global web3 */ + +if (typeof (window.parent.secureApi) === 'object') { + window.api = window.parent.secureApi; + + if (typeof (window.Web3) === 'function') { + Promise.all([ + window.api.parity.dappsInterface(), + window.api.parity.dappsPort() + ]).then(res => { + window.web3 = new window.Web3(new window.Web3.providers.HttpProvider(`http://${res.join(':')}/rpc/`)); + }); + } +} else if (typeof (window.parity) === 'object') { window.api = window.parity.api; +} -if (typeof(window.api) === 'object') - window.api.subscribe('eth_blockNumber', function (error, blockNumber) { - if (error) { - console.log('error', error); - return; - } - refreshWatches(); - }); - -function escapeHtml(str) { - var div = document.createElement('div'); - div.appendChild(document.createTextNode(str)); - return div.innerHTML; +if (typeof (window.api) === 'object') { + window.api.subscribe('eth_blockNumber', function (error, blockNumber) { + if (error) { + console.log('error', error); + return; + } + refreshWatches(); + }); } -function getAllPropertyNames(obj) { - var props = {}; - do { - Object.getOwnPropertyNames(obj).forEach(n => props[n] = true); - } while (obj = Object.getPrototypeOf(obj)); - return Object.keys(props); +function escapeHtml (str) { + let div = document.createElement('div'); + + div.appendChild(document.createTextNode(str)); + return div.innerHTML; } -function htmlToElement(html) { - var template = document.createElement('template'); - template.innerHTML = html; - return template.content.firstChild; +function getAllPropertyNames (obj) { + let props = {}; + + do { + Object.getOwnPropertyNames(obj).forEach(n => { + props[n] = true; + }); + obj = Object.getPrototypeOf(obj); + } while (obj); + + return Object.keys(props); } -function evaluate(x) { +function evaluate (x) { try { - return eval(x); - } - catch (err) { - return eval('(()=>{var x = ' + x + "; return x;})()") + return eval(x); // eslint-disable-line no-eval + } catch (err) { + return eval('(()=>{let x = ' + x + '; return x;})()'); // eslint-disable-line no-eval } } -function safeAccess(obj, prop) { +function safeAccess (obj, prop) { try { return obj[prop]; } catch (e) { @@ -68,23 +77,26 @@ function safeAccess(obj, prop) { } } -function displayReady(x, visited = []) { +function displayReady (x, visited = []) { visited.push(x); - var toString = Object.prototype.toString; - if (x === undefined) - return 'undefined'; - if (x === null) + let toString = Object.prototype.toString; + + if (x === undefined) { return 'undefined'; } + if (x === null) { return 'null'; - if (typeof(x) == "string") - return `"${escapeHtml(x)}"`; - if (toString.call(x) === '[object Array]') + } + if (typeof (x) === 'string') { + return `"${escapeHtml(x)}"`; + } + if (toString.call(x) === '[object Array]') { return `[${x.map(el => displayReady(el, visited)).join(', ')}]`; - if (typeof(x) == "function") - return `function () { /* ... */ }`; - if (typeof(x) == "object") { - var constructor = x.constructor || Object; - var objToString = typeof(x.toString) == "function" ? x.toString : toString; - if (objToString.call(x).indexOf('[object ') != 0) { + } + if (typeof (x) === 'function') { return `function () { /* ... */ }`; } + if (typeof (x) === 'object') { + let constructor = x.constructor || Object; + let objToString = typeof (x.toString) === 'function' ? x.toString : toString; + + if (objToString.call(x).indexOf('[object ') !== 0) { return `${escapeHtml(objToString.call(x))}`; } @@ -95,144 +107,212 @@ function displayReady(x, visited = []) { ${escapeHtml(f)}: ${displayReady(safeAccess(x, f), visited)} `).join(', ')} } - `; + + `; } - return `${escapeHtml(JSON.stringify(x))}`; + return `${escapeHtml(JSON.stringify(x))}`; } -if (!localStorage.history) - localStorage.history = "[]"; +if (!localStorage.history) { + localStorage.history = '[]'; +} window.historyData = JSON.parse(localStorage.history); window.historyIndex = window.historyData.length; -if (!localStorage.watches) - localStorage.watches = "[]"; +if (!localStorage.watches) { + localStorage.watches = '[]'; +} window.watches = {}; -function watch(name, f) { - let status = document.getElementById("status"); +function watch (name, f) { + let status = document.getElementById('status'); let cleanName = name.replace(/[^a-zA-Z0-9]/, ''); - status.innerHTML += `
${escapeHtml(name)}
`; + + status.innerHTML += ` +
+ ${escapeHtml(name)} + +
+ `; window.watches[name] = f; } -var savedWatches = JSON.parse(localStorage.watches); +let savedWatches = JSON.parse(localStorage.watches); + savedWatches.forEach(w => watch(w[1], () => evaluate(w[0]))); -if (typeof(window.web3) == 'object' && window.watches.latest == undefined) +if (typeof (window.web3) === 'object' && window.watches.latest === undefined) { watch('latest', () => window.web3.eth.blockNumber); +} - -function refreshWatches() { - for (n in window.watches) { +function refreshWatches () { + for (let n in window.watches) { let r = window.watches[n](); let cn = n.replace(/[^a-zA-Z0-9]/, ''); let e = document.getElementById(`res_${cn}`); - if (typeof(r) == 'object' && r.constructor && r.constructor.name == "Promise") - r.then(r => e.innerHTML = displayReady(r)); - else + + if (typeof (r) === 'object' && r.then && r.then.call) { + r.then(r => { + e.innerHTML = displayReady(r); + }); + } else { e.innerHTML = displayReady(r); + } } } -function removeWatch(name) { +function removeWatch (name) { let e = document.getElementById(`watch_${name}`); + e.parentNode.removeChild(e); delete window.watches[name]; } -function newLog(level, text) { +function newLog (level, text) { let icon = { - debug: " ", - log: " ", - warn: "âš ", - error: "✖", - info: "ℹ" + debug: ' ', + log: ' ', + warn: 'âš ', + error: '✖', + info: 'ℹ' }; - pushLine('
' + icon[level] + '' + escapeHtml(text) + '
'); + + pushLine([ + '
', + icon[level], + '', + escapeHtml(text), + '
' + ].join('')); } -function exec() { - let command = document.getElementById("command"); +function exec () { + let command = document.getElementById('command'); let c = command.value; - if (c != '') { - command.value = ""; + if (c !== '') { + command.value = ''; window.historyData.push(c); - while (window.historyData.length > 1000) - window.historyData.shift; + while (window.historyData.length > 1000) { + window.historyData.shift(); + } + localStorage.history = JSON.stringify(window.historyData); window.historyIndex = window.historyData.length; - var html = ''; - if (c.indexOf("//") == 0) { + if (c.indexOf('//') === 0) { let n = c.substr(2); - savedWatches = savedWatches.filter(x => x[1] != n); + + savedWatches = savedWatches.filter(x => x[1] !== n); localStorage.watches = JSON.stringify(savedWatches); removeWatch(n); - } - else if (c.indexOf("//") != -1) { - x = c.split("//"); + } else if (c.indexOf('//') !== -1) { + let x = c.split('//'); let e = x[0]; + savedWatches.push(x); localStorage.watches = JSON.stringify(savedWatches); watch(x[1], () => evaluate(e)); - pushLine('
>' + escapeHtml(c) + '
'); - pushLine('
✓Watch added
'); - } - else { - pushLine('
>' + escapeHtml(c) + '
'); + + pushLine([ + '
>', + escapeHtml(c), + '
' + ].join('')); + + pushLine([ + '
✓', + 'Watch added', + '
' + ].join('')); + } else { + pushLine([ + '
>', + escapeHtml(c), + '
' + ].join('')); + let res; + try { res = evaluate(c); - if (typeof(res) == 'object' && res !== null && res.constructor && res.constructor.name == "Promise") { + if (typeof (res) === 'object' && res !== null && typeof res.then === 'function') { let id = window.historyData.length; - pushLine('
<...
'); - res.then(r => document.getElementById('pending' + id).innerHTML = displayReady(r)); + + pushLine([ + '
<...
' + ].join('')); + + res.then(r => { + document.getElementById('pending' + id).innerHTML = displayReady(r); + }); } else { - pushLine('
<' + displayReady(res) + '
'); + pushLine([ + '
<', + displayReady(res), + '
' + ].join('')); } - } - catch (err) { - pushLine('
✖Unhandled exception: ' + escapeHtml(err.message) + '
'); + } catch (err) { + pushLine([ + '
✖Unhandled exception: ', + escapeHtml(err.message), + '
' + ]); } } } + refreshWatches(); } -function pushLine(l) { - document.getElementById("history").innerHTML += l - var h = document.getElementById("history-wrap"); +function pushLine (l) { + document.getElementById('history').innerHTML += l; + let h = document.getElementById('history-wrap'); + h.scrollTop = h.scrollHeight; } -var autocompletes = []; -var currentAuto = null; -var currentPots = []; -var currentStem = null; +let autocompletes = []; +let currentAuto = null; +let currentPots = []; +let currentStem = null; -function updateAutocomplete() { - let v = document.getElementById("command").value; - if (v.length == 0) { +function updateAutocomplete () { + let v = document.getElementById('command').value; + + if (!v.length) { cancelAutocomplete(); return; } + let t = v.split('.'); let last = t.pop(); let tj = t.join('.'); let ex = t.length > 0 ? tj : 'window'; - if (currentStem != tj) { - autocompletes = eval('getAllPropertyNames('+ex+')'); + + if (currentStem !== tj) { + autocompletes = getAllPropertyNames(evaluate(ex)); currentStem = tj; } - let dl = document.getElementById("autocomplete"); + + let dl = document.getElementById('autocomplete'); + currentPots = autocompletes.filter(n => n.startsWith(last)); if (currentPots.length > 0) { - if (currentPots.indexOf(currentAuto) == -1) + if (currentPots.indexOf(currentAuto) === -1) { currentAuto = currentPots[0]; + } + dl.innerHTML = currentPots -// .map(n => `${tj != '' ? tj + '.' : ''}${n}`) - .map((n, i) => `
${escapeHtml(last)}${escapeHtml(n.substr(last.length))}
`) + .map((n, i) => ` +
+ ${escapeHtml(last)}${escapeHtml(n.substr(last.length))} +
` + ) .join(''); dl.hidden = false; } else { @@ -240,391 +320,411 @@ function updateAutocomplete() { } } -function enactAutocomplete() { +function enactAutocomplete () { if (currentAuto != null) { - document.getElementById("command").value = (currentStem != '' ? currentStem + '.' : '') + currentAuto; + document.getElementById('command').value = (currentStem !== '' ? currentStem + '.' : '') + currentAuto; cancelAutocomplete(); } } -function cancelAutocomplete() { - document.getElementById("autocomplete").hidden = true; +function cancelAutocomplete () { + document.getElementById('autocomplete').hidden = true; currentAuto = null; } -function scrollAutocomplete(positive) { +function scrollAutocomplete (positive) { if (currentAuto != null) { - var i = currentPots.indexOf(currentAuto); + let i = currentPots.indexOf(currentAuto); + document.getElementById('pot' + i).classList = ['ac-unselected']; - if (positive && i < currentPots.length - 1) + if (positive && i < currentPots.length - 1) { ++i; - else if (!positive && i > 0) + } else if (!positive && i > 0) { --i; + } currentAuto = currentPots[i]; let sel = document.getElementById('pot' + i); + sel.classList = ['ac-selected']; sel.scrollIntoViewIfNeeded(); } } -document.getElementById("command").addEventListener("paste", updateAutocomplete); -document.getElementById("command").addEventListener("input", updateAutocomplete); -document.getElementById("command").addEventListener("focusout", cancelAutocomplete); -document.getElementById("command").addEventListener("blur", cancelAutocomplete); +document.getElementById('command').addEventListener('paste', updateAutocomplete); +document.getElementById('command').addEventListener('input', updateAutocomplete); +document.getElementById('command').addEventListener('focusout', cancelAutocomplete); +document.getElementById('command').addEventListener('blur', cancelAutocomplete); + +document.getElementById('command').addEventListener('keydown', function (event) { + let el = document.getElementById('command'); -document.getElementById("command").addEventListener("keydown", function(event) { - let el = document.getElementById("command"); if (currentAuto != null) { - if (event.keyCode == 38 || event.keyCode == 40) { + if (event.keyCode === 38 || event.keyCode === 40) { event.preventDefault(); - scrollAutocomplete(event.keyCode == 40); - } - else if ((event.keyCode == 39 || event.keyCode == 9 || event.keyCode == 13) && el.selectionStart == el.value.length) { + scrollAutocomplete(event.keyCode === 40); + } else if ((event.keyCode === 39 || event.keyCode === 9 || event.keyCode === 13) && el.selectionStart === el.value.length) { event.preventDefault(); enactAutocomplete(); - } - else if (event.keyCode == 27) { + } else if (event.keyCode === 27) { event.preventDefault(); cancelAutocomplete(); } } else { - let command = document.getElementById("command"); - if (event.keyCode == 38 && window.historyIndex > 0) { + let command = document.getElementById('command'); + + if (event.keyCode === 38 && window.historyIndex > 0) { event.preventDefault(); window.historyIndex--; command.value = window.historyData[window.historyIndex]; } - if (event.keyCode == 40 && window.historyIndex < window.historyData.length) { + if (event.keyCode === 40 && window.historyIndex < window.historyData.length) { event.preventDefault(); window.historyIndex++; - command.value = window.historyIndex < window.historyData.length ? window.historyData[window.historyIndex] : ""; + command.value = window.historyIndex < window.historyData.length ? window.historyData[window.historyIndex] : ''; } } - if (event.keyCode >= 48 || event.keyCode == 8) { - let t = document.getElementById("command").value; + + if (event.keyCode >= 48 || event.keyCode === 8) { + let t = document.getElementById('command').value; + setTimeout(() => { - if (t != document.getElementById("command").value) + if (t !== document.getElementById('command').value) { updateAutocomplete(); + } }, 0); - } - else { + } else { setTimeout(() => { - if (el.selectionStart != el.value.length) + if (el.selectionStart !== el.value.length) { cancelAutocomplete(); + } }, 0); } }); -document.getElementById("command").addEventListener("keyup", function(event) { - if (event.keyCode == 13) { +document.getElementById('command').addEventListener('keyup', function (event) { + if (event.keyCode === 13) { event.preventDefault(); exec(); } }); -document.getElementById("command").focus(); -if (typeof(web3) === 'object') +document.getElementById('command').focus(); +if (typeof (web3) === 'object') { window.web3 = web3; +} refreshWatches(); -["debug", "error", "info", "log", "warn"].forEach(n => { +['debug', 'error', 'info', 'log', 'warn'].forEach(n => { let old = window.console[n].bind(window.console); - window.console[n] = x => { old(x); newLog(n, x); }; -}); - - - - -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -///// Home comforts. - - -if (typeof(web3) === 'object') { -// Usage example: -// web3.eth.traceCall({ -// to: theChicken.address, -// data: theChicken.withdraw.getData(100000000000000000), -// gas: 100000 -// }, -// `["trace", "vmTrace", "stateDiff"] -// ) -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'traceCall', - call: 'trace_call', - params: 2, - inputFormatter: [web3._extend.formatters.inputCallFormatter, null] - }) - ] -}); - -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'traceSendRawTransaction', - call: 'trace_rawTransaction', - params: 2, - inputFormatter: [null, null] - }) - ] -}); - -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'traceReplayTransaction', - call: 'trace_replayTransaction', - params: 2, - inputFormatter: [null, null] - }) - ] -}); - -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'setMode', - call: 'parity_setMode', - params: 1, - }) - ] -}); - -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'mode', - call: 'parity_mode', - params: 0, - }) - ] -}); - -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'traceTransaction', - call: 'trace_Transaction', - params: 1, - inputFormatter: [null] - }) - ] -}); - -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'gasPriceStatistics', - call: 'parity_gasPriceStatistics', - params: 0, - outputFormatter: function(a) { return a.map(web3.toBigNumber); } - }) - ] -}); - -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'registryAddress', - call: 'parity_registryAddress', - params: 0 - }) - ] -}); - -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'accountsInfo', - call: 'personal_accountsInfo', - outputFormatter: function(m) { Object.keys(m).forEach(k => { - m[k].meta = JSON.parse(m[k].meta); - m[k].meta.name = m[k].name; - m[k].meta.uuid = m[k].uuid; - m[k] = m[k].meta; - }); return m; }, - params: 0 - }) - ] -}); - -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'setAccountName', - call: 'personal_setAccountName', - params: 2, - }) - ] -}); -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'setAccountMeta', - call: 'personal_setAccountMeta', - params: 2, - inputFormatter: [a => a, JSON.stringify] - }) - ] + window.console[n] = x => { + old(x); + newLog(n, x); + }; }); -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'postTransaction', - call: 'eth_postTransaction', - params: 1, - inputFormatter: [web3._extend.formatters.inputCallFormatter] - }) - ] -}); +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// /// Home comforts. + +if (typeof (web3) === 'object') { + // Usage example: + // web3.eth.traceCall({ + // to: theChicken.address, + // data: theChicken.withdraw.getData(100000000000000000), + // gas: 100000 + // }, + // `["trace", "vmTrace", "stateDiff"] + // ) + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'traceCall', + call: 'trace_call', + params: 2, + inputFormatter: [web3._extend.formatters.inputCallFormatter, null] + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'traceSendRawTransaction', + call: 'trace_rawTransaction', + params: 2, + inputFormatter: [null, null] + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'traceReplayTransaction', + call: 'trace_replayTransaction', + params: 2, + inputFormatter: [null, null] + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'setMode', + call: 'parity_setMode', + params: 1 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'mode', + call: 'parity_mode', + params: 0 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'traceTransaction', + call: 'trace_Transaction', + params: 1, + inputFormatter: [null] + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'gasPriceStatistics', + call: 'parity_gasPriceStatistics', + params: 0, + outputFormatter: function (a) { return a.map(web3.toBigNumber); } + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'registryAddress', + call: 'parity_registryAddress', + params: 0 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'accountsInfo', + call: 'personal_accountsInfo', + outputFormatter: function (m) { + Object.keys(m).forEach(k => { + m[k].meta = JSON.parse(m[k].meta); + m[k].meta.name = m[k].name; + m[k].meta.uuid = m[k].uuid; + m[k] = m[k].meta; + }); return m; + }, + params: 0 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'setAccountName', + call: 'personal_setAccountName', + params: 2 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'setAccountMeta', + call: 'personal_setAccountMeta', + params: 2, + inputFormatter: [a => a, JSON.stringify] + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'postTransaction', + call: 'eth_postTransaction', + params: 1, + inputFormatter: [web3._extend.formatters.inputCallFormatter] + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'postSign', + call: 'eth_postSign', + params: 1 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'encryptMessage', + call: 'parity_encryptMessage', + params: 2 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'checkRequest', + call: 'eth_checkRequest', + params: 1 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'listAccounts', + call: 'parity_listAccounts', + params: 0 + }) + ] + }); + + { + let postTransaction = web3.eth.postTransaction.bind(web3.eth); + let sendTransaction = web3.eth.sendTransaction.bind(web3.eth); + + web3.eth.sendTransaction = function (options, f) { + // No callback - do sync API. + if (typeof f !== 'function') { + return sendTransaction(options); + } + // Callback - use async API. + let id = postTransaction(options); + + console.log('Posted trasaction id=' + id); + let timerId = window.setInterval(check, 500); + + function check () { + try { + let r = web3.eth.checkRequest(id); + + if (typeof r === 'string') { + clearInterval(timerId); + if (r === '0x0000000000000000000000000000000000000000000000000000000000000000') { + f('Rejected', r); + } else { + f(null, r); + } + } else if (r !== null) { + console.log('checkRequest returned: ' + r); + } + } catch (e) { + clearInterval(timerId); + f('Rejected', null); + } + } + }; + } -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'postSign', - call: 'eth_postSign', - params: 1 - }) - ] -}); + web3.eth.installInterceptor = function (interceptor) { + let oldSendTransaction = web3.eth.sendTransaction.bind(web3.eth); -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'encryptMessage', - call: 'parity_encryptMessage', - params: 2 - }) - ] -}); + web3.eth.sendTransaction = function (options, f) { + if (!interceptor(options)) { + return '0x0000000000000000000000000000000000000000000000000000000000000000'; + } -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'checkRequest', - call: 'eth_checkRequest', - params: 1 - }) - ] -}); + return oldSendTransaction(options, f); + }; + }; -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'listAccounts', - call: 'parity_listAccounts', - params: 0 - }) - ] -}); + web3.eth.reporter = function (e, r) { + if (e) { + console.log('Error confirming transaction: ' + e); + } else { + let addr = r; + let confirmed = false; + let timerId = window.setInterval(function check () { + let receipt = web3.eth.getTransactionReceipt(addr); + + if (receipt != null) { + if (!confirmed) { + console.log('Transaction confirmed (' + r + '); used ' + receipt.gasUsed + ' gas; left ' + receipt.logs.length + ' logs; mining...'); + confirmed = true; + } + if (typeof receipt.blockHash === 'string') { + clearInterval(timerId); + console.log('Mined into block ' + receipt.blockNumber); + } + } + }, 500); + } + }; -{ - var postTransaction = web3.eth.postTransaction.bind(web3.eth); - var sendTransaction = web3.eth.sendTransaction.bind(web3.eth); - web3.eth.sendTransaction = function(options, f) { - // No callback - do sync API. - if (typeof f != "function") - return sendTransaction(options); - // Callback - use async API. - var id = postTransaction(options); - console.log("Posted trasaction id=" + id); - var timerId = window.setInterval(check, 500); - function check() { - try { - let r = web3.eth.checkRequest(id); - if (typeof r == 'string') { - clearInterval(timerId); - if (r == "0x0000000000000000000000000000000000000000000000000000000000000000") - f("Rejected", r); - else - f(null, r); - } else if (r !== null) { - console.log("checkRequest returned: " + r); - } - } - catch (e) { - clearInterval(timerId); - f("Rejected", null); - } - } - } -} + { + let oldSha3 = web3.sha3; -web3.eth.installInterceptor = function(interceptor) { - var oldSendTransaction = web3.eth.sendTransaction.bind(web3.eth); - web3.eth.sendTransaction = function(options, f) { - if (interceptor(options) == false) - return "0x0000000000000000000000000000000000000000000000000000000000000000"; - return oldSendTransaction(options, f); - }; -} + web3.sha3 = function (data, format) { + if (typeof format !== 'string' || (format !== 'hex' && format !== 'bin')) { + format = data.startsWith('0x') ? 'hex' : 'bin'; + } + return oldSha3(data, { encoding: format }); + }; + } -web3.eth.reporter = function(e, r) { - if (e) { - console.log("Error confirming transaction: " + e); - } else { - var addr = r; - var confirmed = false; - var timer_id = window.setInterval(check, 500); - function check() { - var receipt = web3.eth.getTransactionReceipt(addr); - if (receipt != null) { - if (!confirmed) { - console.log("Transaction confirmed (" + r + "); used " + receipt.gasUsed + " gas; left " + receipt.logs.length + " logs; mining..."); - confirmed = true; - } - if (typeof receipt.blockHash == 'string') { - clearInterval(timer_id); - console.log("Mined into block " + receipt.blockNumber); - } - } - } - } -} + { + let Registry = web3.eth.contract([{ 'constant': false, 'inputs': [{ 'name': '_new', 'type': 'address' }], 'name': 'setOwner', 'outputs': [], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'string' }], 'name': 'confirmReverse', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }], 'name': 'reserve', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }, { 'name': '_value', 'type': 'bytes32' }], 'name': 'set', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }], 'name': 'drop', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }], 'name': 'getAddress', 'outputs': [{ 'name': '', 'type': 'address' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_amount', 'type': 'uint256' }], 'name': 'setFee', 'outputs': [], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_to', 'type': 'address' }], 'name': 'transfer', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': true, 'inputs': [], 'name': 'owner', 'outputs': [{ 'name': '', 'type': 'address' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }], 'name': 'reserved', 'outputs': [{ 'name': 'reserved', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [], 'name': 'drain', 'outputs': [], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'string' }, { 'name': '_who', 'type': 'address' }], 'name': 'proposeReverse', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }], 'name': 'getUint', 'outputs': [{ 'name': '', 'type': 'uint256' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }], 'name': 'get', 'outputs': [{ 'name': '', 'type': 'bytes32' }], 'type': 'function' }, { 'constant': true, 'inputs': [], 'name': 'fee', 'outputs': [{ 'name': '', 'type': 'uint256' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '', 'type': 'address' }], 'name': 'reverse', 'outputs': [{ 'name': '', 'type': 'string' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }, { 'name': '_value', 'type': 'uint256' }], 'name': 'setUint', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [], 'name': 'removeReverse', 'outputs': [], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }, { 'name': '_value', 'type': 'address' }], 'name': 'setAddress', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'anonymous': false, 'inputs': [{ 'indexed': false, 'name': 'amount', 'type': 'uint256' }], 'name': 'Drained', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': false, 'name': 'amount', 'type': 'uint256' }], 'name': 'FeeChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'bytes32' }, { 'indexed': true, 'name': 'owner', 'type': 'address' }], 'name': 'Reserved', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'bytes32' }, { 'indexed': true, 'name': 'oldOwner', 'type': 'address' }, { 'indexed': true, 'name': 'newOwner', 'type': 'address' }], 'name': 'Transferred', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'bytes32' }, { 'indexed': true, 'name': 'owner', 'type': 'address' }], 'name': 'Dropped', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'bytes32' }, { 'indexed': true, 'name': 'owner', 'type': 'address' }, { 'indexed': true, 'name': 'key', 'type': 'string' }, { 'indexed': false, 'name': 'plainKey', 'type': 'string' }], 'name': 'DataChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'string' }, { 'indexed': true, 'name': 'reverse', 'type': 'address' }], 'name': 'ReverseProposed', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'string' }, { 'indexed': true, 'name': 'reverse', 'type': 'address' }], 'name': 'ReverseConfirmed', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'string' }, { 'indexed': true, 'name': 'reverse', 'type': 'address' }], 'name': 'ReverseRemoved', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'old', 'type': 'address' }, { 'indexed': true, 'name': 'current', 'type': 'address' }], 'name': 'NewOwner', 'type': 'event' }]); -{ - var oldSha3 = web3.sha3 - web3.sha3 = function(data, format) { - if (typeof format !== 'string' || (format != 'hex' && format != 'bin')) - format = data.startsWith('0x') ? 'hex' : 'bin'; - return oldSha3(data, {encoding: format}); - } -} + web3.eth.registry = Registry.at(web3.eth.registryAddress()); + web3.eth.registry.lookup = (name, field) => web3.eth.registry.get(web3.sha3(name), field); + web3.eth.registry.lookupAddress = (name, field) => web3.eth.registry.getAddress(web3.sha3(name), field); + web3.eth.registry.lookupUint = (name, field) => web3.eth.registry.getUint(web3.sha3(name), field); -{ - var Registry = web3.eth.contract([{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"set","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_to","type":"address"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"proposeReverse","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"uint256"}],"name":"setUint","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"address"}],"name":"setAddress","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Drained","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"FeeChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"}],"name":"Reserved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"oldOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"Transferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"}],"name":"Dropped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"key","type":"string"},{"indexed":false,"name":"plainKey","type":"string"}],"name":"DataChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":true,"name":"reverse","type":"address"}],"name":"ReverseProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":true,"name":"reverse","type":"address"}],"name":"ReverseConfirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":true,"name":"reverse","type":"address"}],"name":"ReverseRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}]); - web3.eth.registry = Registry.at(web3.eth.registryAddress()); - web3.eth.registry.lookup = (name, field) => web3.eth.registry.get(web3.sha3(name), field); - web3.eth.registry.lookupAddress = (name, field) => web3.eth.registry.getAddress(web3.sha3(name), field); - web3.eth.registry.lookupUint = (name, field) => web3.eth.registry.getUint(web3.sha3(name), field); + let TokenReg = web3.eth.contract([{ 'constant': true, 'inputs': [{ 'name': '_id', 'type': 'uint256' }], 'name': 'token', 'outputs': [{ 'name': 'addr', 'type': 'address' }, { 'name': 'tla', 'type': 'string' }, { 'name': 'base', 'type': 'uint256' }, { 'name': 'name', 'type': 'string' }, { 'name': 'owner', 'type': 'address' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_new', 'type': 'address' }], 'name': 'setOwner', 'outputs': [], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_addr', 'type': 'address' }, { 'name': '_tla', 'type': 'string' }, { 'name': '_base', 'type': 'uint256' }, { 'name': '_name', 'type': 'string' }], 'name': 'register', 'outputs': [{ 'name': '', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_fee', 'type': 'uint256' }], 'name': 'setFee', 'outputs': [], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_id', 'type': 'uint256' }, { 'name': '_key', 'type': 'bytes32' }], 'name': 'meta', 'outputs': [{ 'name': '', 'type': 'bytes32' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_addr', 'type': 'address' }, { 'name': '_tla', 'type': 'string' }, { 'name': '_base', 'type': 'uint256' }, { 'name': '_name', 'type': 'string' }, { 'name': '_owner', 'type': 'address' }], 'name': 'registerAs', 'outputs': [{ 'name': '', 'type': 'bool' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_tla', 'type': 'string' }], 'name': 'fromTLA', 'outputs': [{ 'name': 'id', 'type': 'uint256' }, { 'name': 'addr', 'type': 'address' }, { 'name': 'base', 'type': 'uint256' }, { 'name': 'name', 'type': 'string' }, { 'name': 'owner', 'type': 'address' }], 'type': 'function' }, { 'constant': true, 'inputs': [], 'name': 'owner', 'outputs': [{ 'name': '', 'type': 'address' }], 'type': 'function' }, { 'constant': false, 'inputs': [], 'name': 'drain', 'outputs': [], 'type': 'function' }, { 'constant': true, 'inputs': [], 'name': 'tokenCount', 'outputs': [{ 'name': '', 'type': 'uint256' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_id', 'type': 'uint256' }], 'name': 'unregister', 'outputs': [], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_addr', 'type': 'address' }], 'name': 'fromAddress', 'outputs': [{ 'name': 'id', 'type': 'uint256' }, { 'name': 'tla', 'type': 'string' }, { 'name': 'base', 'type': 'uint256' }, { 'name': 'name', 'type': 'string' }, { 'name': 'owner', 'type': 'address' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_id', 'type': 'uint256' }, { 'name': '_key', 'type': 'bytes32' }, { 'name': '_value', 'type': 'bytes32' }], 'name': 'setMeta', 'outputs': [], 'type': 'function' }, { 'constant': true, 'inputs': [], 'name': 'fee', 'outputs': [{ 'name': '', 'type': 'uint256' }], 'type': 'function' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'tla', 'type': 'string' }, { 'indexed': true, 'name': 'id', 'type': 'uint256' }, { 'indexed': false, 'name': 'addr', 'type': 'address' }, { 'indexed': false, 'name': 'name', 'type': 'string' }], 'name': 'Registered', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'tla', 'type': 'string' }, { 'indexed': true, 'name': 'id', 'type': 'uint256' }], 'name': 'Unregistered', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'id', 'type': 'uint256' }, { 'indexed': true, 'name': 'key', 'type': 'bytes32' }, { 'indexed': false, 'name': 'value', 'type': 'bytes32' }], 'name': 'MetaChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'old', 'type': 'address' }, { 'indexed': true, 'name': 'current', 'type': 'address' }], 'name': 'NewOwner', 'type': 'event' }]); - var TokenReg = web3.eth.contract([{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"token","outputs":[{"name":"addr","type":"address"},{"name":"tla","type":"string"},{"name":"base","type":"uint256"},{"name":"name","type":"string"},{"name":"owner","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_tla","type":"string"},{"name":"_base","type":"uint256"},{"name":"_name","type":"string"}],"name":"register","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_fee","type":"uint256"}],"name":"setFee","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"}],"name":"meta","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_tla","type":"string"},{"name":"_base","type":"uint256"},{"name":"_name","type":"string"},{"name":"_owner","type":"address"}],"name":"registerAs","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"_tla","type":"string"}],"name":"fromTLA","outputs":[{"name":"id","type":"uint256"},{"name":"addr","type":"address"},{"name":"base","type":"uint256"},{"name":"name","type":"string"},{"name":"owner","type":"address"}],"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"tokenCount","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"}],"name":"unregister","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_addr","type":"address"}],"name":"fromAddress","outputs":[{"name":"id","type":"uint256"},{"name":"tla","type":"string"},{"name":"base","type":"uint256"},{"name":"name","type":"string"},{"name":"owner","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"},{"name":"_value","type":"bytes32"}],"name":"setMeta","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"tla","type":"string"},{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"addr","type":"address"},{"indexed":false,"name":"name","type":"string"}],"name":"Registered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"tla","type":"string"},{"indexed":true,"name":"id","type":"uint256"}],"name":"Unregistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":true,"name":"key","type":"bytes32"},{"indexed":false,"name":"value","type":"bytes32"}],"name":"MetaChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}]); - web3.eth.tokenReg = TokenReg.at(web3.eth.registry.lookupAddress('tokenreg', 'A')); -} + web3.eth.tokenReg = TokenReg.at(web3.eth.registry.lookupAddress('tokenreg', 'A')); + } } -/* eslint-enable */ diff --git a/js/src/util/web3.extensions.js b/js/src/util/web3.extensions.js index 0755e365b8..fc9fe06b55 100644 --- a/js/src/util/web3.extensions.js +++ b/js/src/util/web3.extensions.js @@ -14,54 +14,35 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import parity from '~/jsonrpc/interfaces/parity'; +import signer from '~/jsonrpc/interfaces/signer'; +import trace from '~/jsonrpc/interfaces/trace'; + export default function web3extensions (web3) { - const { Method, formatters } = web3._extend; + const { Method } = web3._extend; + + // TODO [ToDr] Consider output/input formatters. + const methods = (object, name) => { + return Object.keys(object).map(method => { + return new Method({ + name: method, + call: `${name}_{method}`, + params: object[method].params.length + }); + }); + }; return [{ - property: 'personal', - methods: [ - new Method({ - name: 'sendTransaction', - call: 'personal_sendTransaction', - params: 2, - inputFormatter: [formatters.inputTransactionFormatter, null] - }), - new Method({ - name: 'signerEnabled', - call: 'personal_signerEnabled', - params: 0, - inputFormatter: [] - }) - ], + property: 'parity', + methods: methods(parity, 'parity'), + properties: [] + }, { + property: 'signer', + methods: methods(signer, 'signer'), properties: [] }, { - property: 'ethcore', - methods: [ - new Method({ - name: 'getNetPeers', - call: 'ethcore_netPeers', - params: 0, - outputFormatter: x => x - }), - new Method({ - name: 'getNetChain', - call: 'ethcore_netChain', - params: 0, - outputFormatter: x => x - }), - new Method({ - name: 'gasPriceStatistics', - call: 'ethcore_gasPriceStatistics', - params: 0, - outputFormatter: a => a.map(web3.toBigNumber) - }), - new Method({ - name: 'unsignedTransactionsCount', - call: 'ethcore_unsignedTransactionsCount', - params: 0, - inputFormatter: [] - }) - ], + property: 'trace', + methods: methods(trace, 'trace'), properties: [] }]; } -- GitLab From 5369a129ae276d38f3490abb18c5093b338246e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 14 Feb 2017 22:45:43 +0100 Subject: [PATCH 030/246] Signer provenance (#4477) * Signer - Tracking Request Provenance * Basic UI * Changing messages * VecDeque::from * Fix dapps tests * Addressing UI grumbles --- Cargo.lock | 3 +- Cargo.toml | 3 +- dapps/src/rpc.rs | 3 +- dapps/src/tests/rpc.rs | 8 +- ethcore/src/engines/tendermint/mod.rs | 4 +- ethcore/src/trace/db.rs | 2 +- js/src/api/format/output.js | 7 + js/src/api/transport/ws/ws.js | 7 + .../Signer/components/RequestOrigin/index.js | 17 ++ .../RequestOrigin/requestOrigin.css | 43 +++++ .../components/RequestOrigin/requestOrigin.js | 116 +++++++++++++ .../RequestOrigin/requestOrigin.spec.js | 72 ++++++++ .../RequestPending/requestPending.js | 5 +- .../components/SignRequest/signRequest.js | 13 +- .../transactionMainDetails.css | 18 +- .../transactionMainDetails.js | 13 +- .../TransactionPending/transactionPending.js | 10 +- .../Signer/containers/Embedded/embedded.js | 3 +- .../containers/RequestsPage/requestsPage.js | 3 +- parity/main.rs | 9 +- parity/rpc.rs | 17 +- parity/signer.rs | 14 +- rpc/src/lib.rs | 12 +- rpc/src/v1/helpers/requests.rs | 4 +- rpc/src/v1/helpers/signing_queue.rs | 20 +-- rpc/src/v1/impls/eth.rs | 4 +- rpc/src/v1/impls/light/eth.rs | 2 +- rpc/src/v1/impls/parity.rs | 2 +- rpc/src/v1/impls/personal.rs | 2 +- rpc/src/v1/impls/signing.rs | 62 ++++--- rpc/src/v1/impls/signing_unsafe.rs | 10 +- rpc/src/v1/metadata.rs | 29 +--- rpc/src/v1/mod.rs | 3 +- rpc/src/v1/tests/mocked/eth.rs | 3 +- rpc/src/v1/tests/mocked/signer.rs | 26 +-- rpc/src/v1/traits/eth_signing.rs | 4 +- rpc/src/v1/traits/parity_signing.rs | 8 +- rpc/src/v1/types/confirmations.rs | 23 ++- rpc/src/v1/types/dapp_id.rs | 80 --------- rpc/src/v1/types/hash.rs | 9 +- rpc/src/v1/types/mod.rs | 33 ++-- rpc/src/v1/types/provenance.rs | 154 ++++++++++++++++++ rpc/src/v1/types/transaction_condition.rs | 1 + rpc_client/src/lib.rs | 3 + signer/src/ws_server/mod.rs | 25 ++- signer/src/ws_server/session.rs | 109 ++++++++----- 46 files changed, 747 insertions(+), 271 deletions(-) create mode 100644 js/src/views/Signer/components/RequestOrigin/index.js create mode 100644 js/src/views/Signer/components/RequestOrigin/requestOrigin.css create mode 100644 js/src/views/Signer/components/RequestOrigin/requestOrigin.js create mode 100644 js/src/views/Signer/components/RequestOrigin/requestOrigin.spec.js delete mode 100644 rpc/src/v1/types/dapp_id.rs create mode 100644 rpc/src/v1/types/provenance.rs diff --git a/Cargo.lock b/Cargo.lock index a01351fbdc..faed42dcd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,10 +25,9 @@ dependencies = [ "ethcore-util 1.6.0", "ethsync 1.6.0", "fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", "isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", - "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index f304c917ba..4dac6a2212 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ number_prefix = "0.2" rpassword = "0.2.1" semver = "0.5" ansi_term = "0.7" -lazy_static = "0.2" regex = "0.1" isatty = "0.1" toml = "0.2" @@ -24,7 +23,7 @@ serde = "0.9" serde_json = "0.9" app_dirs = "1.1.1" fdlimit = "0.1" -hyper = { version = "0.9", default-features = false } +hyper = { default-features = false, git = "https://github.com/ethcore/hyper" } ctrlc = { git = "https://github.com/ethcore/rust-ctrlc.git" } jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" } ethsync = { path = "sync" } diff --git a/dapps/src/rpc.rs b/dapps/src/rpc.rs index c30fd1d49f..cc6f4d81a3 100644 --- a/dapps/src/rpc.rs +++ b/dapps/src/rpc.rs @@ -77,8 +77,7 @@ impl HttpMetaExtractor for MetadataExtractor { }) }); Metadata { - dapp_id: dapp_id, - origin: Origin::Dapps, + origin: Origin::Dapps(dapp_id.map(Into::into).unwrap_or_default()), } } } diff --git a/dapps/src/tests/rpc.rs b/dapps/src/tests/rpc.rs index 7c24860999..0dbba384c2 100644 --- a/dapps/src/tests/rpc.rs +++ b/dapps/src/tests/rpc.rs @@ -55,8 +55,8 @@ fn should_extract_metadata() { // given let mut io = MetaIoHandler::default(); io.add_method_with_meta("rpc_test", |_params, meta: Metadata| { - assert_eq!(meta.dapp_id, Some("https://parity.io/".to_owned())); - assert_eq!(meta.origin, Origin::Dapps); + assert_eq!(meta.origin, Origin::Dapps("https://parity.io/".into())); + assert_eq!(meta.dapp_id(), "https://parity.io/".into()); future::ok(Value::String("Hello World!".into())).boxed() }); let server = serve_with_rpc(io); @@ -89,8 +89,8 @@ fn should_extract_metadata_from_custom_header() { // given let mut io = MetaIoHandler::default(); io.add_method_with_meta("rpc_test", |_params, meta: Metadata| { - assert_eq!(meta.dapp_id, Some("https://parity.io/".to_owned())); - assert_eq!(meta.origin, Origin::Dapps); + assert_eq!(meta.origin, Origin::Dapps("https://parity.io/".into())); + assert_eq!(meta.dapp_id(), "https://parity.io/".into()); future::ok(Value::String("Hello World!".into())).boxed() }); let server = serve_with_rpc(io); diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index a8f953d6fa..e4bb2ea2f6 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -649,8 +649,8 @@ mod tests { use account_provider::AccountProvider; use spec::Spec; use engines::{Engine, EngineError, Seal}; - use super::*; - use super::message::*; + use super::{Step, View, Height, message_info_rlp, message_full_rlp}; + use super::message::VoteStep; /// Accounts inserted with "0" and "1" are validators. First proposer is "0". fn setup() -> (Spec, Arc) { diff --git a/ethcore/src/trace/db.rs b/ethcore/src/trace/db.rs index 206f1cb7ef..5842aaafae 100644 --- a/ethcore/src/trace/db.rs +++ b/ethcore/src/trace/db.rs @@ -305,7 +305,7 @@ impl TraceDatabase for TraceDB where T: DatabaseExtras { } fn trace(&self, block_number: BlockNumber, tx_position: usize, trace_position: Vec) -> Option { - let trace_position_deq = trace_position.into_iter().collect::>(); + let trace_position_deq = VecDeque::from(trace_position); self.extras.block_hash(block_number) .and_then(|block_hash| self.transactions_traces(&block_hash) .and_then(|traces| traces.into_iter().nth(tx_position)) diff --git a/js/src/api/format/output.js b/js/src/api/format/output.js index 92a3635660..094cda25ae 100644 --- a/js/src/api/format/output.js +++ b/js/src/api/format/output.js @@ -203,6 +203,13 @@ export function outSignerRequest (request) { request[key].signTransaction = outTransaction(request[key].signTransaction); request[key].sendTransaction = outTransaction(request[key].sendTransaction); break; + + case 'origin': + const type = Object.keys(request[key])[0]; + const details = request[key][type]; + + request[key] = { type, details }; + break; } }); } diff --git a/js/src/api/transport/ws/ws.js b/js/src/api/transport/ws/ws.js index 478c3e8c5d..4b41935cdc 100644 --- a/js/src/api/transport/ws/ws.js +++ b/js/src/api/transport/ws/ws.js @@ -28,6 +28,7 @@ export default class Ws extends JsonRpcBase { this._url = url; this._token = token; this._messages = {}; + this._sessionHash = null; this._connecting = false; this._connected = false; @@ -78,12 +79,14 @@ export default class Ws extends JsonRpcBase { this._ws.onmessage = null; this._ws.close(); this._ws = null; + this._sessionHash = null; } this._connecting = true; this._connected = false; this._lastError = null; + this._sessionHash = sha3; this._ws = new WebSocket(this._url, hash); this._ws.onerror = this._onError; this._ws.onopen = this._onOpen; @@ -255,6 +258,10 @@ export default class Ws extends JsonRpcBase { return this._token; } + get sessionHash () { + return this._sessionHash; + } + get isAutoConnect () { return this._autoConnect; } diff --git a/js/src/views/Signer/components/RequestOrigin/index.js b/js/src/views/Signer/components/RequestOrigin/index.js new file mode 100644 index 0000000000..2f703c9242 --- /dev/null +++ b/js/src/views/Signer/components/RequestOrigin/index.js @@ -0,0 +1,17 @@ +// 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 from './requestOrigin'; diff --git a/js/src/views/Signer/components/RequestOrigin/requestOrigin.css b/js/src/views/Signer/components/RequestOrigin/requestOrigin.css new file mode 100644 index 0000000000..f4aac4c485 --- /dev/null +++ b/js/src/views/Signer/components/RequestOrigin/requestOrigin.css @@ -0,0 +1,43 @@ +/* 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 . +*/ + +.container { + text-align: left; + margin: 3em .5em; + opacity: 0.6; + font-size: 0.8em; + + .unknown { + color: #e44; + } + + .url { + max-width: 100%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + .hash { + margin-left: .2em; + } + + .hash, .url { + margin-bottom: -.2em; + display: inline-block; + } +} diff --git a/js/src/views/Signer/components/RequestOrigin/requestOrigin.js b/js/src/views/Signer/components/RequestOrigin/requestOrigin.js new file mode 100644 index 0000000000..fb9ef4cd6c --- /dev/null +++ b/js/src/views/Signer/components/RequestOrigin/requestOrigin.js @@ -0,0 +1,116 @@ +// 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 . + +import React, { Component, PropTypes } from 'react'; + +import IdentityIcon from '~/ui/IdentityIcon'; + +import styles from './requestOrigin.css'; + +export default class RequestOrigin extends Component { + static contextTypes = { + api: PropTypes.object.isRequired + }; + + static propTypes = { + origin: PropTypes.shape({ + type: PropTypes.oneOf(['unknown', 'dapp', 'rpc', 'ipc', 'signer']), + details: PropTypes.string.isRequired + }).isRequired + }; + + render () { + const { origin } = this.props; + + return ( +
+ Requested { this.renderOrigin(origin) } +
+ ); + } + + renderOrigin (origin) { + if (origin.type === 'unknown') { + return ( + via unknown interface + ); + } + + if (origin.type === 'dapp') { + return ( + + by a dapp at + { origin.details || 'unknown URL' } + + + ); + } + + if (origin.type === 'rpc') { + return ( + + via RPC + ({ origin.details || 'unidentified' }) + + + ); + } + + if (origin.type === 'ipc') { + return ( + + via IPC session + + + + + ); + } + + if (origin.type === 'signer') { + return this.renderSigner(origin.details); + } + } + + renderSigner (session) { + if (session.substr(2) === this.context.api.transport.sessionHash) { + return ( + via current tab + ); + } + + return ( + + via UI session + + + + + ); + } +} diff --git a/js/src/views/Signer/components/RequestOrigin/requestOrigin.spec.js b/js/src/views/Signer/components/RequestOrigin/requestOrigin.spec.js new file mode 100644 index 0000000000..f1de13db34 --- /dev/null +++ b/js/src/views/Signer/components/RequestOrigin/requestOrigin.spec.js @@ -0,0 +1,72 @@ +// 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 . + +import { shallow } from 'enzyme'; +import React from 'react'; + +import RequestOrigin from './'; + +const context = { + context: { + api: { + transport: { + sessionHash: '1234' + } + } + } +}; + +describe('views/Signer/components/RequestOrigin', () => { + it('renders unknown', () => { + expect(shallow( + , + context + ).text()).to.equal('Requested via unknown interface'); + }); + + it('renders dapps', () => { + expect(shallow( + , + context + ).text()).to.equal('Requested by a dapp at http://parity.io'); + }); + + it('renders rpc', () => { + expect(shallow( + , + context + ).text()).to.equal('Requested via RPC (unidentified)'); + }); + + it('renders ipc', () => { + expect(shallow( + , + context + ).text()).to.equal('Requested via IPC session'); + }); + + it('renders signer', () => { + expect(shallow( + , + context + ).text()).to.equal('Requested via UI session'); + + expect(shallow( + , + context + ).text()).to.equal('Requested via current tab'); + }); +}); diff --git a/js/src/views/Signer/components/RequestPending/requestPending.js b/js/src/views/Signer/components/RequestPending/requestPending.js index f860fcd009..2d745d26fa 100644 --- a/js/src/views/Signer/components/RequestPending/requestPending.js +++ b/js/src/views/Signer/components/RequestPending/requestPending.js @@ -30,6 +30,7 @@ export default class RequestPending extends Component { isTest: PropTypes.bool.isRequired, onConfirm: PropTypes.func.isRequired, onReject: PropTypes.func.isRequired, + origin: PropTypes.object.isRequired, payload: PropTypes.oneOfType([ PropTypes.shape({ sendTransaction: PropTypes.object.isRequired }), PropTypes.shape({ sign: PropTypes.object.isRequired }), @@ -51,7 +52,7 @@ export default class RequestPending extends Component { }; render () { - const { className, date, focus, gasLimit, id, isSending, isTest, onReject, payload, store } = this.props; + const { className, date, focus, gasLimit, id, isSending, isTest, onReject, payload, store, origin } = this.props; if (payload.sign) { const { sign } = payload; @@ -68,6 +69,7 @@ export default class RequestPending extends Component { isTest={ isTest } onConfirm={ this.onConfirm } onReject={ onReject } + origin={ origin } store={ store } /> ); @@ -87,6 +89,7 @@ export default class RequestPending extends Component { isTest={ isTest } onConfirm={ this.onConfirm } onReject={ onReject } + origin={ origin } store={ store } transaction={ transaction } /> diff --git a/js/src/views/Signer/components/SignRequest/signRequest.js b/js/src/views/Signer/components/SignRequest/signRequest.js index 21f5211e62..c5b9cc9762 100644 --- a/js/src/views/Signer/components/SignRequest/signRequest.js +++ b/js/src/views/Signer/components/SignRequest/signRequest.js @@ -19,6 +19,7 @@ import { observer } from 'mobx-react'; import Account from '../Account'; import TransactionPendingForm from '../TransactionPendingForm'; +import RequestOrigin from '../RequestOrigin'; import styles from './signRequest.css'; @@ -40,9 +41,9 @@ export default class SignRequest extends Component { }; static propTypes = { - id: PropTypes.object.isRequired, address: PropTypes.string.isRequired, data: PropTypes.string.isRequired, + id: PropTypes.object.isRequired, isFinished: PropTypes.bool.isRequired, isTest: PropTypes.bool.isRequired, store: PropTypes.object.isRequired, @@ -52,11 +53,16 @@ export default class SignRequest extends Component { isSending: PropTypes.bool, onConfirm: PropTypes.func, onReject: PropTypes.func, + origin: PropTypes.any, status: PropTypes.string }; static defaultProps = { - focus: false + focus: false, + origin: { + type: 'unknown', + details: '' + } }; componentWillMount () { @@ -92,7 +98,7 @@ export default class SignRequest extends Component { renderDetails () { const { api } = this.context; - const { address, isTest, store, data } = this.props; + const { address, isTest, store, data, origin } = this.props; const { balances, externalLink } = store; const balance = balances[address]; @@ -110,6 +116,7 @@ export default class SignRequest extends Component { externalLink={ externalLink } isTest={ isTest } /> +

A request to sign data using your account:

diff --git a/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.css b/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.css index fb11b8506c..048a59c675 100644 --- a/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.css +++ b/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.css @@ -39,15 +39,17 @@ width: 40%; vertical-align: top; - img { - display: inline-block; - width: 50px; - height: 50px; - margin: 5px; - } + .account { + img { + display: inline-block; + width: 50px; + height: 50px; + margin: 5px; + } - span { - display: block; + span { + display: block; + } } } diff --git a/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.js b/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.js index e73fee9222..119af23822 100644 --- a/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.js +++ b/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.js @@ -22,6 +22,8 @@ import { Button, MethodDecoding } from '~/ui'; import * as tUtil from '../util/transaction'; import Account from '../Account'; +import RequestOrigin from '../RequestOrigin'; + import styles from './transactionMainDetails.css'; export default class TransactionMainDetails extends Component { @@ -33,11 +35,19 @@ export default class TransactionMainDetails extends Component { gasStore: PropTypes.object, id: PropTypes.object.isRequired, isTest: PropTypes.bool.isRequired, + origin: PropTypes.any, totalValue: PropTypes.object.isRequired, transaction: PropTypes.object.isRequired, value: PropTypes.object.isRequired }; + static defaultProps = { + origin: { + type: 'unknown', + details: '' + } + }; + componentWillMount () { const { totalValue, value } = this.props; @@ -51,7 +61,7 @@ export default class TransactionMainDetails extends Component { } render () { - const { children, externalLink, from, fromBalance, gasStore, isTest, transaction } = this.props; + const { children, externalLink, from, fromBalance, gasStore, isTest, transaction, origin } = this.props; return (
@@ -64,6 +74,7 @@ export default class TransactionMainDetails extends Component { isTest={ isTest } />
+
{ const { actions, gasLimit, isTest } = this.props; - const { date, id, isSending, payload } = data; + const { date, id, isSending, payload, origin } = data; return ( diff --git a/js/src/views/Signer/containers/RequestsPage/requestsPage.js b/js/src/views/Signer/containers/RequestsPage/requestsPage.js index ac460b8d4f..7ec4abf2ae 100644 --- a/js/src/views/Signer/containers/RequestsPage/requestsPage.js +++ b/js/src/views/Signer/containers/RequestsPage/requestsPage.js @@ -107,7 +107,7 @@ class RequestsPage extends Component { renderPending = (data, index) => { const { actions, gasLimit, isTest } = this.props; - const { date, id, isSending, payload } = data; + const { date, id, isSending, payload, origin } = data; return ( diff --git a/parity/main.rs b/parity/main.rs index 63d59d5fa1..b0c8f9070e 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -28,7 +28,7 @@ extern crate ctrlc; extern crate docopt; extern crate env_logger; extern crate fdlimit; -extern crate hyper; // for price_info.rs +extern crate hyper; extern crate isatty; extern crate jsonrpc_core; extern crate num_cpus; @@ -60,15 +60,14 @@ extern crate parity_reactor; extern crate parity_updater as updater; extern crate rpc_cli; +#[macro_use] +extern crate log as rlog; + #[cfg(feature="stratum")] extern crate ethcore_stratum; #[cfg(feature = "dapps")] extern crate ethcore_dapps; - -#[macro_use] -extern crate log as rlog; - macro_rules! dependency { ($dep_ty:ident, $url:expr) => { { diff --git a/parity/rpc.rs b/parity/rpc.rs index fd077f1ff7..49bd94699c 100644 --- a/parity/rpc.rs +++ b/parity/rpc.rs @@ -21,9 +21,10 @@ use std::io; use io::PanicHandler; use dir::default_data_path; -use ethcore_rpc::{self as rpc, RpcServerError, IpcServerError, Metadata}; +use ethcore_rpc::{self as rpc, RpcServerError, IpcServerError, Metadata, Origin}; use ethcore_rpc::informant::{RpcStats, Middleware}; use helpers::parity_ipc_path; +use hyper; use jsonrpc_core::MetaIoHandler; use jsonrpc_core::reactor::{RpcHandler, Remote}; use rpc_apis; @@ -89,6 +90,18 @@ pub struct Dependencies { pub stats: Arc, } +pub struct RpcExtractor; +impl rpc::HttpMetaExtractor for RpcExtractor { + fn read_metadata(&self, req: &hyper::server::Request) -> Metadata { + let origin = req.headers().get::() + .map(|origin| format!("{}://{}", origin.scheme, origin.host)) + .unwrap_or_else(|| "unknown".into()); + let mut metadata = Metadata::default(); + metadata.origin = Origin::Rpc(origin); + metadata + } +} + pub fn new_http(conf: HttpConfiguration, deps: &Dependencies) -> Result, String> { if !conf.enabled { return Ok(None); @@ -113,7 +126,7 @@ pub fn setup_http_rpc_server( let apis = setup_apis(apis, dependencies); let handler = RpcHandler::new(Arc::new(apis), dependencies.remote.clone()); let ph = dependencies.panic_handler.clone(); - let start_result = rpc::start_http(url, cors_domains, allowed_hosts, ph, handler); + let start_result = rpc::start_http(url, cors_domains, allowed_hosts, ph, handler, RpcExtractor); match start_result { Err(RpcServerError::IoError(err)) => match err.kind() { io::ErrorKind::AddrInUse => Err(format!("RPC address {} is already in use, make sure that another instance of an Ethereum client is not running or change the address using the --jsonrpc-port and --jsonrpc-interface options.", url)), diff --git a/parity/signer.rs b/parity/signer.rs index 9b22968dc4..346276496d 100644 --- a/parity/signer.rs +++ b/parity/signer.rs @@ -23,12 +23,14 @@ pub use ethcore_signer::Server as SignerServer; use ansi_term::Colour; use dir::default_data_path; use ethcore_rpc::informant::RpcStats; +use ethcore_rpc; use ethcore_signer as signer; use helpers::replace_home; use io::{ForwardPanic, PanicHandler}; use jsonrpc_core::reactor::{RpcHandler, Remote}; use rpc_apis; use util::path::restrict_permissions_owner; +use util::H256; const CODES_FILENAME: &'static str = "authcodes"; @@ -67,6 +69,16 @@ pub struct NewToken { pub message: String, } +#[derive(Debug, Default, Clone)] +pub struct StandardExtractor; +impl signer::MetaExtractor for StandardExtractor { + fn extract_metadata(&self, session: &H256) -> ethcore_rpc::Metadata { + let mut metadata = ethcore_rpc::Metadata::default(); + metadata.origin = ethcore_rpc::Origin::Signer((*session).into()); + metadata + } +} + pub fn start(conf: Configuration, deps: Dependencies) -> Result, String> { if !conf.enabled { Ok(None) @@ -133,7 +145,7 @@ fn do_start(conf: Configuration, deps: Dependencies) -> Result>( +pub fn start_http( addr: &SocketAddr, cors_domains: Option>, allowed_hosts: Option>, panic_handler: Arc, handler: RpcHandler, -) -> Result { + extractor: T, +) -> Result where + M: jsonrpc_core::Metadata, + S: jsonrpc_core::Middleware, + T: HttpMetaExtractor, +{ let cors_domains = cors_domains.map(|domains| { domains.into_iter() @@ -90,6 +95,7 @@ pub fn start_http>( }); ServerBuilder::with_rpc_handler(handler) + .meta_extractor(Arc::new(extractor)) .cors(cors_domains.into()) .allowed_hosts(allowed_hosts.into()) .panic_handler(move || { diff --git a/rpc/src/v1/helpers/requests.rs b/rpc/src/v1/helpers/requests.rs index 993a8c5cd8..4a3a3704d9 100644 --- a/rpc/src/v1/helpers/requests.rs +++ b/rpc/src/v1/helpers/requests.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . use util::{Address, U256, Bytes}; -use v1::types::TransactionCondition; +use v1::types::{Origin, TransactionCondition}; /// Transaction request coming from RPC #[derive(Debug, Clone, Default, Eq, PartialEq, Hash)] @@ -102,6 +102,8 @@ pub struct ConfirmationRequest { pub id: U256, /// Payload to confirm pub payload: ConfirmationPayload, + /// Request origin + pub origin: Origin, } /// Payload to confirm in Trusted Signer diff --git a/rpc/src/v1/helpers/signing_queue.rs b/rpc/src/v1/helpers/signing_queue.rs index f224dcf0cf..36563d0615 100644 --- a/rpc/src/v1/helpers/signing_queue.rs +++ b/rpc/src/v1/helpers/signing_queue.rs @@ -22,8 +22,7 @@ use jsonrpc_core; use util::{Mutex, RwLock, U256, Address}; use ethcore::account_provider::DappId; use v1::helpers::{ConfirmationRequest, ConfirmationPayload}; -use v1::metadata::Metadata; -use v1::types::{ConfirmationResponse, H160 as RpcH160}; +use v1::types::{ConfirmationResponse, H160 as RpcH160, Origin, DappId as RpcDappId}; /// Result that can be returned from JSON RPC. pub type RpcResult = Result; @@ -37,9 +36,9 @@ pub enum DefaultAccount { ForDapp(DappId), } -impl From for DefaultAccount { - fn from(meta: Metadata) -> Self { - DefaultAccount::ForDapp(meta.dapp_id.unwrap_or_default().into()) +impl From for DefaultAccount { + fn from(dapp_id: RpcDappId) -> Self { + DefaultAccount::ForDapp(dapp_id.into()) } } @@ -84,7 +83,7 @@ const QUEUE_LIMIT: usize = 50; pub trait SigningQueue: Send + Sync { /// Add new request to the queue. /// Returns a `ConfirmationPromise` that can be used to await for resolution of given request. - fn add_request(&self, request: ConfirmationPayload) -> Result; + fn add_request(&self, request: ConfirmationPayload, origin: Origin) -> Result; /// Removes a request from the queue. /// Notifies possible token holders that request was rejected. @@ -267,7 +266,7 @@ impl Drop for ConfirmationsQueue { } impl SigningQueue for ConfirmationsQueue { - fn add_request(&self, request: ConfirmationPayload) -> Result { + fn add_request(&self, request: ConfirmationPayload, origin: Origin) -> Result { if self.len() > QUEUE_LIMIT { return Err(QueueAddError::LimitReached); } @@ -290,6 +289,7 @@ impl SigningQueue for ConfirmationsQueue { request: ConfirmationRequest { id: id, payload: request, + origin: origin, }, }); queue.get(&id).map(|token| token.as_promise()).expect("Token was just inserted.") @@ -362,7 +362,7 @@ mod test { // when let q = queue.clone(); let handle = thread::spawn(move || { - let v = q.add_request(request).unwrap(); + let v = q.add_request(request, Default::default()).unwrap(); let (tx, rx) = mpsc::channel(); v.wait_for_result(move |res| { tx.send(res).unwrap(); @@ -397,7 +397,7 @@ mod test { *v = Some(notification); }).expect("Should be closed nicely.") }); - queue.add_request(request).unwrap(); + queue.add_request(request, Default::default()).unwrap(); queue.finish(); // then @@ -413,7 +413,7 @@ mod test { let request = request(); // when - queue.add_request(request.clone()).unwrap(); + queue.add_request(request.clone(), Default::default()).unwrap(); let all = queue.requests(); // then diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 7a954ccaf8..d763db8362 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -311,7 +311,7 @@ impl Eth for EthClient where } fn author(&self, meta: Metadata) -> BoxFuture { - let dapp = meta.dapp_id.unwrap_or_default(); + let dapp = meta.dapp_id(); let author = move || { let mut miner = take_weak!(self.miner).author(); @@ -342,7 +342,7 @@ impl Eth for EthClient where } fn accounts(&self, meta: Metadata) -> BoxFuture, Error> { - let dapp = meta.dapp_id.unwrap_or_default(); + let dapp = meta.dapp_id(); let accounts = move || { let accounts = self.dapp_accounts(dapp.into())?; diff --git a/rpc/src/v1/impls/light/eth.rs b/rpc/src/v1/impls/light/eth.rs index 47765bd412..c321657d20 100644 --- a/rpc/src/v1/impls/light/eth.rs +++ b/rpc/src/v1/impls/light/eth.rs @@ -178,7 +178,7 @@ impl Eth for EthClient { } fn accounts(&self, meta: Metadata) -> BoxFuture, Error> { - let dapp: DappId = meta.dapp_id.unwrap_or_default().into(); + let dapp: DappId = meta.dapp_id().into(); let accounts = self.accounts .note_dapp_used(dapp.clone()) diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index 3b22673950..431c34e84f 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -145,7 +145,7 @@ impl Parity for ParityClient where } fn default_account(&self, meta: Self::Metadata) -> BoxFuture { - let dapp_id = meta.dapp_id.unwrap_or_default(); + let dapp_id = meta.dapp_id(); let default_account = move || { Ok(take_weak!(self.accounts) .dapps_addresses(dapp_id.into()) diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 03ce5ffeb6..fba058aee0 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -101,7 +101,7 @@ impl Personal for PersonalClient { let default = match request.from.as_ref() { Some(account) => Ok(account.clone().into()), None => accounts - .default_address(meta.dapp_id.unwrap_or_default().into()) + .default_address(meta.dapp_id().into()) .map_err(|e| errors::account("Cannot find default account.", e)), }; diff --git a/rpc/src/v1/impls/signing.rs b/rpc/src/v1/impls/signing.rs index 6bf2155ed3..9dae40730e 100644 --- a/rpc/src/v1/impls/signing.rs +++ b/rpc/src/v1/impls/signing.rs @@ -38,7 +38,8 @@ use v1::types::{ RichRawTransaction as RpcRichRawTransaction, TransactionRequest as RpcTransactionRequest, ConfirmationPayload as RpcConfirmationPayload, - ConfirmationResponse as RpcConfirmationResponse + ConfirmationResponse as RpcConfirmationResponse, + Origin, }; const MAX_PENDING_DURATION: u64 = 60 * 60; @@ -81,7 +82,7 @@ impl SigningQueueClient { } } - fn dispatch(&self, payload: RpcConfirmationPayload, default_account: DefaultAccount) -> BoxFuture { + fn dispatch(&self, payload: RpcConfirmationPayload, default_account: DefaultAccount, origin: Origin) -> BoxFuture { let accounts = take_weakf!(self.accounts); let default_account = match default_account { DefaultAccount::Provided(acc) => acc, @@ -100,7 +101,7 @@ impl SigningQueueClient { .boxed() } else { future::done( - signer.add_request(payload) + signer.add_request(payload, origin) .map(DispatchResult::Promise) .map_err(|_| errors::request_rejected_limit()) ).boxed() @@ -113,23 +114,26 @@ impl SigningQueueClient { impl ParitySigning for SigningQueueClient { type Metadata = Metadata; - fn post_sign(&self, address: RpcH160, data: RpcBytes) -> BoxFuture, Error> { + fn post_sign(&self, meta: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture, Error> { let pending = self.pending.clone(); - self.dispatch(RpcConfirmationPayload::Signature((address.clone(), data).into()), DefaultAccount::Provided(address.into())) - .map(move |result| match result { - DispatchResult::Value(v) => RpcEither::Or(v), - DispatchResult::Promise(promise) => { - let id = promise.id(); - pending.lock().insert(id, promise); - RpcEither::Either(id.into()) - }, - }) - .boxed() + self.dispatch( + RpcConfirmationPayload::Signature((address.clone(), data).into()), + DefaultAccount::Provided(address.into()), + meta.origin + ).map(move |result| match result { + DispatchResult::Value(v) => RpcEither::Or(v), + DispatchResult::Promise(promise) => { + let id = promise.id(); + pending.lock().insert(id, promise); + RpcEither::Either(id.into()) + }, + }) + .boxed() } fn post_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture, Error> { let pending = self.pending.clone(); - self.dispatch(RpcConfirmationPayload::SendTransaction(request), meta.into()) + self.dispatch(RpcConfirmationPayload::SendTransaction(request), meta.dapp_id().into(), meta.origin) .map(move |result| match result { DispatchResult::Value(v) => RpcEither::Or(v), DispatchResult::Promise(promise) => { @@ -156,8 +160,12 @@ impl ParitySigning for SigningQueueClient { res } - fn decrypt_message(&self, address: RpcH160, data: RpcBytes) -> BoxFuture { - let res = self.dispatch(RpcConfirmationPayload::Decrypt((address.clone(), data).into()), address.into()); + fn decrypt_message(&self, meta: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture { + let res = self.dispatch( + RpcConfirmationPayload::Decrypt((address.clone(), data).into()), + address.into(), + meta.origin, + ); let (ready, p) = futures::oneshot(); @@ -181,8 +189,12 @@ impl ParitySigning for SigningQueueClient { impl EthSigning for SigningQueueClient { type Metadata = Metadata; - fn sign(&self, address: RpcH160, data: RpcBytes) -> BoxFuture { - let res = self.dispatch(RpcConfirmationPayload::Signature((address.clone(), data).into()), address.into()); + fn sign(&self, meta: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture { + let res = self.dispatch( + RpcConfirmationPayload::Signature((address.clone(), data).into()), + address.into(), + meta.origin, + ); let (ready, p) = futures::oneshot(); @@ -200,7 +212,11 @@ impl EthSigning for SigningQueueClient { } fn send_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture { - let res = self.dispatch(RpcConfirmationPayload::SendTransaction(request), meta.into()); + let res = self.dispatch( + RpcConfirmationPayload::SendTransaction(request), + meta.dapp_id().into(), + meta.origin, + ); let (ready, p) = futures::oneshot(); @@ -218,7 +234,11 @@ impl EthSigning for SigningQueueClient { } fn sign_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture { - let res = self.dispatch(RpcConfirmationPayload::SignTransaction(request), meta.into()); + let res = self.dispatch( + RpcConfirmationPayload::SignTransaction(request), + meta.dapp_id().into(), + meta.origin, + ); let (ready, p) = futures::oneshot(); diff --git a/rpc/src/v1/impls/signing_unsafe.rs b/rpc/src/v1/impls/signing_unsafe.rs index 333b823f93..b4900f7eca 100644 --- a/rpc/src/v1/impls/signing_unsafe.rs +++ b/rpc/src/v1/impls/signing_unsafe.rs @@ -72,7 +72,7 @@ impl EthSigning for SigningUnsafeClient { type Metadata = Metadata; - fn sign(&self, address: RpcH160, data: RpcBytes) -> BoxFuture { + fn sign(&self, _: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture { self.handle(RpcConfirmationPayload::Signature((address.clone(), data).into()), address.into()) .then(|res| match res { Ok(RpcConfirmationResponse::Signature(signature)) => Ok(signature), @@ -83,7 +83,7 @@ impl EthSigning for SigningUnsafeClient } fn send_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture { - self.handle(RpcConfirmationPayload::SendTransaction(request), meta.into()) + self.handle(RpcConfirmationPayload::SendTransaction(request), meta.dapp_id().into()) .then(|res| match res { Ok(RpcConfirmationResponse::SendTransaction(hash)) => Ok(hash), Err(e) => Err(e), @@ -93,7 +93,7 @@ impl EthSigning for SigningUnsafeClient } fn sign_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture { - self.handle(RpcConfirmationPayload::SignTransaction(request), meta.into()) + self.handle(RpcConfirmationPayload::SignTransaction(request), meta.dapp_id().into()) .then(|res| match res { Ok(RpcConfirmationResponse::SignTransaction(tx)) => Ok(tx), Err(e) => Err(e), @@ -106,7 +106,7 @@ impl EthSigning for SigningUnsafeClient impl ParitySigning for SigningUnsafeClient { type Metadata = Metadata; - fn decrypt_message(&self, address: RpcH160, data: RpcBytes) -> BoxFuture { + fn decrypt_message(&self, _: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture { self.handle(RpcConfirmationPayload::Decrypt((address.clone(), data).into()), address.into()) .then(|res| match res { Ok(RpcConfirmationResponse::Decrypt(data)) => Ok(data), @@ -116,7 +116,7 @@ impl ParitySigning for SigningUnsafeClient { .boxed() } - fn post_sign(&self, _: RpcH160, _: RpcBytes) -> BoxFuture, Error> { + fn post_sign(&self, _: Metadata, _: RpcH160, _: RpcBytes) -> BoxFuture, Error> { // We don't support this in non-signer mode. future::err(errors::signer_disabled()).boxed() } diff --git a/rpc/src/v1/metadata.rs b/rpc/src/v1/metadata.rs index e3c31252ca..26c79d9764 100644 --- a/rpc/src/v1/metadata.rs +++ b/rpc/src/v1/metadata.rs @@ -16,33 +16,22 @@ use jsonrpc_core; +use v1::types::{DappId, Origin}; + /// RPC methods metadata. #[derive(Clone, Default, Debug, PartialEq)] pub struct Metadata { - /// Current dapplication identifier - pub dapp_id: Option, /// Request origin pub origin: Origin, } -/// RPC request origin -#[derive(Clone, Debug, PartialEq)] -pub enum Origin { - /// RPC server - Rpc, - /// Dapps server - Dapps, - /// IPC server - Ipc, - /// Signer - Signer, - /// Unknown - Unknown, -} - -impl Default for Origin { - fn default() -> Self { - Origin::Unknown +impl Metadata { + /// Get + pub fn dapp_id(&self) -> DappId { + match self.origin { + Origin::Dapps(ref dapp_id) => dapp_id.clone(), + _ => DappId::default(), + } } } diff --git a/rpc/src/v1/mod.rs b/rpc/src/v1/mod.rs index c69acbea36..bd06562191 100644 --- a/rpc/src/v1/mod.rs +++ b/rpc/src/v1/mod.rs @@ -61,4 +61,5 @@ pub mod types; pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Net, Parity, ParityAccounts, ParitySet, ParitySigning, Signer, Personal, Traces, Rpc}; pub use self::impls::*; pub use self::helpers::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, block_import, informant, dispatch}; -pub use self::metadata::{Metadata, Origin}; +pub use self::metadata::Metadata; +pub use self::types::Origin; diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index dd5f63347f..3b668b0306 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -37,6 +37,7 @@ use v1::{Eth, EthClient, EthClientOptions, EthFilter, EthFilterClient, EthSignin use v1::helpers::dispatch::FullDispatcher; use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService, TestSnapshotService}; use v1::metadata::Metadata; +use v1::types::Origin; fn blockchain_client() -> Arc { let client = TestBlockChainClient::new(); @@ -387,7 +388,7 @@ fn rpc_eth_accounts() { let request = r#"{"jsonrpc": "2.0", "method": "eth_accounts", "params": [], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":["0x000000000000000000000000000000000000000a"],"id":1}"#; let mut meta = Metadata::default(); - meta.dapp_id = Some("app1".into()); + meta.origin = Origin::Dapps("app1".into()); assert_eq!((*tester.io).handle_request_sync(request, meta), Some(response.to_owned())); } diff --git a/rpc/src/v1/tests/mocked/signer.rs b/rpc/src/v1/tests/mocked/signer.rs index 4bec863de0..06b75d911b 100644 --- a/rpc/src/v1/tests/mocked/signer.rs +++ b/rpc/src/v1/tests/mocked/signer.rs @@ -25,7 +25,7 @@ use rlp::encode; use serde_json; use jsonrpc_core::IoHandler; -use v1::{SignerClient, Signer}; +use v1::{SignerClient, Signer, Origin}; use v1::metadata::Metadata; use v1::tests::helpers::TestMinerService; use v1::helpers::{SigningQueue, SignerService, FilledTransactionRequest, ConfirmationPayload}; @@ -88,15 +88,15 @@ fn should_return_list_of_items_to_confirm() { data: vec![], nonce: None, condition: None, - })).unwrap(); - tester.signer.add_request(ConfirmationPayload::Signature(1.into(), vec![5].into())).unwrap(); + }), Origin::Dapps("http://parity.io".into())).unwrap(); + tester.signer.add_request(ConfirmationPayload::Signature(1.into(), vec![5].into()), Origin::Unknown).unwrap(); // when let request = r#"{"jsonrpc":"2.0","method":"signer_requestsToConfirm","params":[],"id":1}"#; let response = concat!( r#"{"jsonrpc":"2.0","result":["#, - r#"{"id":"0x1","payload":{"sendTransaction":{"condition":null,"data":"0x","from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x1"}}},"#, - r#"{"id":"0x2","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","data":"0x05"}}}"#, + r#"{"id":"0x1","origin":{"dapp":"http://parity.io"},"payload":{"sendTransaction":{"condition":null,"data":"0x","from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x1"}}},"#, + r#"{"id":"0x2","origin":"unknown","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","data":"0x05"}}}"#, r#"],"id":1}"# ); @@ -119,7 +119,7 @@ fn should_reject_transaction_from_queue_without_dispatching() { data: vec![], nonce: None, condition: None, - })).unwrap(); + }), Origin::Unknown).unwrap(); assert_eq!(tester.signer.requests().len(), 1); // when @@ -146,7 +146,7 @@ fn should_not_remove_transaction_if_password_is_invalid() { data: vec![], nonce: None, condition: None, - })).unwrap(); + }), Origin::Unknown).unwrap(); assert_eq!(tester.signer.requests().len(), 1); // when @@ -162,7 +162,7 @@ fn should_not_remove_transaction_if_password_is_invalid() { fn should_not_remove_sign_if_password_is_invalid() { // given let tester = signer_tester(); - tester.signer.add_request(ConfirmationPayload::Signature(0.into(), vec![5].into())).unwrap(); + tester.signer.add_request(ConfirmationPayload::Signature(0.into(), vec![5].into()), Origin::Unknown).unwrap(); assert_eq!(tester.signer.requests().len(), 1); // when @@ -190,7 +190,7 @@ fn should_confirm_transaction_and_dispatch() { data: vec![], nonce: None, condition: None, - })).unwrap(); + }), Origin::Unknown).unwrap(); let t = Transaction { nonce: U256::zero(), @@ -236,7 +236,7 @@ fn should_alter_the_sender_and_nonce() { data: vec![], nonce: Some(10.into()), condition: None, - })).unwrap(); + }), Origin::Unknown).unwrap(); let t = Transaction { nonce: U256::zero(), @@ -286,7 +286,7 @@ fn should_confirm_transaction_with_token() { data: vec![], nonce: None, condition: None, - })).unwrap(); + }), Origin::Unknown).unwrap(); let t = Transaction { nonce: U256::zero(), @@ -335,7 +335,7 @@ fn should_confirm_transaction_with_rlp() { data: vec![], nonce: None, condition: None, - })).unwrap(); + }), Origin::Unknown).unwrap(); let t = Transaction { nonce: U256::zero(), @@ -383,7 +383,7 @@ fn should_return_error_when_sender_does_not_match() { data: vec![], nonce: None, condition: None, - })).unwrap(); + }), Origin::Unknown).unwrap(); let t = Transaction { nonce: U256::zero(), diff --git a/rpc/src/v1/traits/eth_signing.rs b/rpc/src/v1/traits/eth_signing.rs index 51b7c4efce..e3b9c9b203 100644 --- a/rpc/src/v1/traits/eth_signing.rs +++ b/rpc/src/v1/traits/eth_signing.rs @@ -27,8 +27,8 @@ build_rpc_trait! { type Metadata; /// Signs the hash of data with given address signature. - #[rpc(async, name = "eth_sign")] - fn sign(&self, H160, Bytes) -> BoxFuture; + #[rpc(meta, name = "eth_sign")] + fn sign(&self, Self::Metadata, H160, Bytes) -> BoxFuture; /// Sends transaction; will block waiting for signer to return the /// transaction hash. diff --git a/rpc/src/v1/traits/parity_signing.rs b/rpc/src/v1/traits/parity_signing.rs index 3370bc2596..372c31fb23 100644 --- a/rpc/src/v1/traits/parity_signing.rs +++ b/rpc/src/v1/traits/parity_signing.rs @@ -27,8 +27,8 @@ build_rpc_trait! { /// Posts sign request asynchronously. /// Will return a confirmation ID for later use with check_transaction. - #[rpc(async, name = "parity_postSign")] - fn post_sign(&self, H160, Bytes) -> BoxFuture, Error>; + #[rpc(meta, name = "parity_postSign")] + fn post_sign(&self, Self::Metadata, H160, Bytes) -> BoxFuture, Error>; /// Posts transaction asynchronously. /// Will return a transaction ID for later use with check_transaction. @@ -42,7 +42,7 @@ build_rpc_trait! { /// Decrypt some ECIES-encrypted message. /// First parameter is the address with which it is encrypted, second is the ciphertext. - #[rpc(async, name = "parity_decryptMessage")] - fn decrypt_message(&self, H160, Bytes) -> BoxFuture; + #[rpc(meta, name = "parity_decryptMessage")] + fn decrypt_message(&self, Self::Metadata, H160, Bytes) -> BoxFuture; } } diff --git a/rpc/src/v1/types/confirmations.rs b/rpc/src/v1/types/confirmations.rs index 7a05ee914a..dd44e57503 100644 --- a/rpc/src/v1/types/confirmations.rs +++ b/rpc/src/v1/types/confirmations.rs @@ -21,16 +21,19 @@ use serde::{Serialize, Serializer}; use util::log::Colour; use util::bytes::ToPretty; -use v1::types::{U256, TransactionRequest, RichRawTransaction, H160, H256, H520, Bytes, TransactionCondition}; +use v1::types::{U256, TransactionRequest, RichRawTransaction, H160, H256, H520, Bytes, TransactionCondition, Origin}; use v1::helpers; /// Confirmation waiting in a queue #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct ConfirmationRequest { /// Id of this confirmation pub id: U256, /// Payload pub payload: ConfirmationPayload, + /// Request origin + pub origin: Origin, } impl From for ConfirmationRequest { @@ -38,13 +41,14 @@ impl From for ConfirmationRequest { ConfirmationRequest { id: c.id.into(), payload: c.payload.into(), + origin: c.origin, } } } impl fmt::Display for ConfirmationRequest { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "#{}: {}", self.id, self.payload) + write!(f, "#{}: {} coming from {}", self.id, self.payload, self.origin) } } @@ -61,6 +65,7 @@ impl fmt::Display for ConfirmationPayload { /// Sign request #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct SignRequest { /// Address pub address: H160, @@ -90,6 +95,7 @@ impl fmt::Display for SignRequest { /// Decrypt request #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct DecryptRequest { /// Address pub address: H160, @@ -153,6 +159,7 @@ pub struct ConfirmationResponseWithToken { /// Confirmation payload, i.e. the thing to be confirmed #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub enum ConfirmationPayload { /// Send Transaction #[serde(rename="sendTransaction")] @@ -249,11 +256,12 @@ mod tests { let request = helpers::ConfirmationRequest { id: 15.into(), payload: helpers::ConfirmationPayload::Signature(1.into(), vec![5].into()), + origin: Origin::Rpc("test service".into()), }; // when let res = serde_json::to_string(&ConfirmationRequest::from(request)); - let expected = r#"{"id":"0xf","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","data":"0x05"}}}"#; + let expected = r#"{"id":"0xf","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","data":"0x05"}},"origin":{"rpc":"test service"}}"#; // then assert_eq!(res.unwrap(), expected.to_owned()); @@ -275,11 +283,12 @@ mod tests { nonce: Some(1.into()), condition: None, }), + origin: Origin::Signer(5.into()), }; // when let res = serde_json::to_string(&ConfirmationRequest::from(request)); - let expected = r#"{"id":"0xf","payload":{"sendTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","condition":null}}}"#; + let expected = r#"{"id":"0xf","payload":{"sendTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","condition":null}},"origin":{"signer":"0x0000000000000000000000000000000000000000000000000000000000000005"}}"#; // then assert_eq!(res.unwrap(), expected.to_owned()); @@ -301,11 +310,12 @@ mod tests { nonce: Some(1.into()), condition: None, }), + origin: Origin::Dapps("http://parity.io".into()), }; // when let res = serde_json::to_string(&ConfirmationRequest::from(request)); - let expected = r#"{"id":"0xf","payload":{"signTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","condition":null}}}"#; + let expected = r#"{"id":"0xf","payload":{"signTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","condition":null}},"origin":{"dapp":"http://parity.io"}}"#; // then assert_eq!(res.unwrap(), expected.to_owned()); @@ -319,11 +329,12 @@ mod tests { payload: helpers::ConfirmationPayload::Decrypt( 10.into(), vec![1, 2, 3].into(), ), + origin: Default::default(), }; // when let res = serde_json::to_string(&ConfirmationRequest::from(request)); - let expected = r#"{"id":"0xf","payload":{"decrypt":{"address":"0x000000000000000000000000000000000000000a","msg":"0x010203"}}}"#; + let expected = r#"{"id":"0xf","payload":{"decrypt":{"address":"0x000000000000000000000000000000000000000a","msg":"0x010203"}},"origin":"unknown"}"#; // then assert_eq!(res.unwrap(), expected.to_owned()); diff --git a/rpc/src/v1/types/dapp_id.rs b/rpc/src/v1/types/dapp_id.rs deleted file mode 100644 index e594bab873..0000000000 --- a/rpc/src/v1/types/dapp_id.rs +++ /dev/null @@ -1,80 +0,0 @@ -// 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 . - -//! Dapp Id type - -use ethcore::account_provider::DappId as EthDappId; - -/// Dapplication Internal Id -#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)] -pub struct DappId(pub String); - -impl Into for DappId { - fn into(self) -> String { - self.0 - } -} - -impl From for DappId { - fn from(s: String) -> Self { - DappId(s) - } -} - -impl From for DappId { - fn from(id: EthDappId) -> Self { - DappId(id.into()) - } -} - -impl Into for DappId { - fn into(self) -> EthDappId { - Into::::into(self).into() - } -} - -#[cfg(test)] -mod tests { - - use serde_json; - use super::DappId; - - #[test] - fn should_serialize_dapp_id() { - // given - let id = DappId("testapp".into()); - - // when - let res = serde_json::to_string(&id).unwrap(); - - // then - assert_eq!(res, r#""testapp""#); - } - - #[test] - fn should_deserialize_dapp_id() { - // given - let id = r#""testapp""#; - - // when - let res: DappId = serde_json::from_str(id).unwrap(); - - // then - assert_eq!(res, DappId("testapp".into())); - } - - -} diff --git a/rpc/src/v1/types/hash.rs b/rpc/src/v1/types/hash.rs index 0c86726019..c96a3433bc 100644 --- a/rpc/src/v1/types/hash.rs +++ b/rpc/src/v1/types/hash.rs @@ -36,11 +36,18 @@ macro_rules! impl_hash { } impl fmt::Debug for $name { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0.to_hex()) } } + impl fmt::Display for $name { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let hex = self.0.to_hex(); + write!(f, "{}..{}", &hex[0..2], &hex[$size-2..$size]) + } + } + impl From for $name where $other: From { fn from(o: T) -> Self { $name($other::from(o).0) diff --git a/rpc/src/v1/types/mod.rs b/rpc/src/v1/types/mod.rs index e32acc8ca6..83223d947e 100644 --- a/rpc/src/v1/types/mod.rs +++ b/rpc/src/v1/types/mod.rs @@ -18,29 +18,30 @@ //! RPC types mod account_info; -mod bytes; mod block; mod block_number; +mod bytes; mod call_request; mod confirmations; -mod dapp_id; +mod consensus_status; mod filter; mod hash; +mod histogram; mod index; mod log; -mod sync; -mod transaction; -mod transaction_request; -mod transaction_condition; +mod provenance; mod receipt; mod rpc_settings; +mod sync; mod trace; mod trace_filter; +mod transaction; +mod transaction_request; +mod transaction_condition; mod uint; mod work; -mod histogram; -mod consensus_status; +pub use self::account_info::{AccountInfo, HwAccountInfo}; pub use self::bytes::Bytes; pub use self::block::{RichBlock, Block, BlockTransactions}; pub use self::block_number::BlockNumber; @@ -49,24 +50,24 @@ pub use self::confirmations::{ ConfirmationPayload, ConfirmationRequest, ConfirmationResponse, ConfirmationResponseWithToken, TransactionModification, SignRequest, DecryptRequest, Either }; -pub use self::dapp_id::DappId; +pub use self::consensus_status::*; pub use self::filter::{Filter, FilterChanges}; pub use self::hash::{H64, H160, H256, H512, H520, H2048}; +pub use self::histogram::Histogram; pub use self::index::Index; pub use self::log::Log; +pub use self::provenance::{Origin, DappId}; +pub use self::receipt::Receipt; +pub use self::rpc_settings::RpcSettings; pub use self::sync::{ SyncStatus, SyncInfo, Peers, PeerInfo, PeerNetworkInfo, PeerProtocolsInfo, TransactionStats, ChainStatus, EthProtocolInfo, LesProtocolInfo, }; +pub use self::trace::{LocalizedTrace, TraceResults}; +pub use self::trace_filter::TraceFilter; pub use self::transaction::{Transaction, RichRawTransaction, LocalTransactionStatus}; pub use self::transaction_request::TransactionRequest; pub use self::transaction_condition::TransactionCondition; -pub use self::receipt::Receipt; -pub use self::rpc_settings::RpcSettings; -pub use self::trace::{LocalizedTrace, TraceResults}; -pub use self::trace_filter::TraceFilter; pub use self::uint::{U128, U256}; pub use self::work::Work; -pub use self::histogram::Histogram; -pub use self::consensus_status::*; -pub use self::account_info::{AccountInfo, HwAccountInfo}; + diff --git a/rpc/src/v1/types/provenance.rs b/rpc/src/v1/types/provenance.rs new file mode 100644 index 0000000000..1e014dd5ff --- /dev/null +++ b/rpc/src/v1/types/provenance.rs @@ -0,0 +1,154 @@ +// 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 . + +//! Request Provenance + +use std::fmt; +use ethcore::account_provider::DappId as EthDappId; +use v1::types::H256; + +/// RPC request origin +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub enum Origin { + /// RPC server (includes request origin) + #[serde(rename="rpc")] + Rpc(String), + /// Dapps server (includes DappId) + #[serde(rename="dapp")] + Dapps(DappId), + /// IPC server (includes session hash) + #[serde(rename="ipc")] + Ipc(H256), + /// Signer (includes session hash) + #[serde(rename="signer")] + Signer(H256), + /// Unknown + #[serde(rename="unknown")] + Unknown, +} + +impl Default for Origin { + fn default() -> Self { + Origin::Unknown + } +} + +impl fmt::Display for Origin { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Origin::Rpc(ref origin) => write!(f, "RPC (service: {})", origin), + Origin::Dapps(ref origin) => write!(f, "Dapp {}", origin), + Origin::Ipc(ref session) => write!(f, "IPC (session: {})", session), + Origin::Signer(ref session) => write!(f, "UI (session: {})", session), + Origin::Unknown => write!(f, "unknown origin"), + } + } +} + +/// Dapplication Internal Id +#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)] +pub struct DappId(pub String); + +impl fmt::Display for DappId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl Into for DappId { + fn into(self) -> String { + self.0 + } +} + +impl From for DappId { + fn from(s: String) -> Self { + DappId(s) + } +} + +impl<'a> From<&'a str> for DappId { + fn from(s: &'a str) -> Self { + DappId(s.to_owned()) + } +} + +impl From for DappId { + fn from(id: EthDappId) -> Self { + DappId(id.into()) + } +} + +impl Into for DappId { + fn into(self) -> EthDappId { + Into::::into(self).into() + } +} + +#[cfg(test)] +mod tests { + use serde_json; + use super::{DappId, Origin}; + + #[test] + fn should_serialize_origin() { + // given + let o1 = Origin::Rpc("test service".into()); + let o2 = Origin::Dapps("http://parity.io".into()); + let o3 = Origin::Ipc(5.into()); + let o4 = Origin::Signer(10.into()); + let o5 = Origin::Unknown; + + // when + let res1 = serde_json::to_string(&o1).unwrap(); + let res2 = serde_json::to_string(&o2).unwrap(); + let res3 = serde_json::to_string(&o3).unwrap(); + let res4 = serde_json::to_string(&o4).unwrap(); + let res5 = serde_json::to_string(&o5).unwrap(); + + // then + assert_eq!(res1, r#"{"rpc":"test service"}"#); + assert_eq!(res2, r#"{"dapp":"http://parity.io"}"#); + assert_eq!(res3, r#"{"ipc":"0x0000000000000000000000000000000000000000000000000000000000000005"}"#); + assert_eq!(res4, r#"{"signer":"0x000000000000000000000000000000000000000000000000000000000000000a"}"#); + assert_eq!(res5, r#""unknown""#); + } + + #[test] + fn should_serialize_dapp_id() { + // given + let id = DappId("testapp".into()); + + // when + let res = serde_json::to_string(&id).unwrap(); + + // then + assert_eq!(res, r#""testapp""#); + } + + #[test] + fn should_deserialize_dapp_id() { + // given + let id = r#""testapp""#; + + // when + let res: DappId = serde_json::from_str(id).unwrap(); + + // then + assert_eq!(res, DappId("testapp".into())); + } +} diff --git a/rpc/src/v1/types/transaction_condition.rs b/rpc/src/v1/types/transaction_condition.rs index 2f530f6861..1682f2dd94 100644 --- a/rpc/src/v1/types/transaction_condition.rs +++ b/rpc/src/v1/types/transaction_condition.rs @@ -18,6 +18,7 @@ use ethcore; /// Represents condition on minimum block number or block timestamp. #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub enum TransactionCondition { /// Valid at this minimum block number. #[serde(rename="block")] diff --git a/rpc_client/src/lib.rs b/rpc_client/src/lib.rs index 87dd3a6ee3..7bc2652506 100644 --- a/rpc_client/src/lib.rs +++ b/rpc_client/src/lib.rs @@ -18,6 +18,9 @@ extern crate log; #[cfg(test)] mod tests { + #[macro_use] + extern crate matches; + use futures::Future; use std::path::PathBuf; use client::{Rpc, RpcError}; diff --git a/signer/src/ws_server/mod.rs b/signer/src/ws_server/mod.rs index 7fc046f491..b799b0f66c 100644 --- a/signer/src/ws_server/mod.rs +++ b/signer/src/ws_server/mod.rs @@ -33,6 +33,8 @@ use rpc::informant::RpcStats; mod session; +pub use self::session::MetaExtractor; + /// Signer startup error #[derive(Debug)] pub enum ServerError { @@ -51,6 +53,11 @@ impl From for ServerError { } } +/// Dummy metadata extractor +#[derive(Clone)] +pub struct NoopExtractor; +impl session::MetaExtractor for NoopExtractor {} + /// Builder for `WebSockets` server pub struct ServerBuilder { queue: Arc, @@ -86,6 +93,17 @@ impl ServerBuilder { /// Starts a new `WebSocket` server in separate thread. /// Returns a `Server` handle which closes the server when droped. pub fn start>(self, addr: SocketAddr, handler: RpcHandler) -> Result { + self.start_with_extractor(addr, handler, NoopExtractor) + } + + /// Starts a new `WebSocket` server in separate thread. + /// Returns a `Server` handle which closes the server when droped. + pub fn start_with_extractor, T: session::MetaExtractor>( + self, + addr: SocketAddr, + handler: RpcHandler, + meta_extractor: T, + ) -> Result { Server::start( addr, handler, @@ -93,8 +111,10 @@ impl ServerBuilder { self.authcodes_path, self.skip_origin_validation, self.stats, + meta_extractor, ) } + } /// `WebSockets` server implementation. @@ -114,13 +134,14 @@ impl Server { /// Starts a new `WebSocket` server in separate thread. /// Returns a `Server` handle which closes the server when droped. - fn start>( + fn start, T: session::MetaExtractor>( addr: SocketAddr, handler: RpcHandler, queue: Arc, authcodes_path: PathBuf, skip_origin_validation: bool, stats: Option>, + meta_extractor: T, ) -> Result { let config = { let mut config = ws::Settings::default(); @@ -135,7 +156,7 @@ impl Server { let origin = format!("{}", addr); let port = addr.port(); let ws = ws::Builder::new().with_settings(config).build( - session::Factory::new(handler, origin, port, authcodes_path, skip_origin_validation, stats) + session::Factory::new(handler, origin, port, authcodes_path, skip_origin_validation, stats, meta_extractor) )?; let panic_handler = PanicHandler::new_in_arc(); diff --git a/signer/src/ws_server/session.rs b/signer/src/ws_server/session.rs index f19e86215c..5194855abd 100644 --- a/signer/src/ws_server/session.rs +++ b/signer/src/ws_server/session.rs @@ -16,15 +16,16 @@ //! Session handlers factory. -use ws; -use authcode_store::AuthCodes; use std::path::{PathBuf, Path}; use std::sync::Arc; use std::str::FromStr; + +use authcode_store::AuthCodes; use jsonrpc_core::{Metadata, Middleware}; use jsonrpc_core::reactor::RpcHandler; use rpc::informant::RpcStats; use util::{H256, version}; +use ws; #[cfg(feature = "parity-ui")] mod ui { @@ -78,35 +79,39 @@ fn origin_is_allowed(self_origin: &str, header: Option<&[u8]>) -> bool { } } -fn auth_is_valid(codes_path: &Path, protocols: ws::Result>) -> bool { +fn auth_token_hash(codes_path: &Path, protocols: ws::Result>) -> Option { match protocols { Ok(ref protocols) if protocols.len() == 1 => { - protocols.iter().any(|protocol| { - let mut split = protocol.split('_'); - let auth = split.next().and_then(|v| H256::from_str(v).ok()); - let time = split.next().and_then(|v| u64::from_str_radix(v, 10).ok()); - - if let (Some(auth), Some(time)) = (auth, time) { - // Check if the code is valid - AuthCodes::from_file(codes_path) - .map(|mut codes| { - // remove old tokens - codes.clear_garbage(); - - let res = codes.is_valid(&auth, time); - // make sure to save back authcodes - it might have been modified - if codes.to_file(codes_path).is_err() { - warn!(target: "signer", "Couldn't save authorization codes to file."); - } - res - }) - .unwrap_or(false) - } else { - false - } - }) + let protocol = protocols[0]; + let mut split = protocol.split('_'); + let auth = split.next().and_then(|v| H256::from_str(v).ok()); + let time = split.next().and_then(|v| u64::from_str_radix(v, 10).ok()); + + if let (Some(auth), Some(time)) = (auth, time) { + // Check if the code is valid + AuthCodes::from_file(codes_path) + .ok() + .and_then(|mut codes| { + // remove old tokens + codes.clear_garbage(); + + let res = codes.is_valid(&auth, time); + // make sure to save back authcodes - it might have been modified + if codes.to_file(codes_path).is_err() { + warn!(target: "signer", "Couldn't save authorization codes to file."); + } + + if res { + Some(auth) + } else { + None + } + }) + } else { + None + } }, - _ => false + _ => None, } } @@ -125,7 +130,16 @@ fn add_headers(mut response: ws::Response, mime: &str) -> ws::Response { response } -pub struct Session> { +/// Metadata extractor from session data. +pub trait MetaExtractor: Send + Clone + 'static { + /// Extract metadata for given session + fn extract_metadata(&self, _session_id: &H256) -> M { + Default::default() + } +} + +pub struct Session, T> { + session_id: H256, out: ws::Sender, skip_origin_validation: bool, self_origin: String, @@ -134,16 +148,16 @@ pub struct Session> { handler: RpcHandler, file_handler: Arc, stats: Option>, + meta_extractor: T, } -impl> Drop for Session { +impl, T> Drop for Session { fn drop(&mut self) { self.stats.as_ref().map(|stats| stats.close_session()); } } -impl> ws::Handler for Session { - #[cfg_attr(feature="dev", allow(collapsible_if))] +impl, T: MetaExtractor> ws::Handler for Session { fn on_request(&mut self, req: &ws::Request) -> ws::Result<(ws::Response)> { trace!(target: "signer", "Handling request: {:?}", req); @@ -186,9 +200,15 @@ impl> ws::Handler for Session { // (styles file skips origin validation, so make sure to prevent WS connections on this resource) if req.header("sec-websocket-key").is_some() && !is_styles_file { // Check authorization - if !auth_is_valid(&self.authcodes_path, req.protocols()) { - info!(target: "signer", "Unauthorized connection to Signer API blocked."); - return Ok(error(ErrorType::Forbidden, "Not Authorized", "Request to this API was not authorized.", None)); + let auth_token_hash = auth_token_hash(&self.authcodes_path, req.protocols()); + match auth_token_hash { + None => { + info!(target: "signer", "Unauthorized connection to Signer API blocked."); + return Ok(error(ErrorType::Forbidden, "Not Authorized", "Request to this API was not authorized.", None)); + }, + Some(auth) => { + self.session_id = auth; + }, } let protocols = req.protocols().expect("Existence checked by authorization."); @@ -214,8 +234,8 @@ impl> ws::Handler for Session { fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> { let req = msg.as_text()?; let out = self.out.clone(); - // TODO [ToDr] Extract metadata for PubSub/Session - let metadata = Default::default(); + // TODO [ToDr] Move to on_connect + let metadata = self.meta_extractor.extract_metadata(&self.session_id); self.handler.handle_request(req, metadata, move |response| { if let Some(result) = response { @@ -229,24 +249,26 @@ impl> ws::Handler for Session { } } -pub struct Factory> { +pub struct Factory, T> { handler: RpcHandler, skip_origin_validation: bool, self_origin: String, self_port: u16, authcodes_path: PathBuf, + meta_extractor: T, file_handler: Arc, stats: Option>, } -impl> Factory { +impl, T> Factory { pub fn new( handler: RpcHandler, self_origin: String, - self_port: u16, + self_port: u16, authcodes_path: PathBuf, skip_origin_validation: bool, stats: Option>, + meta_extractor: T, ) -> Self { Factory { handler: handler, @@ -254,25 +276,28 @@ impl> Factory { self_origin: self_origin, self_port: self_port, authcodes_path: authcodes_path, + meta_extractor: meta_extractor, file_handler: Arc::new(ui::Handler::default()), stats: stats, } } } -impl> ws::Factory for Factory { - type Handler = Session; +impl, T: MetaExtractor> ws::Factory for Factory { + type Handler = Session; fn connection_made(&mut self, sender: ws::Sender) -> Self::Handler { self.stats.as_ref().map(|stats| stats.open_session()); Session { + session_id: 0.into(), out: sender, handler: self.handler.clone(), skip_origin_validation: self.skip_origin_validation, self_origin: self.self_origin.clone(), self_port: self.self_port, authcodes_path: self.authcodes_path.clone(), + meta_extractor: self.meta_extractor.clone(), file_handler: self.file_handler.clone(), stats: self.stats.clone(), } -- GitLab From ac27806a439032d89edc5db9c8a41450cde79e59 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Tue, 14 Feb 2017 22:47:34 +0100 Subject: [PATCH 031/246] Close on double-click for Signer Account selection (#4540) * Close on double-click + Optimistic UX (#4525) * PR Gumble + Tests --- js/src/views/ParityBar/accountStore.js | 23 +++++++++++++++++++-- js/src/views/ParityBar/accountStore.spec.js | 10 +++++++++ js/src/views/ParityBar/parityBar.js | 17 ++++++++------- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/js/src/views/ParityBar/accountStore.js b/js/src/views/ParityBar/accountStore.js index b53f40dd28..f1d704d505 100644 --- a/js/src/views/ParityBar/accountStore.js +++ b/js/src/views/ParityBar/accountStore.js @@ -24,7 +24,9 @@ export default class AccountStore { constructor (api) { this._api = api; - this.loadAccounts(); + this.loadDefaultAccount() + .then(() => this.loadAccounts()); + this.subscribeDefaultAccount(); } @@ -33,7 +35,15 @@ export default class AccountStore { } @action setDefaultAccount = (defaultAccount) => { - this.defaultAccount = defaultAccount; + transaction(() => { + this.accounts = this.accounts.map((account) => { + account.default = account.address === defaultAccount; + + return account; + }); + + this.defaultAccount = defaultAccount; + }); } @action setLoading = (isLoading) => { @@ -47,6 +57,9 @@ export default class AccountStore { .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); + return this._api.parity .setNewDappsWhitelist(accounts) .catch((error) => { @@ -54,6 +67,12 @@ export default class AccountStore { }); } + loadDefaultAccount () { + return this._api.parity + .defaultAccount() + .then((address) => this.setDefaultAccount(address)); + } + loadAccounts () { this.setLoading(true); diff --git a/js/src/views/ParityBar/accountStore.spec.js b/js/src/views/ParityBar/accountStore.spec.js index 6dd2198063..c13c62aa91 100644 --- a/js/src/views/ParityBar/accountStore.spec.js +++ b/js/src/views/ParityBar/accountStore.spec.js @@ -89,6 +89,16 @@ describe('views/ParityBar/AccountStore', () => { }); }); + describe('loadDefaultAccount', () => { + beforeEach(() => { + return store.loadDefaultAccount(); + }); + + it('load and set the default account', () => { + expect(store.defaultAccount).to.equal(ACCOUNT_DEFAULT); + }); + }); + describe('makeDefaultAccount', () => { beforeEach(() => { return store.makeDefaultAccount(ACCOUNT_NEW); diff --git a/js/src/views/ParityBar/parityBar.js b/js/src/views/ParityBar/parityBar.js index ac251d0a36..fe737a8732 100644 --- a/js/src/views/ParityBar/parityBar.js +++ b/js/src/views/ParityBar/parityBar.js @@ -343,15 +343,22 @@ class ParityBar extends Component { } renderAccount = (account) => { - const onMakeDefault = () => { + const makeDefaultAccount = () => { + return this.accountStore + .makeDefaultAccount(account.address) + .then(() => this.accountStore.loadAccounts()); + }; + + const onDoubleClick = () => { this.toggleAccountsDisplay(); - this.accountStore.makeDefaultAccount(account.address); + makeDefaultAccount(); }; return (
{ -- GitLab From 812017f9b3b3dccac609e788a72c7b8bc32fb91e Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Tue, 14 Feb 2017 22:00:39 +0000 Subject: [PATCH 032/246] [ci skip] js-precompiled 20170214-215513 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index faed42dcd6..949b3fba22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1606,7 +1606,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#ec5da356aeb6b7598a27bc07b535e5795f0c82be" +source = "git+https://github.com/ethcore/js-precompiled.git#acdd023610ac819b69b14d6464879497aff53c85" 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 d09483c0e3..81610dccfe 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.84", + "version": "0.3.85", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", -- GitLab From efe76d7004974e46ac2902711c8a80ed7c286ec0 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 15 Feb 2017 11:56:51 +0100 Subject: [PATCH 033/246] Tooltips with react-intl (#4549) * Tooltips support intl strings * FormattedMessage for strings to Tooltip * Fix TabBar tooltip display * r after o (PR comment) --- js/src/ui/Tooltips/Tooltip/tooltip.js | 68 ++++++++++++----- js/src/ui/Tooltips/Tooltip/tooltip.spec.js | 68 +++++++++++++++++ js/src/ui/Tooltips/index.js | 7 +- js/src/ui/Tooltips/tooltips.js | 5 +- js/src/ui/Tooltips/tooltips.spec.js | 76 +++++++++++++++++++ js/src/views/Accounts/accounts.js | 15 +++- js/src/views/Application/TabBar/Tab/tab.js | 7 +- .../views/Application/TabBar/Tab/tabs.spec.js | 1 - js/src/views/Application/TabBar/tabBar.css | 4 +- js/src/views/Application/TabBar/tabBar.js | 23 +++--- 10 files changed, 226 insertions(+), 48 deletions(-) create mode 100644 js/src/ui/Tooltips/Tooltip/tooltip.spec.js create mode 100644 js/src/ui/Tooltips/tooltips.spec.js diff --git a/js/src/ui/Tooltips/Tooltip/tooltip.js b/js/src/ui/Tooltips/Tooltip/tooltip.js index a8faf20170..af3b5e0b33 100644 --- a/js/src/ui/Tooltips/Tooltip/tooltip.js +++ b/js/src/ui/Tooltips/Tooltip/tooltip.js @@ -15,12 +15,13 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { FlatButton } from 'material-ui'; -import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; -import ContentClear from 'material-ui/svg-icons/content/clear'; -import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; + +import { CancelIcon, DoneIcon, NextIcon } from '~/ui/Icons'; +import { nodeOrStringProptype } from '~/util/proptypes'; import { newTooltip, nextTooltip, closeTooltips } from '../actions'; @@ -30,15 +31,15 @@ let tooltipId = 0; class Tooltip extends Component { static propTypes = { - title: PropTypes.string, - text: PropTypes.string, - right: PropTypes.bool, + className: PropTypes.string, currentId: PropTypes.number, maxId: PropTypes.number, - className: PropTypes.string, onNewTooltip: PropTypes.func, onNextTooltip: PropTypes.func, - onCloseTooltips: PropTypes.func + onCloseTooltips: PropTypes.func, + right: PropTypes.bool, + text: nodeOrStringProptype(), + title: nodeOrStringProptype() } state = { @@ -54,8 +55,7 @@ class Tooltip extends Component { render () { const { id } = this.state; - const { className, currentId, maxId, right, onCloseTooltips, onNextTooltip } = this.props; - const classes = `${styles.box} ${right ? styles.arrowRight : styles.arrowLeft} ${className}`; + const { className, currentId, maxId, right, onCloseTooltips, onNextTooltip, text, title } = this.props; if (id !== currentId) { return null; @@ -64,32 +64,57 @@ class Tooltip extends Component { const buttons = id !== maxId ? [ } key='skipButton' - icon={ } - label='Skip' + label={ + + } onTouchTap={ onCloseTooltips } />, } key='nextButton' - icon={ } - label='Next' + label={ + + } onTouchTap={ onNextTooltip } /> ] : ( } - label='Done' + icon={ } + label={ + + } onTouchTap={ onCloseTooltips } /> ); return ( -
+
- { this.props.title } + { title }
- { this.props.text } + { text }
{ buttons } @@ -102,7 +127,10 @@ class Tooltip extends Component { function mapStateToProps (state) { const { currentId, maxId } = state.tooltip; - return { currentId, maxId }; + return { + currentId, + maxId + }; } function mapDispatchToProps (dispatch) { diff --git a/js/src/ui/Tooltips/Tooltip/tooltip.spec.js b/js/src/ui/Tooltips/Tooltip/tooltip.spec.js new file mode 100644 index 0000000000..57e820c967 --- /dev/null +++ b/js/src/ui/Tooltips/Tooltip/tooltip.spec.js @@ -0,0 +1,68 @@ +// 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 . + +import React from 'react'; +import { shallow } from 'enzyme'; +import sinon from 'sinon'; + +import Tooltip from './'; + +let component; +let store; + +function createRedux (currentId = 0) { + store = { + dispatch: sinon.stub(), + subscribe: sinon.stub(), + getState: () => { + return { + tooltip: { + currentId, + maxId: 2 + } + }; + } + }; + + return store; +} + +function render () { + component = shallow( + , + { + context: { + store: createRedux() + } + } + ).find('Tooltip').shallow(); + + return component; +} + +describe('ui/Tooltips/Tooltip', () => { + beforeEach(() => { + render(); + }); + + it('renders defaults', () => { + expect(component.get(0)).to.be.ok; + }); + + it('renders null when id !== currentId', () => { + expect(render(1).get(0)).to.be.null; + }); +}); diff --git a/js/src/ui/Tooltips/index.js b/js/src/ui/Tooltips/index.js index 9996fdc744..8918ce14c0 100644 --- a/js/src/ui/Tooltips/index.js +++ b/js/src/ui/Tooltips/index.js @@ -14,8 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import Tooltip from './Tooltip'; -import tooltipReducer from './reducers'; - export default from './tooltips'; -export { Tooltip, tooltipReducer }; + +export Tooltip from './Tooltip'; +export tooltipReducer from './reducers'; diff --git a/js/src/ui/Tooltips/tooltips.js b/js/src/ui/Tooltips/tooltips.js index fb2de6f224..0647cae37b 100644 --- a/js/src/ui/Tooltips/tooltips.js +++ b/js/src/ui/Tooltips/tooltips.js @@ -29,7 +29,6 @@ class Tooltips extends Component { static propTypes = { currentId: PropTypes.number, - closed: PropTypes.bool, onNextTooltip: PropTypes.func } @@ -72,7 +71,9 @@ class Tooltips extends Component { function mapStateToProps (state) { const { currentId } = state.tooltip; - return { currentId }; + return { + currentId + }; } function mapDispatchToProps (dispatch) { diff --git a/js/src/ui/Tooltips/tooltips.spec.js b/js/src/ui/Tooltips/tooltips.spec.js new file mode 100644 index 0000000000..b52153f8ee --- /dev/null +++ b/js/src/ui/Tooltips/tooltips.spec.js @@ -0,0 +1,76 @@ +// 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 . + +import { shallow } from 'enzyme'; +import React from 'react'; +import sinon from 'sinon'; + +import Tooltips from './'; + +let component; +let router; +let store; + +function createRedux () { + store = { + dispatch: sinon.stub(), + subscribe: sinon.stub(), + getState: () => { + return { + tooltip: { + currentId: 1 + } + }; + } + }; + + return store; +} + +function createRouter () { + router = { + push: sinon.stub() + }; + + return router; +} + +function render () { + component = shallow( + , + { + context: { + store: createRedux() + } + } + ).find('Tooltips').shallow({ + context: { + router: createRouter() + } + }); + + return component; +} + +describe('ui/Tooltips', () => { + beforeEach(() => { + render(); + }); + + it('renders defaults', () => { + expect(component.get(0)).to.be.ok; + }); +}); diff --git a/js/src/views/Accounts/accounts.js b/js/src/views/Accounts/accounts.js index c84400f3a9..80a292cd61 100644 --- a/js/src/views/Accounts/accounts.js +++ b/js/src/views/Accounts/accounts.js @@ -15,6 +15,7 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import ContentAdd from 'material-ui/svg-icons/content/add'; @@ -88,7 +89,12 @@ class Accounts extends Component { + } /> { this.renderWallets() } @@ -228,7 +234,12 @@ class Accounts extends Component { + } /> ); diff --git a/js/src/views/Application/TabBar/Tab/tab.js b/js/src/views/Application/TabBar/Tab/tab.js index 7763c581ac..9496c73e4b 100644 --- a/js/src/views/Application/TabBar/Tab/tab.js +++ b/js/src/views/Application/TabBar/Tab/tab.js @@ -26,13 +26,12 @@ const SIGNER_ID = 'signer'; export default class Tab extends Component { static propTypes = { - children: PropTypes.node, pendings: PropTypes.number, view: PropTypes.object.isRequired }; render () { - const { view, children } = this.props; + const { view } = this.props; return ( - { children } - + /> ); } diff --git a/js/src/views/Application/TabBar/Tab/tabs.spec.js b/js/src/views/Application/TabBar/Tab/tabs.spec.js index e7ab61a7b0..c1771412a2 100644 --- a/js/src/views/Application/TabBar/Tab/tabs.spec.js +++ b/js/src/views/Application/TabBar/Tab/tabs.spec.js @@ -26,7 +26,6 @@ let instance; function render (id = 'signer') { component = shallow( testChildren
} pending={ 5 } view={ { id } } /> diff --git a/js/src/views/Application/TabBar/tabBar.css b/js/src/views/Application/TabBar/tabBar.css index 4ac00b8486..2903f3ed3f 100644 --- a/js/src/views/Application/TabBar/tabBar.css +++ b/js/src/views/Application/TabBar/tabBar.css @@ -60,8 +60,8 @@ } .tabbarTooltip { - left: 3.3em; - top: 0.5em; + left: 3em; + top: 4em; } .label { diff --git a/js/src/views/Application/TabBar/tabBar.js b/js/src/views/Application/TabBar/tabBar.js index 939e1b298a..d9aa070890 100644 --- a/js/src/views/Application/TabBar/tabBar.js +++ b/js/src/views/Application/TabBar/tabBar.js @@ -15,6 +15,7 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; import { Link } from 'react-router'; import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar'; @@ -49,6 +50,15 @@ class TabBar extends Component {
{ this.renderTabItems() } + + } + />
@@ -61,15 +71,6 @@ class TabBar extends Component { const { views, pending } = this.props; return views.map((view, index) => { - const body = (view.id === 'accounts') - ? ( - - ) - : null; - return ( - { body } - + /> ); }); -- GitLab From 3218c365e9b1e532697185f3783e9ddab4eee08d Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Wed, 15 Feb 2017 11:08:57 +0000 Subject: [PATCH 034/246] [ci skip] js-precompiled 20170215-110429 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 949b3fba22..1337eccef6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1606,7 +1606,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#acdd023610ac819b69b14d6464879497aff53c85" +source = "git+https://github.com/ethcore/js-precompiled.git#033b76f68f91e14c886bf1975c1fe7969180276e" 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 81610dccfe..4d34464907 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.85", + "version": "0.3.86", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", -- GitLab From 8aaa18d75df3b46e34894e8a8d7eedcb81ffb205 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Wed, 15 Feb 2017 13:37:58 +0100 Subject: [PATCH 035/246] Fix pasting of value in Input fields (#4555) * Fix logging and logger issues * onPaste submit value + pasted text (#4553) --- js/src/config.js | 4 ++++ js/src/redux/providers/certifications/middleware.js | 12 ++++++++---- js/src/ui/Form/Input/input.js | 5 +++-- js/src/views/Settings/Parity/parity.js | 4 ++-- js/src/views/Settings/Parity/store.js | 6 +++--- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/js/src/config.js b/js/src/config.js index 7a919e73f4..5113e9399b 100644 --- a/js/src/config.js +++ b/js/src/config.js @@ -21,6 +21,10 @@ export const LOG_KEYS = { key: 'balances', desc: 'Balances fetching' }, + CertificationsMiddleware: { + key: 'certifications.middleware', + desc: 'Certifications Middleware' + }, TransferModalStore: { key: 'modalsTransferStore', desc: 'Transfer modal MobX store' diff --git a/js/src/redux/providers/certifications/middleware.js b/js/src/redux/providers/certifications/middleware.js index 763c4c34ee..6ea0bf37b7 100644 --- a/js/src/redux/providers/certifications/middleware.js +++ b/js/src/redux/providers/certifications/middleware.js @@ -16,10 +16,14 @@ import { uniq, range, debounce } from 'lodash'; -import CertifierABI from '~/contracts/abi/certifier.json'; +import { addCertification, removeCertification } from './actions'; + +import { getLogger, LOG_KEYS } from '~/config'; import Contract from '~/api/contract'; import Contracts from '~/contracts'; -import { addCertification, removeCertification } from './actions'; +import CertifierABI from '~/contracts/abi/certifier.json'; + +const log = getLogger(LOG_KEYS.CertificationsMiddleware); // TODO: move this to a more general place const updatableFilter = (api, onFilter) => { @@ -180,10 +184,10 @@ export default class CertificationsMiddleware { }) .catch((err) => { if (/does not exist/.test(err.toString())) { - return console.warn(err.toString()); + return log.info(err.toString()); } - console.warn(`Could not fetch certifier ${id}:`, err); + log.warn(`Could not fetch certifier ${id}:`, err); }); }); diff --git a/js/src/ui/Form/Input/input.js b/js/src/ui/Form/Input/input.js index ff782f76e1..14f6f47cfe 100644 --- a/js/src/ui/Form/Input/input.js +++ b/js/src/ui/Form/Input/input.js @@ -218,10 +218,11 @@ export default class Input extends Component { } onPaste = (event) => { - const value = event.clipboardData.getData('Text'); + const { value } = event.target; + const pasted = event.clipboardData.getData('Text'); window.setTimeout(() => { - this.onSubmit(value); + this.onSubmit(value + pasted); }, 0); } diff --git a/js/src/views/Settings/Parity/parity.js b/js/src/views/Settings/Parity/parity.js index 8010056166..769345d0a5 100644 --- a/js/src/views/Settings/Parity/parity.js +++ b/js/src/views/Settings/Parity/parity.js @@ -106,10 +106,10 @@ export default class Parity extends Component { .keys(logLevels) .map((key) => { const { level, log } = logLevels[key]; - const { path, desc } = log; + const { desc } = log; const onChange = (_, index) => { - this.store.updateLoggerLevel(path, Object.values(LOGLEVEL_OPTIONS)[index].value); + this.store.updateLoggerLevel(log.key, Object.values(LOGLEVEL_OPTIONS)[index].value); }; return ( diff --git a/js/src/views/Settings/Parity/store.js b/js/src/views/Settings/Parity/store.js index 3dbd4d3a6e..3dc34a6961 100644 --- a/js/src/views/Settings/Parity/store.js +++ b/js/src/views/Settings/Parity/store.js @@ -40,7 +40,7 @@ export default class Store { } @action setLogLevels = (logLevels) => { - this.logLevels = logLevels; + this.logLevels = { ...logLevels }; } @action setLogLevelsSelect = (logLevelsSelect) => { @@ -83,8 +83,8 @@ export default class Store { ); } - updateLoggerLevel (path, level) { - LogLevel.getLogger(path).setLevel(level); + updateLoggerLevel (key, level) { + LogLevel.getLogger(key).setLevel(level); this.loadLogLevels(); } -- GitLab From 4bae2c751c4129c453c0e7df9c339d9a7b85d91e Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Wed, 15 Feb 2017 13:42:55 +0100 Subject: [PATCH 036/246] Fixes BadgeReg Middleware (#4547) (#4556) --- js/src/redux/providers/certifications/middleware.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/js/src/redux/providers/certifications/middleware.js b/js/src/redux/providers/certifications/middleware.js index 6ea0bf37b7..a1403ee3a6 100644 --- a/js/src/redux/providers/certifications/middleware.js +++ b/js/src/redux/providers/certifications/middleware.js @@ -87,12 +87,12 @@ export default class CertificationsMiddleware { badgeReg .getContract() .then((badgeRegContract) => { - return badgeRegUpdateFilter(badgeRegContract.address, [ + return badgeRegUpdateFilter(badgeRegContract.address, [ [ badgeRegContract.instance.Registered.signature, badgeRegContract.instance.Unregistered.signature, badgeRegContract.instance.MetaChanged.signature, badgeRegContract.instance.AddressChanged.signature - ]); + ] ]); }) .then(() => { shortFetchChanges(); @@ -125,9 +125,13 @@ export default class CertificationsMiddleware { } function onBadgeRegLogs (logs) { - const ids = logs.map((log) => log.params.id.value.toNumber()); + return badgeReg.getContract() + .then((badgeRegContract) => { + logs = badgeRegContract.parseEventLogs(logs); + const ids = logs.map((log) => log.params.id.value.toNumber()); - return fetchCertifiers(uniq(ids)); + return fetchCertifiers(uniq(ids)); + }); } function _fetchChanges () { -- GitLab From b38a874b1801b1f05ec1f936b112e109d7b60bb3 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Wed, 15 Feb 2017 12:54:04 +0000 Subject: [PATCH 037/246] [ci skip] js-precompiled 20170215-124941 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1337eccef6..bcb34d0eca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1606,7 +1606,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#033b76f68f91e14c886bf1975c1fe7969180276e" +source = "git+https://github.com/ethcore/js-precompiled.git#6a330ba7f6fcae21291f75f002c59833766613db" 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 4d34464907..6938d20722 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.86", + "version": "0.3.87", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", -- GitLab From 0d289c773236845984eeaea394ca4fe449fb3778 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Wed, 15 Feb 2017 14:44:14 +0100 Subject: [PATCH 038/246] Skip OOG check for simple transfers (#4550) (#4558) --- js/src/ui/TxHash/txHash.js | 77 +++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 26 deletions(-) diff --git a/js/src/ui/TxHash/txHash.js b/js/src/ui/TxHash/txHash.js index 6b8278a747..724004ae74 100644 --- a/js/src/ui/TxHash/txHash.js +++ b/js/src/ui/TxHash/txHash.js @@ -22,6 +22,7 @@ import { connect } from 'react-redux'; import { txLink } from '~/3rdparty/etherscan/links'; import Warning from '~/ui/Warning'; +import { DEFAULT_GAS } from '~/util/constants'; import ShortenedHash from '../ShortenedHash'; import styles from './txHash.css'; @@ -44,13 +45,14 @@ class TxHash extends Component { state = { blockNumber: new BigNumber(0), - gas: {}, + isRecipientContract: false, subscriptionId: null, - transaction: null + transaction: null, + transactionReceipt: null } componentWillMount () { - this.fetchTransactionGas(); + this.fetchTransaction(); } componentWillReceiveProps (nextProps) { @@ -58,15 +60,14 @@ class TxHash extends Component { const nextHash = nextProps.hash; if (prevHash !== nextHash) { - this.fetchTransactionGas(nextProps); + this.fetchTransaction(nextProps); } } /** - * Get the gas send for the current transaction - * and save the value in the state + * Get the sent transaction data */ - fetchTransactionGas (props = this.props) { + fetchTransaction (props = this.props) { const { hash } = props; if (!hash) { @@ -75,10 +76,27 @@ class TxHash extends Component { this.context.api.eth .getTransactionByHash(hash) - .then((transaction = {}) => { - const { gas = new BigNumber(0) } = transaction; + .then((transaction) => { + this.setState({ transaction }); - this.setState({ gas: { hash, value: gas } }); + return this.fetchRecipientCode(transaction); + }); + } + + fetchRecipientCode (transaction) { + if (!transaction || !transaction.to) { + return; + } + + this.context.api.eth + .getCode(transaction.to) + .then((code) => { + const isRecipientContract = code && !/^(0x)?0*$/.test(code); + + this.setState({ isRecipientContract }); + }) + .catch((error) => { + console.error('fetchRecipientCode', error); }); } @@ -124,16 +142,22 @@ class TxHash extends Component { } renderWarning () { - const { gas, transaction } = this.state; + const { isRecipientContract, transaction, transactionReceipt } = this.state; - if (!(transaction && transaction.blockNumber && transaction.blockNumber.gt(0))) { + if (!(transactionReceipt && transactionReceipt.blockNumber && transactionReceipt.blockNumber.gt(0))) { return null; } - const { gasUsed = new BigNumber(0) } = transaction; - const isOog = transaction.transactionHash === gas.hash && gasUsed.gte(gas.value); + const { gas, input } = transaction; + const { gasUsed = new BigNumber(0) } = transactionReceipt; + + const isOog = gasUsed.gte(gas); + + // Skip OOG check if a simple transaction to a non-contract account + // @see: https://github.com/ethcore/parity/issues/4550 + const skipOogCheck = gasUsed.eq(DEFAULT_GAS) && (!input || input === '0x') && !isRecipientContract; - if (!isOog) { + if (!isOog || skipOogCheck) { return null; } @@ -151,9 +175,9 @@ class TxHash extends Component { renderConfirmations () { const { maxConfirmations } = this.props; - const { blockNumber, transaction } = this.state; + const { blockNumber, transactionReceipt } = this.state; - if (!(transaction && transaction.blockNumber && transaction.blockNumber.gt(0))) { + if (!(transactionReceipt && transactionReceipt.blockNumber && transactionReceipt.blockNumber.gt(0))) { return (
{ - this.setState({ - blockNumber, - transaction - }); + .then((transactionReceipt) => { + nextState.transactionReceipt = transactionReceipt; }) .catch((error) => { - console.warn('onBlockNumber', error); - this.setState({ blockNumber }); + console.error('onBlockNumber', error); + }) + .then(() => { + this.setState(nextState); }); } } -- GitLab From 4889cff3106f97fabaa4d5d31a2f75aded4af1a7 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Wed, 15 Feb 2017 13:56:12 +0000 Subject: [PATCH 039/246] [ci skip] js-precompiled 20170215-135145 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bcb34d0eca..3da15278e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1606,7 +1606,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#6a330ba7f6fcae21291f75f002c59833766613db" +source = "git+https://github.com/ethcore/js-precompiled.git#3687825172c852e0ad4bea85ec3119e9fedb8414" 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 6938d20722..41274bbcc4 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.87", + "version": "0.3.88", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", -- GitLab From 494a0de1e2e648a24ddcffab6c5abdade9e74f1f Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Wed, 15 Feb 2017 18:56:15 +0300 Subject: [PATCH 040/246] Key derivation in ethstore & rpc (#4515) * initial in secret store * generation * test * refactor of the derivation * signing * account provider * tests for account provider * rpc types * rpc types converts * rpc tests * fix warnings * some extra docs * derivate -> derive * secret() -> as_raw() * secret() -> as_raw() in rpc * fix merge bug * align with new serde changes --- ethcore/src/account_provider/mod.rs | 96 ++++++++++++++- ethkey/src/extended.rs | 22 ++-- ethkey/src/lib.rs | 2 +- ethstore/src/error.rs | 9 ++ ethstore/src/ethstore.rs | 116 +++++++++++++++++- ethstore/src/lib.rs | 5 +- ethstore/src/secret_store.rs | 23 +++- rpc/src/v1/impls/parity_accounts.rs | 28 ++++- rpc/src/v1/tests/mocked/parity_accounts.rs | 40 +++++++ rpc/src/v1/traits/parity_accounts.rs | 13 +- rpc/src/v1/types/derivation.rs | 131 +++++++++++++++++++++ rpc/src/v1/types/mod.rs | 3 +- 12 files changed, 464 insertions(+), 24 deletions(-) create mode 100644 rpc/src/v1/types/derivation.rs diff --git a/ethcore/src/account_provider/mod.rs b/ethcore/src/account_provider/mod.rs index 975d2cbc95..568cbd4e3a 100755 --- a/ethcore/src/account_provider/mod.rs +++ b/ethcore/src/account_provider/mod.rs @@ -31,6 +31,7 @@ use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator}; use ethjson::misc::AccountMeta; use hardware_wallet::{Error as HardwareError, HardwareWalletManager, KeyPath}; pub use ethstore::ethkey::Signature; +pub use ethstore::{Derivation, IndexDerivation}; /// Type of unlock. #[derive(Clone)] @@ -197,6 +198,20 @@ impl AccountProvider { Ok(account.address) } + /// Generates new derived account based on the existing one + /// If password is not provided, account must be unlocked + /// New account will be created with the same password (if save: true) + pub fn derive_account(&self, address: &Address, password: Option, derivation: Derivation, save: bool) + -> Result + { + let account = self.sstore.account_ref(&address)?; + let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?; + Ok( + if save { self.sstore.insert_derived(SecretVaultRef::Root, &account, &password, derivation)?.address } + else { self.sstore.generate_derived(&account, &password, derivation)? } + ) + } + /// Import a new presale wallet. pub fn import_presale(&self, presale_json: &[u8], password: &str) -> Result { let account = self.sstore.import_presale(SecretVaultRef::Root, presale_json, password)?; @@ -458,6 +473,15 @@ impl AccountProvider { Ok(self.sstore.sign(&account, &password, &message)?) } + /// Signs message using the derived secret. If password is not provided the account must be unlocked. + pub fn sign_derived(&self, address: &Address, password: Option, derivation: Derivation, message: Message) + -> Result + { + let account = self.sstore.account_ref(address)?; + let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?; + Ok(self.sstore.sign_derived(&account, &password, derivation, &message)?) + } + /// Signs given message with supplied token. Returns a token to use in next signing within this session. pub fn sign_with_token(&self, address: Address, token: AccountToken, message: Message) -> Result<(Signature, AccountToken), SignError> { let account = self.sstore.account_ref(&address)?; @@ -593,7 +617,8 @@ mod tests { use super::{AccountProvider, Unlock, DappId}; use std::time::Instant; use ethstore::ethkey::{Generator, Random}; - use ethstore::StoreAccountRef; + use ethstore::{StoreAccountRef, Derivation}; + use util::H256; #[test] fn unlock_account_temp() { @@ -606,6 +631,75 @@ mod tests { assert!(ap.sign(kp.address(), None, Default::default()).is_err()); } + #[test] + fn derived_account_nosave() { + let kp = Random.generate().unwrap(); + let ap = AccountProvider::transient_provider(); + assert!(ap.insert_account(kp.secret().clone(), "base").is_ok()); + assert!(ap.unlock_account_permanently(kp.address(), "base".into()).is_ok()); + + let derived_addr = ap.derive_account( + &kp.address(), + None, + Derivation::SoftHash(H256::from(999)), + false, + ).expect("Derivation should not fail"); + + assert!(ap.unlock_account_permanently(derived_addr, "base".into()).is_err(), + "There should be an error because account is not supposed to be saved"); + } + + #[test] + fn derived_account_save() { + let kp = Random.generate().unwrap(); + let ap = AccountProvider::transient_provider(); + assert!(ap.insert_account(kp.secret().clone(), "base").is_ok()); + assert!(ap.unlock_account_permanently(kp.address(), "base".into()).is_ok()); + + let derived_addr = ap.derive_account( + &kp.address(), + None, + Derivation::SoftHash(H256::from(999)), + true, + ).expect("Derivation should not fail"); + + assert!(ap.unlock_account_permanently(derived_addr, "base_wrong".into()).is_err(), + "There should be an error because password is invalid"); + + assert!(ap.unlock_account_permanently(derived_addr, "base".into()).is_ok(), + "Should be ok because account is saved and password is valid"); + } + + #[test] + fn derived_account_sign() { + let kp = Random.generate().unwrap(); + let ap = AccountProvider::transient_provider(); + assert!(ap.insert_account(kp.secret().clone(), "base").is_ok()); + assert!(ap.unlock_account_permanently(kp.address(), "base".into()).is_ok()); + + let derived_addr = ap.derive_account( + &kp.address(), + None, + Derivation::SoftHash(H256::from(1999)), + true, + ).expect("Derivation should not fail"); + ap.unlock_account_permanently(derived_addr, "base".into()) + .expect("Should be ok because account is saved and password is valid"); + + let msg = Default::default(); + let signed_msg1 = ap.sign(derived_addr, None, msg) + .expect("Signing with existing unlocked account should not fail"); + let signed_msg2 = ap.sign_derived( + &kp.address(), + None, + Derivation::SoftHash(H256::from(1999)), + msg, + ).expect("Derived signing with existing unlocked account should not fail"); + + assert_eq!(signed_msg1, signed_msg2, + "Signed messages should match"); + } + #[test] fn unlock_account_perm() { let kp = Random.generate().unwrap(); diff --git a/ethkey/src/extended.rs b/ethkey/src/extended.rs index 2d2e27b5b0..c8f057a4ba 100644 --- a/ethkey/src/extended.rs +++ b/ethkey/src/extended.rs @@ -106,7 +106,7 @@ impl ExtendedSecret { } /// Private key component of the extended key. - pub fn secret(&self) -> &Secret { + pub fn as_raw(&self) -> &Secret { &self.secret } } @@ -127,7 +127,7 @@ impl ExtendedPublic { pub fn from_secret(secret: &ExtendedSecret) -> Result { Ok( ExtendedPublic::new( - derivation::point(**secret.secret())?, + derivation::point(**secret.as_raw())?, secret.chain_code.clone(), ) ) @@ -410,7 +410,7 @@ mod tests { let (private_seed, chain_code) = master_chain_basic(); let extended_secret = ExtendedSecret::with_code(Secret::from_slice(&*private_seed).unwrap(), chain_code); let derived = f(extended_secret); - assert_eq!(**derived.secret(), test_private); + assert_eq!(**derived.as_raw(), test_private); } #[test] @@ -419,14 +419,14 @@ mod tests { let extended_secret = ExtendedSecret::with_code(secret.clone(), 0u64.into()); // hardened - assert_eq!(&**extended_secret.secret(), &*secret); - assert_eq!(&**extended_secret.derive(2147483648.into()).secret(), &"0927453daed47839608e414a3738dfad10aed17c459bbd9ab53f89b026c834b6".into()); - assert_eq!(&**extended_secret.derive(2147483649.into()).secret(), &"44238b6a29c6dcbe9b401364141ba11e2198c289a5fed243a1c11af35c19dc0f".into()); + assert_eq!(&**extended_secret.as_raw(), &*secret); + assert_eq!(&**extended_secret.derive(2147483648.into()).as_raw(), &"0927453daed47839608e414a3738dfad10aed17c459bbd9ab53f89b026c834b6".into()); + assert_eq!(&**extended_secret.derive(2147483649.into()).as_raw(), &"44238b6a29c6dcbe9b401364141ba11e2198c289a5fed243a1c11af35c19dc0f".into()); // normal - assert_eq!(&**extended_secret.derive(0.into()).secret(), &"bf6a74e3f7b36fc4c96a1e12f31abc817f9f5904f5a8fc27713163d1f0b713f6".into()); - assert_eq!(&**extended_secret.derive(1.into()).secret(), &"bd4fca9eb1f9c201e9448c1eecd66e302d68d4d313ce895b8c134f512205c1bc".into()); - assert_eq!(&**extended_secret.derive(2.into()).secret(), &"86932b542d6cab4d9c65490c7ef502d89ecc0e2a5f4852157649e3251e2a3268".into()); + assert_eq!(&**extended_secret.derive(0.into()).as_raw(), &"bf6a74e3f7b36fc4c96a1e12f31abc817f9f5904f5a8fc27713163d1f0b713f6".into()); + assert_eq!(&**extended_secret.derive(1.into()).as_raw(), &"bd4fca9eb1f9c201e9448c1eecd66e302d68d4d313ce895b8c134f512205c1bc".into()); + assert_eq!(&**extended_secret.derive(2.into()).as_raw(), &"86932b542d6cab4d9c65490c7ef502d89ecc0e2a5f4852157649e3251e2a3268".into()); let extended_public = ExtendedPublic::from_secret(&extended_secret).expect("Extended public should be created"); let derived_public = extended_public.derive(0.into()).expect("First derivation of public should succeed"); @@ -436,7 +436,7 @@ mod tests { Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap(), 064.into(), ); - assert_eq!(&**keypair.derive(2147483648u32.into()).expect("Derivation of keypair should succeed").secret().secret(), &"edef54414c03196557cf73774bc97a645c9a1df2164ed34f0c2a78d1375a930c".into()); + assert_eq!(&**keypair.derive(2147483648u32.into()).expect("Derivation of keypair should succeed").secret().as_raw(), &"edef54414c03196557cf73774bc97a645c9a1df2164ed34f0c2a78d1375a930c".into()); } #[test] @@ -461,7 +461,7 @@ mod tests { let derivation_secret = H256::from_str("51eaf04f9dbbc1417dc97e789edd0c37ecda88bac490434e367ea81b71b7b015").unwrap(); let extended_secret = ExtendedSecret::with_code(secret.clone(), 1u64.into()); - assert_eq!(&**extended_secret.derive(Derivation::Hard(derivation_secret)).secret(), &"2bc2d696fb744d77ff813b4a1ef0ad64e1e5188b622c54ba917acc5ebc7c5486".into()); + assert_eq!(&**extended_secret.derive(Derivation::Hard(derivation_secret)).as_raw(), &"2bc2d696fb744d77ff813b4a1ef0ad64e1e5188b622c54ba917acc5ebc7c5486".into()); } #[test] diff --git a/ethkey/src/lib.rs b/ethkey/src/lib.rs index 3882b3559c..9c0c7907d3 100644 --- a/ethkey/src/lib.rs +++ b/ethkey/src/lib.rs @@ -51,7 +51,7 @@ pub use self::prefix::Prefix; pub use self::random::Random; pub use self::signature::{sign, verify_public, verify_address, recover, Signature}; pub use self::secret::Secret; -pub use self::extended::{ExtendedPublic, ExtendedSecret, ExtendedKeyPair, DerivationError}; +pub use self::extended::{ExtendedPublic, ExtendedSecret, ExtendedKeyPair, DerivationError, Derivation}; use bigint::hash::{H160, H256, H512}; diff --git a/ethstore/src/error.rs b/ethstore/src/error.rs index ff6b4e4db5..8a2eb5e8b1 100755 --- a/ethstore/src/error.rs +++ b/ethstore/src/error.rs @@ -18,6 +18,7 @@ use std::fmt; use std::io::Error as IoError; use ethkey::Error as EthKeyError; use crypto::Error as EthCryptoError; +use ethkey::DerivationError; #[derive(Debug)] pub enum Error { @@ -35,6 +36,7 @@ pub enum Error { CreationFailed, EthKey(EthKeyError), EthCrypto(EthCryptoError), + Derivation(DerivationError), Custom(String), } @@ -55,6 +57,7 @@ impl fmt::Display for Error { Error::CreationFailed => "Account creation failed".into(), Error::EthKey(ref err) => err.to_string(), Error::EthCrypto(ref err) => err.to_string(), + Error::Derivation(ref err) => format!("Derivation error: {:?}", err), Error::Custom(ref s) => s.clone(), }; @@ -79,3 +82,9 @@ impl From for Error { Error::EthCrypto(err) } } + +impl From for Error { + fn from(err: DerivationError) -> Self { + Error::Derivation(err) + } +} diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index ff1a4fa676..86b6dce461 100755 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -21,12 +21,12 @@ use parking_lot::{Mutex, RwLock}; use crypto::KEY_ITERATIONS; use random::Random; -use ethkey::{Signature, Address, Message, Secret, Public, KeyPair}; +use ethkey::{self, Signature, Address, Message, Secret, Public, KeyPair, ExtendedKeyPair}; use dir::{KeyDirectory, VaultKeyDirectory, VaultKey, SetKeyError}; use account::SafeAccount; use presale::PresaleWallet; use json::{self, Uuid}; -use {import, Error, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef}; +use {import, Error, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation}; pub struct EthStore { store: EthMultiStore, @@ -54,6 +54,16 @@ impl SimpleSecretStore for EthStore { self.store.insert_account(vault, secret, password) } + fn insert_derived(&self, vault: SecretVaultRef, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) + -> Result + { + self.store.insert_derived(vault, account_ref, password, derivation) + } + + fn generate_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) -> Result { + self.store.generate_derived(account_ref, password, derivation) + } + fn account_ref(&self, address: &Address) -> Result { self.store.account_ref(address) } @@ -71,8 +81,13 @@ impl SimpleSecretStore for EthStore { } fn sign(&self, account: &StoreAccountRef, password: &str, message: &Message) -> Result { - let account = self.get(account)?; - account.sign(password, message) + self.get(account)?.sign(password, message) + } + + fn sign_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation, message: &Message) + -> Result + { + self.store.sign_derived(account_ref, password, derivation, message) } fn decrypt(&self, account: &StoreAccountRef, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error> { @@ -340,6 +355,23 @@ impl EthMultiStore { return Ok(()); } + + fn generate(&self, secret: Secret, derivation: Derivation) -> Result { + let mut extended = ExtendedKeyPair::new(secret); + match derivation { + Derivation::Hierarchical(path) => { + for path_item in path { + extended = extended.derive( + if path_item.soft { ethkey::Derivation::Soft(path_item.index) } + else { ethkey::Derivation::Hard(path_item.index) } + )?; + } + }, + Derivation::SoftHash(h256) => { extended = extended.derive(ethkey::Derivation::Soft(h256))?; } + Derivation::HardHash(h256) => { extended = extended.derive(ethkey::Derivation::Hard(h256))?; } + } + Ok(extended) + } } impl SimpleSecretStore for EthMultiStore { @@ -350,6 +382,54 @@ impl SimpleSecretStore for EthMultiStore { self.import(vault, account) } + fn insert_derived(&self, vault: SecretVaultRef, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) + -> Result + { + let accounts = self.get(account_ref)?; + for account in accounts { + // Skip if password is invalid + if !account.check_password(password) { + continue; + } + let extended = self.generate(account.crypto.secret(password)?, derivation)?; + return self.insert_account(vault, extended.secret().as_raw().clone(), password); + } + Err(Error::InvalidPassword) + } + + fn generate_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) + -> Result + { + let accounts = self.get(&account_ref)?; + for account in accounts { + // Skip if password is invalid + if !account.check_password(password) { + continue; + } + let extended = self.generate(account.crypto.secret(password)?, derivation)?; + + return Ok(ethkey::public_to_address(extended.public().public())); + } + Err(Error::InvalidPassword) + } + + fn sign_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation, message: &Message) + -> Result + { + let accounts = self.get(&account_ref)?; + for account in accounts { + // Skip if password is invalid + if !account.check_password(password) { + continue; + } + let extended = self.generate(account.crypto.secret(password)?, derivation)?; + let secret = extended.secret().as_raw(); + return Ok(ethkey::sign(&secret, message)?) + } + Err(Error::InvalidPassword) + + } + fn account_ref(&self, address: &Address) -> Result { self.reload_accounts()?; self.cache.read().keys() @@ -511,7 +591,7 @@ impl SimpleSecretStore for EthMultiStore { let vault_provider = self.dir.as_vault_provider().ok_or(Error::VaultsAreNotSupported)?; vault_provider.vault_meta(name) }) - + } fn set_vault_meta(&self, name: &str, meta: &str) -> Result<(), Error> { @@ -527,9 +607,10 @@ mod tests { use dir::{KeyDirectory, MemoryDirectory, RootDiskDirectory}; use ethkey::{Random, Generator, KeyPair}; - use secret_store::{SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef}; + use secret_store::{SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation}; use super::{EthStore, EthMultiStore}; use devtools::RandomTempPath; + use util::H256; fn keypair() -> KeyPair { Random.generate().unwrap() @@ -898,4 +979,27 @@ mod tests { assert_eq!(store.get_vault_meta(name1).unwrap(), "Hello, world!!!".to_owned()); assert!(store.get_vault_meta("vault2").is_err()); } + + #[test] + fn should_store_derived_keys() { + // given we have one account in the store + let store = store(); + let keypair = keypair(); + let address = store.insert_account(SecretVaultRef::Root, keypair.secret().clone(), "test").unwrap(); + + // when we deriving from that account + let derived = store.insert_derived( + SecretVaultRef::Root, + &address, + "test", + Derivation::HardHash(H256::from(0)), + ).unwrap(); + + // there should be 2 accounts in the store + let accounts = store.accounts().unwrap(); + assert_eq!(accounts.len(), 2); + + // and we can sign with the derived contract + assert!(store.sign(&derived, "test", &Default::default()).is_ok(), "Second password should work for second store."); + } } diff --git a/ethstore/src/lib.rs b/ethstore/src/lib.rs index 51ac0afbab..f092c3fe62 100755 --- a/ethstore/src/lib.rs +++ b/ethstore/src/lib.rs @@ -57,5 +57,8 @@ pub use self::error::Error; pub use self::ethstore::{EthStore, EthMultiStore}; pub use self::import::{import_accounts, read_geth_accounts}; pub use self::presale::PresaleWallet; -pub use self::secret_store::{SecretVaultRef, StoreAccountRef, SimpleSecretStore, SecretStore}; +pub use self::secret_store::{ + SecretVaultRef, StoreAccountRef, SimpleSecretStore, SecretStore, + Derivation, IndexDerivation, +}; pub use self::random::{random_phrase, random_string}; diff --git a/ethstore/src/secret_store.rs b/ethstore/src/secret_store.rs index 442c48a1df..1eff95335d 100755 --- a/ethstore/src/secret_store.rs +++ b/ethstore/src/secret_store.rs @@ -19,6 +19,7 @@ use std::path::PathBuf; use ethkey::{Address, Message, Signature, Secret, Public}; use Error; use json::Uuid; +use util::H256; /// Key directory reference #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -40,10 +41,12 @@ pub struct StoreAccountRef { pub trait SimpleSecretStore: Send + Sync { fn insert_account(&self, vault: SecretVaultRef, secret: Secret, password: &str) -> Result; + fn insert_derived(&self, vault: SecretVaultRef, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) -> Result; fn change_password(&self, account: &StoreAccountRef, old_password: &str, new_password: &str) -> Result<(), Error>; fn remove_account(&self, account: &StoreAccountRef, password: &str) -> Result<(), Error>; - + fn generate_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) -> Result; fn sign(&self, account: &StoreAccountRef, password: &str, message: &Message) -> Result; + fn sign_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation, message: &Message) -> Result; fn decrypt(&self, account: &StoreAccountRef, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error>; fn accounts(&self) -> Result, Error>; @@ -116,3 +119,21 @@ impl Hash for StoreAccountRef { self.address.hash(state); } } + +/// Node in hierarchical derivation. +pub struct IndexDerivation { + /// Node is soft (allows proof of parent from parent node). + pub soft: bool, + /// Index sequence of the node. + pub index: u32, +} + +/// Derivation scheme for keys +pub enum Derivation { + /// Hierarchical derivation + Hierarchical(Vec), + /// Hash derivation, soft. + SoftHash(H256), + /// Hash derivation, hard. + HardHash(H256), +} diff --git a/rpc/src/v1/impls/parity_accounts.rs b/rpc/src/v1/impls/parity_accounts.rs index 16dbae64f0..e28ea25101 100644 --- a/rpc/src/v1/impls/parity_accounts.rs +++ b/rpc/src/v1/impls/parity_accounts.rs @@ -25,7 +25,7 @@ use ethcore::account_provider::AccountProvider; use jsonrpc_core::Error; use v1::helpers::errors; use v1::traits::ParityAccounts; -use v1::types::{H160 as RpcH160, H256 as RpcH256, DappId}; +use v1::types::{H160 as RpcH160, H256 as RpcH256, DappId, Derive, DeriveHierarchical, DeriveHash}; /// Account management (personal) rpc implementation. pub struct ParityAccountsClient { @@ -261,6 +261,32 @@ impl ParityAccounts for ParityAccountsClient { .map_err(|e| errors::account("Could not update vault metadata.", e)) .map(|_| true) } + + fn derive_key_index(&self, addr: RpcH160, password: String, derivation: DeriveHierarchical, save_as_account: bool) -> Result { + let addr: Address = addr.into(); + take_weak!(self.accounts) + .derive_account( + &addr, + Some(password), + Derive::from(derivation).to_derivation() + .map_err(|c| errors::account("Could not parse derivation request: {:?}", c))?, + save_as_account) + .map(Into::into) + .map_err(|e| errors::account("Could not derive account.", e)) + } + + fn derive_key_hash(&self, addr: RpcH160, password: String, derivation: DeriveHash, save_as_account: bool) -> Result { + let addr: Address = addr.into(); + take_weak!(self.accounts) + .derive_account( + &addr, + Some(password), + Derive::from(derivation).to_derivation() + .map_err(|c| errors::account("Could not parse derivation request: {:?}", c))?, + save_as_account) + .map(Into::into) + .map_err(|e| errors::account("Could not derive account.", e)) + } } fn into_vec(a: Vec) -> Vec where diff --git a/rpc/src/v1/tests/mocked/parity_accounts.rs b/rpc/src/v1/tests/mocked/parity_accounts.rs index 0f0a1836a4..6c518945f9 100644 --- a/rpc/src/v1/tests/mocked/parity_accounts.rs +++ b/rpc/src/v1/tests/mocked/parity_accounts.rs @@ -393,3 +393,43 @@ fn rpc_parity_get_set_vault_meta() { assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); } + +// name: parity_deriveAddressHash +// example: {"jsonrpc": "2.0", "method": "parity_deriveAddressHash", "params": ["0xc171033d5cbff7175f29dfd3a63dda3d6f8f385e", "password1", { "type": "soft", "hash": "0x0c0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0c0c" }, true ], "id": 3} +#[test] +fn derive_key_hash() { + let tester = setup(); + let hash = tester.accounts + .insert_account( + "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a".parse().unwrap(), + "password1") + .expect("account should be inserted ok"); + + assert_eq!(hash, "c171033d5cbff7175f29dfd3a63dda3d6f8f385e".parse().unwrap()); + + // derive by hash + let request = r#"{"jsonrpc": "2.0", "method": "parity_deriveAddressHash", "params": ["0xc171033d5cbff7175f29dfd3a63dda3d6f8f385e", "password1", { "type": "soft", "hash": "0x0c0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0c0c" }, true ], "id": 3}"#; + let response = r#"{"jsonrpc":"2.0","result":"0xf28c28fcddf4a9b8f474237278d3647f9c0d1b3c","id":3}"#; + let res = tester.io.handle_request_sync(&request); + assert_eq!(res, Some(response.into())); +} + +// name: parity_deriveAddressIndex +// example: {"jsonrpc": "2.0", "method": "parity_deriveAddressIndex", "params": ["0xc171033d5cbff7175f29dfd3a63dda3d6f8f385e", "password1", [{ "type": "soft", "index": 0 }, { "type": "soft", "index": 1 }], false ], "id": 3} +#[test] +fn derive_key_index() { + let tester = setup(); + let hash = tester.accounts + .insert_account( + "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a".parse().unwrap(), + "password1") + .expect("account should be inserted ok"); + + assert_eq!(hash, "c171033d5cbff7175f29dfd3a63dda3d6f8f385e".parse().unwrap()); + + // derive by hash + let request = r#"{"jsonrpc": "2.0", "method": "parity_deriveAddressIndex", "params": ["0xc171033d5cbff7175f29dfd3a63dda3d6f8f385e", "password1", [{ "type": "soft", "index": 0 }, { "type": "soft", "index": 1 }], false ], "id": 3}"#; + let response = r#"{"jsonrpc":"2.0","result":"0xcc548e0bb2efe792a920ae0fbf583b13919f274f","id":3}"#; + let res = tester.io.handle_request_sync(&request); + assert_eq!(res, Some(response.into())); +} diff --git a/rpc/src/v1/traits/parity_accounts.rs b/rpc/src/v1/traits/parity_accounts.rs index 72aeeaa955..5767860738 100644 --- a/rpc/src/v1/traits/parity_accounts.rs +++ b/rpc/src/v1/traits/parity_accounts.rs @@ -18,7 +18,7 @@ use std::collections::BTreeMap; use jsonrpc_core::Error; -use v1::types::{H160, H256, DappId}; +use v1::types::{H160, H256, DappId, DeriveHash, DeriveHierarchical}; build_rpc_trait! { /// Personal Parity rpc interface. @@ -141,5 +141,16 @@ build_rpc_trait! { /// Set vault metadata string. #[rpc(name = "parity_setVaultMeta")] fn set_vault_meta(&self, String, String) -> Result; + + /// Derive new address from given account address using specific hash. + /// Resulting address can be either saved as a new account (with the same password). + #[rpc(name = "parity_deriveAddressHash")] + fn derive_key_hash(&self, H160, String, DeriveHash, bool) -> Result; + + /// Derive new address from given account address using + /// hierarchical derivation (sequence of 32-bit integer indices). + /// Resulting address can be either saved as a new account (with the same password). + #[rpc(name = "parity_deriveAddressIndex")] + fn derive_key_index(&self, H160, String, DeriveHierarchical, bool) -> Result; } } diff --git a/rpc/src/v1/types/derivation.rs b/rpc/src/v1/types/derivation.rs new file mode 100644 index 0000000000..decf48171f --- /dev/null +++ b/rpc/src/v1/types/derivation.rs @@ -0,0 +1,131 @@ +// 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 . + +use std::fmt; +use serde::{Deserialize, Deserializer}; +use serde::de::{Error, Visitor}; + +use ethstore; + +use super::hash::H256; + +/// Type of derivation +pub enum DerivationType { + /// Soft - allow proof of parent + Soft, + /// Hard - does not allow proof of parent + Hard, +} + +/// Derivation request by hash +#[derive(Deserialize)] +pub struct DeriveHash { + hash: H256, + #[serde(rename="type")] + d_type: DerivationType, +} + +/// Node propertoes in hierarchical derivation request +#[derive(Deserialize)] +pub struct DeriveHierarchicalItem { + index: u64, + #[serde(rename="type")] + d_type: DerivationType, +} + +/// Hierarchical (index sequence) request +pub type DeriveHierarchical = Vec; + +/// Generic derivate request +pub enum Derive { + /// Hierarchical (index sequence) request + Hierarchical(DeriveHierarchical), + /// Hash request + Hash(DeriveHash), +} + +impl From for Derive { + fn from(d: DeriveHierarchical) -> Self { + Derive::Hierarchical(d) + } +} + +impl From for Derive { + fn from(d: DeriveHash) -> Self { + Derive::Hash(d) + } +} + +/// Error converting request data +#[derive(Debug)] +pub enum ConvertError { + IndexOverlfow(u64), +} + +impl Derive { + /// Convert to account provider struct dealing with possible overflows + pub fn to_derivation(self) -> Result { + Ok(match self { + Derive::Hierarchical(drv) => { + ethstore::Derivation::Hierarchical({ + let mut members = Vec::::new(); + for h in drv { + if h.index > ::std::u32::MAX as u64 { return Err(ConvertError::IndexOverlfow(h.index)); } + members.push(match h.d_type { + DerivationType::Soft => ethstore::IndexDerivation { soft: true, index: h.index as u32 }, + DerivationType::Hard => ethstore::IndexDerivation { soft: false, index: h.index as u32 }, + }); + } + members + }) + }, + Derive::Hash(drv) => { + match drv.d_type { + DerivationType::Soft => ethstore::Derivation::SoftHash(drv.hash.into()), + DerivationType::Hard => ethstore::Derivation::HardHash(drv.hash.into()), + } + }, + }) + } +} + +impl Deserialize for DerivationType { + fn deserialize(deserializer: D) -> Result where D: Deserializer { + deserializer.deserialize(DerivationTypeVisitor) + } +} + +struct DerivationTypeVisitor; + +impl Visitor for DerivationTypeVisitor { + type Value = DerivationType; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "'hard' or 'soft'") + } + + fn visit_str(self, value: &str) -> Result where E: Error { + match value { + "soft" => Ok(DerivationType::Soft), + "hard" => Ok(DerivationType::Hard), + _ => Err(Error::custom("invalid derivation type")), + } + } + + fn visit_string(self, value: String) -> Result where E: Error { + self.visit_str(value.as_ref()) + } +} diff --git a/rpc/src/v1/types/mod.rs b/rpc/src/v1/types/mod.rs index 83223d947e..a4bfcb41f5 100644 --- a/rpc/src/v1/types/mod.rs +++ b/rpc/src/v1/types/mod.rs @@ -24,6 +24,7 @@ mod bytes; mod call_request; mod confirmations; mod consensus_status; +mod derivation; mod filter; mod hash; mod histogram; @@ -51,6 +52,7 @@ pub use self::confirmations::{ TransactionModification, SignRequest, DecryptRequest, Either }; pub use self::consensus_status::*; +pub use self::derivation::{DeriveHash, DeriveHierarchical, Derive}; pub use self::filter::{Filter, FilterChanges}; pub use self::hash::{H64, H160, H256, H512, H520, H2048}; pub use self::histogram::Histogram; @@ -70,4 +72,3 @@ pub use self::transaction_request::TransactionRequest; pub use self::transaction_condition::TransactionCondition; pub use self::uint::{U128, U256}; pub use self::work::Work; - -- GitLab From 895298fc49922cf9191ef3c12eaec776fd951893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 15 Feb 2017 16:57:27 +0100 Subject: [PATCH 041/246] Alias for personal_sendTransaction (#4554) --- rpc/src/v1/impls/personal.rs | 5 +++++ rpc/src/v1/tests/mocked/personal.rs | 13 +++++++++++-- rpc/src/v1/traits/personal.rs | 5 +++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 2501e3f3a6..966dc9a743 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -127,4 +127,9 @@ impl Personal for PersonalClient { }) .boxed() } + + fn sign_and_send_transaction(&self, meta: Metadata, request: TransactionRequest, password: String) -> BoxFuture { + warn!("Using deprecated personal_signAndSendTransaction, use personal_sendTransaction instead."); + self.send_transaction(meta, request, password) + } } diff --git a/rpc/src/v1/tests/mocked/personal.rs b/rpc/src/v1/tests/mocked/personal.rs index af152ad0b8..ba20ad15ae 100644 --- a/rpc/src/v1/tests/mocked/personal.rs +++ b/rpc/src/v1/tests/mocked/personal.rs @@ -117,16 +117,25 @@ fn sign_and_send_transaction_with_invalid_password() { assert_eq!(tester.io.handle_request_sync(request.as_ref()), Some(response.into())); } +#[test] +fn send_transaction() { + sign_and_send_test("personal_sendTransaction"); +} + #[test] fn sign_and_send_transaction() { + sign_and_send_test("personal_signAndSendTransaction"); +} + +fn sign_and_send_test(method: &str) { let tester = setup(); let address = tester.accounts.new_account("password123").unwrap(); let request = r#"{ "jsonrpc": "2.0", - "method": "personal_sendTransaction", + "method": ""#.to_owned() + method + r#"", "params": [{ - "from": ""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", + "from": ""# + format!("0x{:?}", address).as_ref() + r#"", "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", "gas": "0x76c0", "gasPrice": "0x9184e72a000", diff --git a/rpc/src/v1/traits/personal.rs b/rpc/src/v1/traits/personal.rs index 2076d87f2c..a0a8cf1a43 100644 --- a/rpc/src/v1/traits/personal.rs +++ b/rpc/src/v1/traits/personal.rs @@ -42,5 +42,10 @@ build_rpc_trait! { /// Sends transaction and signs it in single call. The account is not unlocked in such case. #[rpc(meta, name = "personal_sendTransaction")] fn send_transaction(&self, Self::Metadata, TransactionRequest, String) -> BoxFuture; + + /// Deprecated alias for `personal_sendTransaction`. + #[rpc(meta, name = "personal_signAndSendTransaction")] + fn sign_and_send_transaction(&self, Self::Metadata, TransactionRequest, String) -> BoxFuture; + } } -- GitLab From efb95a59bec3033ab55d5377a526dfd9fbdb8a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 15 Feb 2017 16:58:42 +0100 Subject: [PATCH 042/246] Fixing evmbin compilation and added standard build. (#4561) --- .gitlab-ci.yml | 3 ++- Cargo.lock | 11 +++++++++++ Cargo.toml | 1 + evmbin/Cargo.toml | 2 +- evmbin/src/ext.rs | 6 +++--- evmbin/src/main.rs | 1 - 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 31f15c3061..207481b117 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -379,6 +379,7 @@ darwin: export PLATFORM=x86_64-apple-darwin cargo build -j 8 --features final --release #$CARGOFLAGS cargo build -j 8 --features final --release -p ethstore #$CARGOFLAGS + cargo build -j 8 --features final --release -p evmbin #$CARGOFLAGS rm -rf parity.md5 md5sum target/release/parity > parity.md5 export SHA3=$(target/release/parity tools hash target/release/parity) @@ -504,7 +505,7 @@ test-windows: - git submodule update --init --recursive script: - set RUST_BACKTRACE=1 - - echo cargo test --features json-tests -p rlp -p ethash -p ethcore -p ethcore-bigint -p ethcore-dapps -p ethcore-rpc -p ethcore-signer -p ethcore-util -p ethcore-network -p ethcore-io -p ethkey -p ethstore -p ethsync -p ethcore-ipc -p ethcore-ipc-tests -p ethcore-ipc-nano -p parity %CARGOFLAGS% --verbose --release + - echo cargo test --features json-tests -p rlp -p ethash -p ethcore -p ethcore-bigint -p ethcore-dapps -p ethcore-rpc -p ethcore-signer -p ethcore-util -p ethcore-network -p ethcore-io -p ethkey -p ethstore -p evmbin -p ethsync -p ethcore-ipc -p ethcore-ipc-tests -p ethcore-ipc-nano -p parity %CARGOFLAGS% --verbose --release tags: - rust-windows allow_failure: true diff --git a/Cargo.lock b/Cargo.lock index 3da15278e5..90ef54f8f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,7 @@ dependencies = [ "ethcore-stratum 1.6.0", "ethcore-util 1.6.0", "ethsync 1.6.0", + "evmbin 0.1.0", "fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", "isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -781,6 +782,16 @@ dependencies = [ "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "evmbin" +version = "0.1.0" +dependencies = [ + "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore 1.6.0", + "ethcore-util 1.6.0", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "evmjit" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index 4dac6a2212..e44852c742 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ ethcore-ipc-hypervisor = { path = "ipc/hypervisor" } ethcore-light = { path = "ethcore/light" } ethcore-logger = { path = "logger" } ethcore-stratum = { path = "stratum" } +evmbin = { path = "evmbin" } rlp = { path = "util/rlp" } rpc-cli = { path = "rpc_cli" } parity-rpc-client = { path = "rpc_client" } diff --git a/evmbin/Cargo.toml b/evmbin/Cargo.toml index ad2d69d572..98193a5c35 100644 --- a/evmbin/Cargo.toml +++ b/evmbin/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "evm" +name = "evmbin" description = "Parity's EVM implementation" version = "0.1.0" authors = ["Parity Technologies "] diff --git a/evmbin/src/ext.rs b/evmbin/src/ext.rs index 1b5f17c052..6492f4fdc0 100644 --- a/evmbin/src/ext.rs +++ b/evmbin/src/ext.rs @@ -31,7 +31,7 @@ pub struct FakeExt { impl Default for FakeExt { fn default() -> Self { FakeExt { - schedule: Schedule::new_homestead_gas_fix(), + schedule: Schedule::new_post_eip150(usize::max_value(), true, true, true), store: HashMap::new(), depth: 1, } @@ -51,8 +51,8 @@ impl Ext for FakeExt { unimplemented!(); } - fn exists_and_not_null(&self, address: &Address) -> bool { - unimplemented!(); + fn exists_and_not_null(&self, _address: &Address) -> bool { + unimplemented!(); } fn origin_balance(&self) -> U256 { diff --git a/evmbin/src/main.rs b/evmbin/src/main.rs index 5ba4e59507..6fbe5a2807 100644 --- a/evmbin/src/main.rs +++ b/evmbin/src/main.rs @@ -21,7 +21,6 @@ extern crate ethcore; extern crate rustc_serialize; extern crate docopt; -#[macro_use] extern crate ethcore_util as util; mod ext; -- GitLab From 06937be74b58420c4865ddc38314dee52ad85b17 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Wed, 15 Feb 2017 20:09:47 +0400 Subject: [PATCH 043/246] add libudev-dev in deb [ci-skip] --- scripts/deb-build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/deb-build.sh b/scripts/deb-build.sh index 682cbcbbf5..ec11c056a3 100644 --- a/scripts/deb-build.sh +++ b/scripts/deb-build.sh @@ -25,7 +25,7 @@ echo "Homepage: https://ethcore.io" >> $control echo "Vcs-Git: git://github.com/ethcore/parity.git" >> $control echo "Vcs-Browser: https://github.com/ethcore/parity" >> $control echo "Architecture: $1" >> $control -echo "Depends: libssl1.0.0 (>=1.0.0)" >> $control +echo "Depends: libssl1.0.0 (>=1.0.0), libudev-dev" >> $control echo "Description: Ethereum network client by Ethcore" >> $control #build .deb package -- GitLab From eb327338e820a36a2e7aa59ec85a909c70203f85 Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Wed, 15 Feb 2017 18:07:30 +0100 Subject: [PATCH 044/246] Tests and grumbles --- ipfs/src/error.rs | 6 ++ ipfs/src/handler.rs | 183 ++++++++++++++++++++++++++++++++++---------- ipfs/src/lib.rs | 20 ++--- parity/run.rs | 1 - 4 files changed, 160 insertions(+), 50 deletions(-) diff --git a/ipfs/src/error.rs b/ipfs/src/error.rs index 6513c6da0c..774763786e 100644 --- a/ipfs/src/error.rs +++ b/ipfs/src/error.rs @@ -28,6 +28,7 @@ pub enum ServerError { Other(hyper::error::Error), } +#[derive(Debug, PartialEq)] pub enum Error { CidParsingFailed, UnsupportedHash, @@ -37,6 +38,8 @@ pub enum Error { StateRootNotFound, } +/// Convert Error into Out, handy when switching from Rust's Result-based +/// error handling to Hyper's request handling. impl From for Out { fn from(err: Error) -> Out { use self::Error::*; @@ -52,18 +55,21 @@ impl From for Out { } } +/// Convert Content ID errors. impl From for Error { fn from(_: cid::Error) -> Error { Error::CidParsingFailed } } +/// Convert multihash errors (multihash being part of CID). impl From for Error { fn from(_: multihash::Error) -> Error { Error::CidParsingFailed } } +/// Handle IO errors (ports taken when starting the server). impl From<::std::io::Error> for ServerError { fn from(err: ::std::io::Error) -> ServerError { ServerError::IoError(err) diff --git a/ipfs/src/handler.rs b/ipfs/src/handler.rs index 0673826c25..7dd83da474 100644 --- a/ipfs/src/handler.rs +++ b/ipfs/src/handler.rs @@ -19,7 +19,6 @@ use error::{Error, Result}; use cid::{ToCid, Codec}; use std::sync::Arc; -use std::ops::Deref; use multihash::Hash; use hyper::Next; use util::{Bytes, H256}; @@ -27,12 +26,15 @@ use ethcore::client::{BlockId, TransactionId, BlockChainClient}; type Reason = &'static str; +/// Keeps the state of the response to send out +#[derive(Debug, PartialEq)] pub enum Out { OctetStream(Bytes), NotFound(Reason), Bad(Reason), } +/// Request/response handler pub struct IpfsHandler { client: Arc, out: Out, @@ -42,34 +44,34 @@ impl IpfsHandler { pub fn new(client: Arc) -> Self { IpfsHandler { client: client, - out: Out::NotFound("Route not found") + out: Out::Bad("Invalid Request") } } + /// Exposes the outgoing state. The outgoing state should be immutable from the outside. pub fn out(&self) -> &Out { &self.out } + /// Route path + query string to a specialized method pub fn route(&mut self, path: &str, query: Option<&str>) -> Next { - let result = match path { - "/api/v0/block/get" => self.route_cid(query), - _ => return Next::write(), - }; + self.out = match path { + "/api/v0/block/get" => { + let arg = query.and_then(|q| get_param(q, "arg")).unwrap_or(""); - match result { - Ok(_) => Next::write(), - Err(err) => { - self.out = err.into(); + self.route_cid(arg).unwrap_or_else(Into::into) + }, - Next::write() - } - } - } + _ => Out::NotFound("Route not found") + }; - fn route_cid(&mut self, query: Option<&str>) -> Result<()> { - let query = query.unwrap_or(""); + Next::write() + } - let cid = get_param(&query, "arg").ok_or(Error::CidParsingFailed)?.to_cid()?; + /// Attempt to read Content ID from `arg` query parameter, get a hash and + /// route further by the CID's codec. + fn route_cid(&self, cid: &str) -> Result { + let cid = cid.to_cid()?; let mh = multihash::decode(&cid.hash)?; @@ -78,51 +80,47 @@ impl IpfsHandler { let hash: H256 = mh.digest.into(); match cid.codec { - Codec::EthereumBlock => self.get_block(hash), - Codec::EthereumBlockList => self.get_block_list(hash), - Codec::EthereumTx => self.get_transaction(hash), - Codec::EthereumStateTrie => self.get_state_trie(hash), + Codec::EthereumBlock => self.block(hash), + Codec::EthereumBlockList => self.block_list(hash), + Codec::EthereumTx => self.transaction(hash), + Codec::EthereumStateTrie => self.state_trie(hash), _ => return Err(Error::UnsupportedCid), } } - fn get_block(&mut self, hash: H256) -> Result<()> { + /// Get block header by hash as raw binary. + fn block(&self, hash: H256) -> Result { let block_id = BlockId::Hash(hash); let block = self.client.block_header(block_id).ok_or(Error::BlockNotFound)?; - self.out = Out::OctetStream(block.into_inner()); - - Ok(()) + Ok(Out::OctetStream(block.into_inner())) } - fn get_block_list(&mut self, hash: H256) -> Result<()> { - let ommers = self.client.find_uncles(&hash).ok_or(Error::BlockNotFound)?; - - self.out = Out::OctetStream(rlp::encode(&ommers).to_vec()); + /// Get list of block ommers by hash as raw binary. + fn block_list(&self, hash: H256) -> Result { + let uncles = self.client.find_uncles(&hash).ok_or(Error::BlockNotFound)?; - Ok(()) + Ok(Out::OctetStream(rlp::encode(&uncles).to_vec())) } - fn get_transaction(&mut self, hash: H256) -> Result<()> { + /// Get transaction by hash and return as raw binary. + fn transaction(&self, hash: H256) -> Result { let tx_id = TransactionId::Hash(hash); let tx = self.client.transaction(tx_id).ok_or(Error::TransactionNotFound)?; - self.out = Out::OctetStream(rlp::encode(tx.deref()).to_vec()); - - Ok(()) + Ok(Out::OctetStream(rlp::encode(&*tx).to_vec())) } - fn get_state_trie(&mut self, hash: H256) -> Result<()> { + /// Get state trie node by hash and return as raw binary. + fn state_trie(&self, hash: H256) -> Result { let data = self.client.state_data(&hash).ok_or(Error::StateRootNotFound)?; - self.out = Out::OctetStream(data); - - Ok(()) + Ok(Out::OctetStream(data)) } } /// Get a query parameter's value by name. -pub fn get_param<'a>(query: &'a str, name: &str) -> Option<&'a str> { +fn get_param<'a>(query: &'a str, name: &str) -> Option<&'a str> { query.split('&') .find(|part| part.starts_with(name) && part[name.len()..].starts_with("=")) .map(|part| &part[name.len() + 1..]) @@ -131,8 +129,13 @@ pub fn get_param<'a>(query: &'a str, name: &str) -> Option<&'a str> { #[cfg(test)] mod tests { use super::*; + use ethcore::client::TestBlockChainClient; + + fn get_mocked_handler() -> IpfsHandler { + IpfsHandler::new(Arc::new(TestBlockChainClient::new())) + } - #[test] + #[test] fn test_get_param() { let query = "foo=100&bar=200&qux=300"; @@ -141,5 +144,105 @@ mod tests { assert_eq!(get_param(query, "qux"), Some("300")); assert_eq!(get_param(query, "bar="), None); assert_eq!(get_param(query, "200"), None); + assert_eq!(get_param("", "foo"), None); + assert_eq!(get_param("foo", "foo"), None); + assert_eq!(get_param("foo&bar", "foo"), None); + assert_eq!(get_param("bar&foo", "foo"), None); + } + + #[test] + fn cid_route_block() { + let handler = get_mocked_handler(); + + // `eth-block` with Keccak-256 + let cid = "z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM"; + + assert_eq!(Err(Error::BlockNotFound), handler.route_cid(cid)); + } + + #[test] + fn cid_route_block_list() { + let handler = get_mocked_handler(); + + // `eth-block-list` with Keccak-256 + let cid = "z43c7o7FsNxqdLJW8Ucj19tuCALtnmUb2EkDptj4W6xSkFVTqWs"; + + assert_eq!(Err(Error::BlockNotFound), handler.route_cid(cid)); + } + + #[test] + fn cid_route_tx() { + let handler = get_mocked_handler(); + + // `eth-tx` with Keccak-256 + let cid = "z44VCrqbpbPcb8SUBc8Tba4EaKuoDz2grdEoQXx4TP7WYh9ZGBu"; + + assert_eq!(Err(Error::TransactionNotFound), handler.route_cid(cid)); + } + + #[test] + fn cid_route_state_trie() { + let handler = get_mocked_handler(); + + // `eth-state-trie` with Keccak-256 + let cid = "z45oqTS7kR2n2peRGJQ4VCJEeaG9sorqcCyfmznZPJM7FMdhQCT"; + + assert_eq!(Err(Error::StateRootNotFound), handler.route_cid(&cid)); + } + + #[test] + fn cid_route_invalid_hash() { + let handler = get_mocked_handler(); + + // `eth-block` with SHA3-256 hash + let cid = "z43Aa9gr1MM7TENJh4Em9d9Ttr7p3UcfyMpNei6WLVeCmSEPu8F"; + + assert_eq!(Err(Error::UnsupportedHash), handler.route_cid(cid)); + } + + #[test] + fn cid_route_invalid_codec() { + let handler = get_mocked_handler(); + + // `bitcoin-block` with Keccak-256 + let cid = "z4HFyHvb8CarYARyxz4cCcPaciduXd49TFPCKLhYmvNxf7Auvwu"; + + assert_eq!(Err(Error::UnsupportedCid), handler.route_cid(&cid)); + } + + #[test] + fn route_block() { + let mut handler = get_mocked_handler(); + + let _ = handler.route("/api/v0/block/get", Some("arg=z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM")); + + assert_eq!(handler.out(), &Out::NotFound("Block not found")); + } + + #[test] + fn route_block_missing_query() { + let mut handler = get_mocked_handler(); + + let _ = handler.route("/api/v0/block/get", None); + + assert_eq!(handler.out(), &Out::Bad("CID parsing failed")); + } + + #[test] + fn route_block_invalid_query() { + let mut handler = get_mocked_handler(); + + let _ = handler.route("/api/v0/block/get", Some("arg=foobarz43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM")); + + assert_eq!(handler.out(), &Out::Bad("CID parsing failed")); + } + + #[test] + fn route_invalid_route() { + let mut handler = get_mocked_handler(); + + let _ = handler.route("/foo/bar/baz", Some("arg=z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM")); + + assert_eq!(handler.out(), &Out::NotFound("Route not found")); } } diff --git a/ipfs/src/lib.rs b/ipfs/src/lib.rs index 341d80ab8c..2a427cd14c 100644 --- a/ipfs/src/lib.rs +++ b/ipfs/src/lib.rs @@ -34,6 +34,7 @@ use hyper::header::{ContentLength, ContentType}; use hyper::{Next, Encoder, Decoder, Method, RequestUri, StatusCode}; use ethcore::client::BlockChainClient; +/// Implement Hyper's HTTP handler impl Handler for IpfsHandler { fn on_request(&mut self, req: Request) -> Next { if *req.method() != Method::Get { @@ -109,15 +110,16 @@ impl Handler for IpfsHandler { pub fn start_server(client: Arc) -> Result { let addr = "0.0.0.0:5001".parse().expect("can't fail on static input; qed"); - hyper::Server::http(&addr)? - .handle(move |_| IpfsHandler::new(client.clone())) - .map(|(listening, srv)| { + Ok( + hyper::Server::http(&addr)? + .handle(move |_| IpfsHandler::new(client.clone())) + .map(|(listening, srv)| { - ::std::thread::spawn(move || { - srv.run(); - }); + ::std::thread::spawn(move || { + srv.run(); + }); - listening - }) - .map_err(Into::into) + listening + })? + ) } diff --git a/parity/run.rs b/parity/run.rs index 728505068a..ac6f0dcf90 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -421,7 +421,6 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R }; let signer_server = signer::start(cmd.signer_conf.clone(), signer_deps)?; - // the ipfs server let ipfs_server = ipfs::start_server(client.clone())?; -- GitLab From ff2c4d117815869746649ca48e0d8739494d0b5e Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Wed, 15 Feb 2017 21:14:07 +0400 Subject: [PATCH 045/246] add tools add `evmbin`, `ethstore`, `ethkey` to the CI --- .gitlab-ci.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 207481b117..37df0bc054 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,11 +22,18 @@ linux-stable: - triggers script: - cargo build -j $(nproc) --release --features final $CARGOFLAGS + - cargo build -j $(nproc) --release -p evmbin ethstore ethkey - strip target/release/parity + - strip target/release/evmbin + - 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/evmbin deb/usr/bin/evmbin + - 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" @@ -379,7 +386,6 @@ darwin: export PLATFORM=x86_64-apple-darwin cargo build -j 8 --features final --release #$CARGOFLAGS cargo build -j 8 --features final --release -p ethstore #$CARGOFLAGS - cargo build -j 8 --features final --release -p evmbin #$CARGOFLAGS rm -rf parity.md5 md5sum target/release/parity > parity.md5 export SHA3=$(target/release/parity tools hash target/release/parity) @@ -505,7 +511,7 @@ test-windows: - git submodule update --init --recursive script: - set RUST_BACKTRACE=1 - - echo cargo test --features json-tests -p rlp -p ethash -p ethcore -p ethcore-bigint -p ethcore-dapps -p ethcore-rpc -p ethcore-signer -p ethcore-util -p ethcore-network -p ethcore-io -p ethkey -p ethstore -p evmbin -p ethsync -p ethcore-ipc -p ethcore-ipc-tests -p ethcore-ipc-nano -p parity %CARGOFLAGS% --verbose --release + - echo cargo test --features json-tests -p rlp -p ethash -p ethcore -p ethcore-bigint -p ethcore-dapps -p ethcore-rpc -p ethcore-signer -p ethcore-util -p ethcore-network -p ethcore-io -p ethkey -p ethstore -p ethsync -p ethcore-ipc -p ethcore-ipc-tests -p ethcore-ipc-nano -p parity %CARGOFLAGS% --verbose --release tags: - rust-windows allow_failure: true -- GitLab From d005410e1ab11b4260574df04d756f416fdc0994 Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Wed, 15 Feb 2017 18:26:35 +0100 Subject: [PATCH 046/246] No .expect on mime types --- Cargo.lock | 1 + ipfs/Cargo.toml | 5 +++-- ipfs/src/lib.rs | 23 +++++++++++++++-------- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1b9be74ed7..9bc78e975c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1603,6 +1603,7 @@ dependencies = [ "ethcore 1.6.0", "ethcore-util 1.6.0", "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", + "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "multihash 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.1.0", ] diff --git a/ipfs/Cargo.toml b/ipfs/Cargo.toml index 46a1dd3aac..d1798b4257 100644 --- a/ipfs/Cargo.toml +++ b/ipfs/Cargo.toml @@ -9,6 +9,7 @@ authors = ["Parity Technologies "] ethcore = { path = "../ethcore" } ethcore-util = { path = "../util" } rlp = { path = "../util/rlp" } +mime = "0.2" hyper = { default-features = false, git = "https://github.com/ethcore/hyper" } -cid = "~0.2.0" -multihash = "~0.5.0" +cid = "0.2" +multihash = "0.5" diff --git a/ipfs/src/lib.rs b/ipfs/src/lib.rs index 2a427cd14c..776dfe85d2 100644 --- a/ipfs/src/lib.rs +++ b/ipfs/src/lib.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +#[macro_use] +extern crate mime; extern crate hyper; extern crate multihash; extern crate cid; @@ -58,11 +60,18 @@ impl Handler for IpfsHandler { match *self.out() { OctetStream(ref bytes) => { - let headers = res.headers_mut(); + use mime::{Mime, TopLevel, SubLevel}; - headers.set(ContentLength(bytes.len() as u64)); - headers.set(ContentType("application/octet-stream".parse() - .expect("Static content type; qed"))); + // `OctetStream` is not a valid variant, so need to construct + // the type manually. + let content_type = Mime( + TopLevel::Application, + SubLevel::Ext("octet-stream".into()), + vec![] + ); + + res.headers_mut().set(ContentLength(bytes.len() as u64)); + res.headers_mut().set(ContentType(content_type)); Next::write() }, @@ -70,8 +79,7 @@ impl Handler for IpfsHandler { res.set_status(StatusCode::NotFound); res.headers_mut().set(ContentLength(reason.len() as u64)); - res.headers_mut().set(ContentType("text/plain".parse() - .expect("Static content type; qed"))); + res.headers_mut().set(ContentType(mime!(Text/Plain))); Next::write() }, @@ -79,8 +87,7 @@ impl Handler for IpfsHandler { res.set_status(StatusCode::BadRequest); res.headers_mut().set(ContentLength(reason.len() as u64)); - res.headers_mut().set(ContentType("text/plain".parse() - .expect("Static content type; qed"))); + res.headers_mut().set(ContentType(mime!(Text/Plain))); Next::write() } -- GitLab From 34d8498a2c29d66e5b86cca7f43154b8fa8b6168 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Wed, 15 Feb 2017 21:37:23 +0400 Subject: [PATCH 047/246] clean Docker remove RUST tools from Docker [ci-ckip] --- docker/hub/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/hub/Dockerfile b/docker/hub/Dockerfile index 7ab94b3cc2..6eaedf62e4 100644 --- a/docker/hub/Dockerfile +++ b/docker/hub/Dockerfile @@ -63,6 +63,7 @@ RUN git clone https://github.com/ethcore/parity && \ strip /build/parity/target/release/parity RUN file /build/parity/target/release/parity +RUN rm -rf /root/.cargo&&rm -rf /root/.multirust&&rm -rf /root/.rustup EXPOSE 8080 8545 8180 ENTRYPOINT ["/build/parity/target/release/parity"] -- GitLab From c808954413efcb9918209ab3ba716b5ed5d84c6d Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Wed, 15 Feb 2017 21:40:23 +0400 Subject: [PATCH 048/246] clean Docker [ci-skip] --- docker/hub/Dockerfile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docker/hub/Dockerfile b/docker/hub/Dockerfile index 6eaedf62e4..f78bbd217b 100644 --- a/docker/hub/Dockerfile +++ b/docker/hub/Dockerfile @@ -62,8 +62,9 @@ RUN git clone https://github.com/ethcore/parity && \ ls /build/parity/target/release/parity && \ strip /build/parity/target/release/parity -RUN file /build/parity/target/release/parity -RUN rm -rf /root/.cargo&&rm -rf /root/.multirust&&rm -rf /root/.rustup +RUN file /build/parity/target/release/parity&&cp /build/parity/target/release/parity /parity +#cleanup Docker image +RUN rm -rf /root/.cargo&&rm -rf /root/.multirust&&rm -rf /root/.rustup&&rm -rf /build EXPOSE 8080 8545 8180 -ENTRYPOINT ["/build/parity/target/release/parity"] +ENTRYPOINT ["/parity"] -- GitLab From 9cfa27830cd9a99fe43e5861ca1030db12d2a1dc Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Wed, 15 Feb 2017 19:25:57 +0100 Subject: [PATCH 049/246] Write output as chunks --- ipfs/src/handler.rs | 24 ++++++------ ipfs/src/lib.rs | 91 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 91 insertions(+), 24 deletions(-) diff --git a/ipfs/src/handler.rs b/ipfs/src/handler.rs index 7dd83da474..5197e8a1a8 100644 --- a/ipfs/src/handler.rs +++ b/ipfs/src/handler.rs @@ -36,23 +36,25 @@ pub enum Out { /// Request/response handler pub struct IpfsHandler { + /// Reference to the Blockchain Client client: Arc, - out: Out, + + /// Response to send out + pub out: Out, + + /// How many bytes from the response have been written + pub out_progress: usize, } impl IpfsHandler { pub fn new(client: Arc) -> Self { IpfsHandler { client: client, - out: Out::Bad("Invalid Request") + out: Out::Bad("Invalid Request"), + out_progress: 0, } } - /// Exposes the outgoing state. The outgoing state should be immutable from the outside. - pub fn out(&self) -> &Out { - &self.out - } - /// Route path + query string to a specialized method pub fn route(&mut self, path: &str, query: Option<&str>) -> Next { self.out = match path { @@ -216,7 +218,7 @@ mod tests { let _ = handler.route("/api/v0/block/get", Some("arg=z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM")); - assert_eq!(handler.out(), &Out::NotFound("Block not found")); + assert_eq!(handler.out, Out::NotFound("Block not found")); } #[test] @@ -225,7 +227,7 @@ mod tests { let _ = handler.route("/api/v0/block/get", None); - assert_eq!(handler.out(), &Out::Bad("CID parsing failed")); + assert_eq!(handler.out, Out::Bad("CID parsing failed")); } #[test] @@ -234,7 +236,7 @@ mod tests { let _ = handler.route("/api/v0/block/get", Some("arg=foobarz43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM")); - assert_eq!(handler.out(), &Out::Bad("CID parsing failed")); + assert_eq!(handler.out, Out::Bad("CID parsing failed")); } #[test] @@ -243,6 +245,6 @@ mod tests { let _ = handler.route("/foo/bar/baz", Some("arg=z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM")); - assert_eq!(handler.out(), &Out::NotFound("Route not found")); + assert_eq!(handler.out, Out::NotFound("Route not found")); } } diff --git a/ipfs/src/lib.rs b/ipfs/src/lib.rs index 776dfe85d2..5c95fe88e7 100644 --- a/ipfs/src/lib.rs +++ b/ipfs/src/lib.rs @@ -27,6 +27,7 @@ extern crate ethcore_util as util; mod error; mod handler; +use std::io::Write; use std::sync::Arc; use error::ServerError; use handler::{IpfsHandler, Out}; @@ -58,7 +59,7 @@ impl Handler for IpfsHandler { fn on_response(&mut self, res: &mut Response) -> Next { use Out::*; - match *self.out() { + match self.out { OctetStream(ref bytes) => { use mime::{Mime, TopLevel, SubLevel}; @@ -97,20 +98,33 @@ impl Handler for IpfsHandler { fn on_response_writable(&mut self, transport: &mut Encoder) -> Next { use Out::*; - match *self.out() { - OctetStream(ref bytes) => { - // Nothing to do here - let _ = transport.write(&bytes); + // Get the data to write as a byte slice + let data = match self.out { + OctetStream(ref bytes) => &bytes, + NotFound(reason) | Bad(reason) => reason.as_bytes(), + }; - Next::end() - }, - NotFound(reason) | Bad(reason) => { - // Nothing to do here - let _ = transport.write(reason.as_bytes()); + write_chunk(transport, &mut self.out_progress, data) + } +} - Next::end() - } - } +fn write_chunk(transport: &mut W, progress: &mut usize, data: &[u8]) -> Next { + // Skip any bytes that have already been written + let chunk = &data[*progress..]; + + // Write an get written count + let written = match transport.write(chunk) { + Ok(written) => written, + Err(_) => return Next::end(), + }; + + *progress += written; + + // Close the connection if the entire chunk has been written, otherwise increment progress + if written < chunk.len() { + Next::write() + } else { + Next::end() } } @@ -130,3 +144,54 @@ pub fn start_server(client: Arc) -> Result = Cursor::new(&mut buf); + let _ = write_chunk(&mut transport, &mut progress, b"foobar"); + } + + assert_eq!(*b"foo", buf); + assert_eq!(3, progress); + + { + let mut transport: Cursor<&mut [u8]> = Cursor::new(&mut buf); + let _ = write_chunk(&mut transport, &mut progress, b"foobar"); + } + + assert_eq!(*b"bar", buf); + assert_eq!(6, progress); + } +} -- GitLab From 451cf42452e28b25bc8f5535ad38b8cb2d36267c Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Wed, 15 Feb 2017 20:29:29 +0100 Subject: [PATCH 050/246] Adding CLI flags for IPFS --- parity/cli/config.full.toml | 4 ++++ parity/cli/config.toml | 4 ++++ parity/cli/mod.rs | 23 ++++++++++++++++++++++- parity/cli/usage.txt | 3 +++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index 5e6bc367a0..b527a9b65d 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -67,6 +67,10 @@ path = "$HOME/.parity/dapps" user = "test_user" pass = "test_pass" +[ipfs] +disable = true +port = 5001 + [mining] author = "0xdeadbeefcafe0000000000000000000000000001" engine_signer = "0xdeadbeefcafe0000000000000000000000000001" diff --git a/parity/cli/config.toml b/parity/cli/config.toml index 608999799a..66886e71ed 100644 --- a/parity/cli/config.toml +++ b/parity/cli/config.toml @@ -38,6 +38,10 @@ port = 8080 user = "username" pass = "password" +[ipfs] +disable = true +port = 5001 + [mining] author = "0xdeadbeefcafe0000000000000000000000000001" engine_signer = "0xdeadbeefcafe0000000000000000000000000001" diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 7ac7f8c8d1..4cb7ff4e56 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -189,6 +189,12 @@ usage! { or |c: &Config| otry!(c.dapps).pass.clone().map(Some), flag_dapps_apis_all: bool = false, or |_| None, + // IPFS + flag_ipfs_off: bool = true, + or |c: &Config| otry!(c.ipfs).disable.clone(), + flag_ipfs_port: u16 = 5001u16, + or |c: &Config| otry!(c.ipfs).port.clone(), + // -- Sealing/Mining Options flag_author: Option = None, or |c: &Config| otry!(c.mining).author.clone().map(Some), @@ -321,6 +327,7 @@ struct Config { rpc: Option, ipc: Option, dapps: Option, + ipfs: Option, mining: Option, footprint: Option, snapshots: Option, @@ -409,6 +416,12 @@ struct Dapps { pass: Option, } +#[derive(Default, Debug, PartialEq, RustcDecodable)] +struct Ipfs { + disable: Option, + port: Option, +} + #[derive(Default, Debug, PartialEq, RustcDecodable)] struct Mining { author: Option, @@ -482,7 +495,7 @@ struct Misc { mod tests { use super::{ Args, ArgsError, - Config, Operating, Account, Ui, Network, Rpc, Ipc, Dapps, Mining, Footprint, Snapshots, VM, Misc + Config, Operating, Account, Ui, Network, Rpc, Ipc, Dapps, Ipfs, Mining, Footprint, Snapshots, VM, Misc }; use toml; @@ -637,6 +650,10 @@ mod tests { flag_dapps_pass: Some("test_pass".into()), flag_dapps_apis_all: false, + // IPFS + flag_ipfs_off: true, + flag_ipfs_port: 5001u16, + // -- Sealing/Mining Options flag_author: Some("0xdeadbeefcafe0000000000000000000000000001".into()), flag_engine_signer: Some("0xdeadbeefcafe0000000000000000000000000001".into()), @@ -822,6 +839,10 @@ mod tests { user: Some("username".into()), pass: Some("password".into()) }), + ipfs: Some(Ipfs { + disable: Some(true), + port: Some(5001) + }), mining: Some(Mining { author: Some("0xdeadbeefcafe0000000000000000000000000001".into()), engine_signer: Some("0xdeadbeefcafe0000000000000000000000000001".into()), diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index 90c207378e..c13e0dee7f 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -175,6 +175,9 @@ API and Console Options: --dapps-apis-all Expose all possible RPC APIs on Dapps port. WARNING: INSECURE. Used only for development. (default: {flag_dapps_apis_all}) + --no-ipfs Disable IPFS-compatible HTTP API. (default: {flag_ipfs_off}) + --ipfs-port PORT Configure on which port the IPFS HTTP API should listen. + (default: {flag_ipfs_port}) Sealing/Mining Options: --author ADDRESS Specify the block author (aka "coinbase") address -- GitLab From c14ecefa1da01c3448a2202cd66748ef90f725a9 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 16 Feb 2017 13:47:51 +0100 Subject: [PATCH 051/246] Explicitly set seconds to 0 from selector (#4559) * Explicitly set seconds/milli to 0 * Use condition time & block setters consistently * Fix failing test * test for 0 ms & sec * It cannot hurt, clone date before setting * Prettier date test constants (OCD) --- js/src/ui/GasPriceEditor/store.js | 11 ++++++++--- js/src/ui/GasPriceEditor/store.spec.js | 14 +++++++++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/js/src/ui/GasPriceEditor/store.js b/js/src/ui/GasPriceEditor/store.js index 62c3f840c7..573654c512 100644 --- a/js/src/ui/GasPriceEditor/store.js +++ b/js/src/ui/GasPriceEditor/store.js @@ -81,11 +81,11 @@ export default class GasPriceEditor { switch (conditionType) { case CONDITIONS.BLOCK: - this.condition = Object.assign({}, this.condition, { block: this.blockNumber || 1 }); + this.setConditionBlockNumber(this.blockNumber || 1); break; case CONDITIONS.TIME: - this.condition = Object.assign({}, this.condition, { time: new Date() }); + this.setConditionDateTime(new Date()); break; case CONDITIONS.NONE: @@ -103,7 +103,12 @@ export default class GasPriceEditor { }); } - @action setConditionDateTime = (time) => { + @action setConditionDateTime = (_time) => { + const time = new Date(_time); + + time.setMilliseconds(0); // ignored by/not passed to Parity + time.setSeconds(0); // current time selector doesn't allow seconds + this.condition = Object.assign({}, this.condition, { time }); } diff --git a/js/src/ui/GasPriceEditor/store.spec.js b/js/src/ui/GasPriceEditor/store.spec.js index 36d02c8c42..4580748ea3 100644 --- a/js/src/ui/GasPriceEditor/store.spec.js +++ b/js/src/ui/GasPriceEditor/store.spec.js @@ -162,9 +162,17 @@ describe('ui/GasPriceEditor/Store', () => { }); describe('setConditionDateTime', () => { - it('sets the datatime', () => { - store.setConditionDateTime('testingDateTime'); - expect(store.condition.time).to.equal('testingDateTime'); + const BASEDATE = '1973-06-11 07:52'; + const ZEROTIME = new Date(BASEDATE).getTime(); + + it('sets the datetime', () => { + store.setConditionDateTime(new Date(`${BASEDATE}:00.000`)); + expect(store.condition.time.getTime()).to.equal(ZEROTIME); + }); + + it('zeros both seconds and miliseconds', () => { + store.setConditionDateTime(new Date(`${BASEDATE}:12.345`)); + expect(store.condition.time.getTime()).to.equal(ZEROTIME); }); }); -- GitLab From 39d4e4607304ead82dd478a8d0dc8a8ce9d24dcb Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 16 Feb 2017 12:59:57 +0000 Subject: [PATCH 052/246] [ci skip] js-precompiled 20170216-125519 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 90ef54f8f9..c51c85ee0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1617,7 +1617,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#3687825172c852e0ad4bea85ec3119e9fedb8414" +source = "git+https://github.com/ethcore/js-precompiled.git#8cfa973a48243b57279f2f0b2cbe3f010c6c5a34" 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 41274bbcc4..cdc8611940 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.88", + "version": "0.3.89", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", -- GitLab From ad8e3f023064e7ae4ef827fde9fa96d966a61199 Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Thu, 16 Feb 2017 14:41:33 +0100 Subject: [PATCH 053/246] Added CLI flags --- ipfs/src/lib.rs | 5 +++-- parity/cli/config.full.toml | 2 +- parity/cli/config.toml | 2 +- parity/cli/mod.rs | 14 +++++++------- parity/cli/usage.txt | 6 +++--- parity/configuration.rs | 11 +++++++++++ parity/ipfs.rs | 16 ++++++++++++++++ parity/main.rs | 3 ++- parity/run.rs | 6 +++++- 9 files changed, 49 insertions(+), 16 deletions(-) create mode 100644 parity/ipfs.rs diff --git a/ipfs/src/lib.rs b/ipfs/src/lib.rs index 5c95fe88e7..e497faed74 100644 --- a/ipfs/src/lib.rs +++ b/ipfs/src/lib.rs @@ -29,6 +29,7 @@ mod handler; use std::io::Write; use std::sync::Arc; +use std::net::{SocketAddr, IpAddr, Ipv4Addr}; use error::ServerError; use handler::{IpfsHandler, Out}; use hyper::server::{Listening, Handler, Request, Response}; @@ -128,8 +129,8 @@ fn write_chunk(transport: &mut W, progress: &mut usize, data: &[u8]) - } } -pub fn start_server(client: Arc) -> Result { - let addr = "0.0.0.0:5001".parse().expect("can't fail on static input; qed"); +pub fn start_server(port: u16, client: Arc) -> Result { + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), port); Ok( hyper::Server::http(&addr)? diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index b527a9b65d..0983bf7924 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -68,7 +68,7 @@ user = "test_user" pass = "test_pass" [ipfs] -disable = true +enable = false port = 5001 [mining] diff --git a/parity/cli/config.toml b/parity/cli/config.toml index 66886e71ed..288f3b2ed8 100644 --- a/parity/cli/config.toml +++ b/parity/cli/config.toml @@ -39,7 +39,7 @@ user = "username" pass = "password" [ipfs] -disable = true +enable = false port = 5001 [mining] diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 4cb7ff4e56..a416aa4ce7 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -190,9 +190,9 @@ usage! { flag_dapps_apis_all: bool = false, or |_| None, // IPFS - flag_ipfs_off: bool = true, - or |c: &Config| otry!(c.ipfs).disable.clone(), - flag_ipfs_port: u16 = 5001u16, + flag_ipfs_api: bool = false, + or |c: &Config| otry!(c.ipfs).enable.clone(), + flag_ipfs_api_port: u16 = 5001u16, or |c: &Config| otry!(c.ipfs).port.clone(), // -- Sealing/Mining Options @@ -418,7 +418,7 @@ struct Dapps { #[derive(Default, Debug, PartialEq, RustcDecodable)] struct Ipfs { - disable: Option, + enable: Option, port: Option, } @@ -651,8 +651,8 @@ mod tests { flag_dapps_apis_all: false, // IPFS - flag_ipfs_off: true, - flag_ipfs_port: 5001u16, + flag_ipfs_api: false, + flag_ipfs_api_port: 5001u16, // -- Sealing/Mining Options flag_author: Some("0xdeadbeefcafe0000000000000000000000000001".into()), @@ -840,7 +840,7 @@ mod tests { pass: Some("password".into()) }), ipfs: Some(Ipfs { - disable: Some(true), + enable: Some(false), port: Some(5001) }), mining: Some(Mining { diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index c13e0dee7f..fd19a80043 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -175,9 +175,9 @@ API and Console Options: --dapps-apis-all Expose all possible RPC APIs on Dapps port. WARNING: INSECURE. Used only for development. (default: {flag_dapps_apis_all}) - --no-ipfs Disable IPFS-compatible HTTP API. (default: {flag_ipfs_off}) - --ipfs-port PORT Configure on which port the IPFS HTTP API should listen. - (default: {flag_ipfs_port}) + --ipfs-api Enable IPFS-compatible HTTP API. (default: {flag_ipfs_api}) + --ipfs-api-port PORT Configure on which port the IPFS HTTP API should listen. + (default: {flag_ipfs_api_port}) Sealing/Mining Options: --author ADDRESS Specify the block author (aka "coinbase") address diff --git a/parity/configuration.rs b/parity/configuration.rs index 349e576796..34fea453d6 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -37,6 +37,7 @@ use params::{ResealPolicy, AccountsConfig, GasPricerConfig, MinerExtras}; use ethcore_logger::Config as LogConfig; use dir::{self, Directories, default_hypervisor_path, default_local_path, default_data_path}; use dapps::Configuration as DappsConfiguration; +use ipfs::Configuration as IpfsConfiguration; use signer::{Configuration as SignerConfiguration}; use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack}; use run::RunCmd; @@ -118,6 +119,7 @@ impl Configuration { let geth_compatibility = self.args.flag_geth; let ui_address = self.ui_port().map(|port| (self.ui_interface(), port)); let dapps_conf = self.dapps_config(); + let ipfs_conf = self.ipfs_config(); let signer_conf = self.signer_config(); let format = self.format()?; @@ -342,6 +344,7 @@ impl Configuration { ui_address: ui_address, net_settings: self.network_settings(), dapps_conf: dapps_conf, + ipfs_conf: ipfs_conf, signer_conf: signer_conf, dapp: self.dapp_to_open()?, ui: self.args.cmd_ui, @@ -539,6 +542,13 @@ impl Configuration { } } + fn ipfs_config(&self) -> IpfsConfiguration { + IpfsConfiguration { + enabled: self.args.flag_ipfs_api, + port: self.args.flag_ipfs_api_port, + } + } + fn dapp_to_open(&self) -> Result, String> { if !self.args.cmd_dapp { return Ok(None); @@ -1101,6 +1111,7 @@ mod tests { ui_address: Some(("127.0.0.1".into(), 8180)), net_settings: Default::default(), dapps_conf: Default::default(), + ipfs_conf: Default::default(), signer_conf: Default::default(), ui: false, dapp: None, diff --git a/parity/ipfs.rs b/parity/ipfs.rs new file mode 100644 index 0000000000..ed7b594500 --- /dev/null +++ b/parity/ipfs.rs @@ -0,0 +1,16 @@ +pub use parity_ipfs::start_server; + +#[derive(Debug, PartialEq, Clone)] +pub struct Configuration { + pub enabled: bool, + pub port: u16, +} + +impl Default for Configuration { + fn default() -> Self { + Configuration { + enabled: false, + port: 5001, + } + } +} diff --git a/parity/main.rs b/parity/main.rs index 199dff4b47..b9ec461365 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -56,7 +56,7 @@ extern crate ethcore_signer; extern crate ethcore_util as util; extern crate ethsync; extern crate parity_hash_fetch as hash_fetch; -extern crate parity_ipfs as ipfs; +extern crate parity_ipfs; extern crate parity_reactor; extern crate parity_updater as updater; extern crate rpc_cli; @@ -88,6 +88,7 @@ mod cache; mod cli; mod configuration; mod dapps; +mod ipfs; mod deprecated; mod dir; mod helpers; diff --git a/parity/run.rs b/parity/run.rs index ac6f0dcf90..df9fc7384c 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -94,6 +94,7 @@ pub struct RunCmd { pub ui_address: Option<(String, u16)>, pub net_settings: NetworkSettings, pub dapps_conf: dapps::Configuration, + pub ipfs_conf: ipfs::Configuration, pub signer_conf: signer::Configuration, pub dapp: Option, pub ui: bool, @@ -422,7 +423,10 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R let signer_server = signer::start(cmd.signer_conf.clone(), signer_deps)?; // the ipfs server - let ipfs_server = ipfs::start_server(client.clone())?; + let ipfs_server = match cmd.ipfs_conf.enabled { + true => Some(ipfs::start_server(cmd.ipfs_conf.port, client.clone())?), + false => None, + }; // the informant let informant = Arc::new(Informant::new( -- GitLab From c4b4a22203cc1a92b164bbb63789a1228137312a Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Thu, 16 Feb 2017 14:51:33 +0100 Subject: [PATCH 054/246] Rename `parity-ipfs` to `parity-ipfs-api` --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- ipfs/Cargo.toml | 4 ++-- parity/ipfs.rs | 2 +- parity/main.rs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9bc78e975c..a161d6ea01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,7 +33,7 @@ dependencies = [ "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "parity-hash-fetch 1.6.0", - "parity-ipfs 1.6.0", + "parity-ipfs-api 1.6.0", "parity-reactor 0.1.0", "parity-rpc-client 1.4.0", "parity-updater 1.6.0", @@ -1596,7 +1596,7 @@ dependencies = [ ] [[package]] -name = "parity-ipfs" +name = "parity-ipfs-api" version = "1.6.0" dependencies = [ "cid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 520a6c4c5c..094c71c360 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ rlp = { path = "util/rlp" } rpc-cli = { path = "rpc_cli" } parity-rpc-client = { path = "rpc_client" } parity-hash-fetch = { path = "hash-fetch" } -parity-ipfs = { path = "ipfs" } +parity-ipfs-api = { path = "ipfs" } parity-updater = { path = "updater" } parity-reactor = { path = "util/reactor" } ethcore-dapps = { path = "dapps", optional = true } diff --git a/ipfs/Cargo.toml b/ipfs/Cargo.toml index d1798b4257..d7698ac742 100644 --- a/ipfs/Cargo.toml +++ b/ipfs/Cargo.toml @@ -1,6 +1,6 @@ [package] -description = "Parity IPFS crate" -name = "parity-ipfs" +description = "Parity IPFS-compatible API" +name = "parity-ipfs-api" version = "1.6.0" license = "GPL-3.0" authors = ["Parity Technologies "] diff --git a/parity/ipfs.rs b/parity/ipfs.rs index ed7b594500..c68ace3c16 100644 --- a/parity/ipfs.rs +++ b/parity/ipfs.rs @@ -1,4 +1,4 @@ -pub use parity_ipfs::start_server; +pub use parity_ipfs_api::start_server; #[derive(Debug, PartialEq, Clone)] pub struct Configuration { diff --git a/parity/main.rs b/parity/main.rs index b9ec461365..2d9d888d7e 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -56,7 +56,7 @@ extern crate ethcore_signer; extern crate ethcore_util as util; extern crate ethsync; extern crate parity_hash_fetch as hash_fetch; -extern crate parity_ipfs; +extern crate parity_ipfs_api; extern crate parity_reactor; extern crate parity_updater as updater; extern crate rpc_cli; -- GitLab From 8d6275bf07a3eb454e2b93e3af2ee50da1533198 Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Thu, 16 Feb 2017 16:08:54 +0100 Subject: [PATCH 055/246] Only allow requests from Origin 127.0.0.1 --- ipfs/src/lib.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ipfs/src/lib.rs b/ipfs/src/lib.rs index e497faed74..37373344a0 100644 --- a/ipfs/src/lib.rs +++ b/ipfs/src/lib.rs @@ -34,7 +34,7 @@ use error::ServerError; use handler::{IpfsHandler, Out}; use hyper::server::{Listening, Handler, Request, Response}; use hyper::net::HttpStream; -use hyper::header::{ContentLength, ContentType}; +use hyper::header::{ContentLength, ContentType, Origin}; use hyper::{Next, Encoder, Decoder, Method, RequestUri, StatusCode}; use ethcore::client::BlockChainClient; @@ -45,6 +45,13 @@ impl Handler for IpfsHandler { return Next::write(); } + // Reject requests if the Origin header isn't valid + if req.headers().get::().map(|o| "127.0.0.1" != &o.host.hostname).unwrap_or(false) { + self.out = Out::Bad("Illegal Origin"); + + return Next::write(); + } + let (path, query) = match *req.uri() { RequestUri::AbsolutePath { ref path, ref query } => (path, query.as_ref().map(AsRef::as_ref)), _ => return Next::write(), @@ -130,7 +137,7 @@ fn write_chunk(transport: &mut W, progress: &mut usize, data: &[u8]) - } pub fn start_server(port: u16, client: Arc) -> Result { - let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), port); + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port); Ok( hyper::Server::http(&addr)? -- GitLab From 2b91c922c14d21d6704d606397a5a2369c86d0bf Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 16 Feb 2017 16:08:58 +0100 Subject: [PATCH 056/246] 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 a113b43676..bebe89e0ea 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 de0207d792..e8fbf9b765 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(); -- GitLab From d4149b965e87e701da0334f7f15eb0e48707a5c3 Mon Sep 17 00:00:00 2001 From: NikVolf Date: Thu, 16 Feb 2017 18:20:24 +0300 Subject: [PATCH 057/246] files list separate fn, sha3 of the list --- ethstore/src/dir/disk.rs | 28 ++++++++++++++++------------ util/src/sha3.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index 4a4637850f..023db1ac20 100755 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -90,11 +90,8 @@ impl DiskDirectory where T: KeyFileManager { } } - /// all accounts found in keys directory - fn files(&self) -> Result, Error> { - // it's not done using one iterator cause - // there is an issue with rustc and it takes tooo much time to compile - let paths = fs::read_dir(&self.path)? + fn files(&self) -> Result, Error> { + Ok(fs::read_dir(&self.path)? .flat_map(Result::ok) .filter(|entry| { let metadata = entry.metadata().ok(); @@ -102,14 +99,21 @@ impl DiskDirectory where T: KeyFileManager { let name = file_name.to_string_lossy(); // filter directories metadata.map_or(false, |m| !m.is_dir()) && - // hidden files - !name.starts_with(".") && - // other ignored files - !IGNORED_FILES.contains(&&*name) + // hidden files + !name.starts_with(".") && + // other ignored files + !IGNORED_FILES.contains(&&*name) }) .map(|entry| entry.path()) - .collect::>(); + .collect::>() + ) + } + /// all accounts found in keys directory + fn files_content(&self) -> Result, Error> { + // it's not done using one iterator cause + // there is an issue with rustc and it takes tooo much time to compile + let paths = self.files()?; Ok(paths .into_iter() .filter_map(|path| { @@ -166,7 +170,7 @@ impl DiskDirectory where T: KeyFileManager { impl KeyDirectory for DiskDirectory where T: KeyFileManager { fn load(&self) -> Result, Error> { - let accounts = self.files()? + let accounts = self.files_content()? .into_iter() .map(|(_, account)| account) .collect(); @@ -191,7 +195,7 @@ impl KeyDirectory for DiskDirectory where T: KeyFileManager { fn remove(&self, account: &SafeAccount) -> Result<(), Error> { // enumerate all entries in keystore // and find entry with given address - let to_remove = self.files()? + let to_remove = self.files_content()? .into_iter() .find(|&(_, ref acc)| acc.id == account.id && acc.address == account.address); diff --git a/util/src/sha3.rs b/util/src/sha3.rs index 45d6a34d50..4d35023135 100644 --- a/util/src/sha3.rs +++ b/util/src/sha3.rs @@ -68,6 +68,24 @@ impl Hashable for T where T: AsRef<[u8]> { } } +impl Hashable for [T] where T: Hashable { + fn sha3(&self) -> H256 { + use std::ops::BitXor; + + let mut sha3 = SHA3_EMPTY; + for t in self.iter() { + sha3 = sha3.bitxor(t.sha3()); + }; + + sha3 + } + // todo: optimize? + fn sha3_into(&self, dest: &mut [u8]) { + let sha3 = self.sha3(); + dest.copy_from_slice(&*sha3); + } +} + /// Calculate SHA3 of given stream. pub fn sha3(r: &mut io::BufRead) -> Result { let mut output = [0u8; 32]; @@ -120,4 +138,15 @@ mod tests { // then assert_eq!(format!("{:?}", hash), "68371d7e884c168ae2022c82bd837d51837718a7f7dfb7aa3f753074a35e1d87"); } + + #[test] + fn should_sha3_strs() { + let strs = vec!["abc".to_owned(), "gdc".to_owned()]; + let hash = strs.sha3(); + assert_eq!(hash, "b8f24d705171c55892a34c7b863c258f4d47e6864f7a7da45f84155597a3b338".parse().unwrap()); + + let strs = vec!["abc".to_owned(), "gdc_".to_owned()]; + let hash = strs.sha3(); + assert_eq!(hash, "41bd661b8e02faccad55cdbb28db974dd5c9ae41825b89907fcf25db793b8b09".parse().unwrap()); + } } -- GitLab From f2027c02ef8a2ca10a0ea6954ba8c6f2de1db439 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Thu, 16 Feb 2017 16:37:40 +0100 Subject: [PATCH 058/246] Fixed fonts URLs (#4579) --- js/assets/fonts/Roboto/font.css | 14 +++++++------- js/assets/fonts/RobotoMono/font.css | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/js/assets/fonts/Roboto/font.css b/js/assets/fonts/Roboto/font.css index 5eea2bfd1f..75226e6218 100644 --- a/js/assets/fonts/Roboto/font.css +++ b/js/assets/fonts/Roboto/font.css @@ -3,7 +3,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(v15/0eC6fl06luXEYWpBSJvXCIX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); + src: local('Roboto Light'), local('Roboto-Light'), url(./v15/0eC6fl06luXEYWpBSJvXCIX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; } /* cyrillic */ @@ -11,7 +11,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(v15/Fl4y0QdOxyyTHEGMXX8kcYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); + src: local('Roboto Light'), local('Roboto-Light'), url(./v15/Fl4y0QdOxyyTHEGMXX8kcYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* greek-ext */ @@ -19,7 +19,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(v15/-L14Jk06m6pUHB-5mXQQnYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); + src: local('Roboto Light'), local('Roboto-Light'), url(./v15/-L14Jk06m6pUHB-5mXQQnYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); unicode-range: U+1F00-1FFF; } /* greek */ @@ -27,7 +27,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(v15/I3S1wsgSg9YCurV6PUkTOYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); + src: local('Roboto Light'), local('Roboto-Light'), url(./v15/I3S1wsgSg9YCurV6PUkTOYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); unicode-range: U+0370-03FF; } /* vietnamese */ @@ -35,7 +35,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(v15/NYDWBdD4gIq26G5XYbHsFIX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); + src: local('Roboto Light'), local('Roboto-Light'), url(./v15/NYDWBdD4gIq26G5XYbHsFIX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB; } /* latin-ext */ @@ -43,7 +43,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(v15/Pru33qjShpZSmG3z6VYwnYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); + src: local('Roboto Light'), local('Roboto-Light'), url(./v15/Pru33qjShpZSmG3z6VYwnYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; } /* latin */ @@ -51,6 +51,6 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(v15/Hgo13k-tfSpn0qi1SFdUfZBw1xU1rKptJj_0jans920.woff2) format('woff2'); + src: local('Roboto Light'), local('Roboto-Light'), url(./v15/Hgo13k-tfSpn0qi1SFdUfZBw1xU1rKptJj_0jans920.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; } diff --git a/js/assets/fonts/RobotoMono/font.css b/js/assets/fonts/RobotoMono/font.css index 5e9ab5721c..6a86af729e 100644 --- a/js/assets/fonts/RobotoMono/font.css +++ b/js/assets/fonts/RobotoMono/font.css @@ -3,7 +3,7 @@ font-family: 'Roboto Mono'; font-style: normal; font-weight: 300; - src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(v4/N4duVc9C58uwPiY8_59Fz0ExlR2MysFCBK8OirNw2kM.woff2) format('woff2'); + src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz0ExlR2MysFCBK8OirNw2kM.woff2) format('woff2'); unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; } /* cyrillic */ @@ -11,7 +11,7 @@ font-family: 'Roboto Mono'; font-style: normal; font-weight: 300; - src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(v4/N4duVc9C58uwPiY8_59Fz2dsm03krrxlabhmVQFB99s.woff2) format('woff2'); + src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz2dsm03krrxlabhmVQFB99s.woff2) format('woff2'); unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* greek-ext */ @@ -19,7 +19,7 @@ font-family: 'Roboto Mono'; font-style: normal; font-weight: 300; - src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(v4/N4duVc9C58uwPiY8_59FzyJ0caWjaSBdV-xZbEgst_k.woff2) format('woff2'); + src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59FzyJ0caWjaSBdV-xZbEgst_k.woff2) format('woff2'); unicode-range: U+1F00-1FFF; } /* greek */ @@ -27,7 +27,7 @@ font-family: 'Roboto Mono'; font-style: normal; font-weight: 300; - src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(v4/N4duVc9C58uwPiY8_59Fz2MSHb9EAJwuSzGfuRChQzQ.woff2) format('woff2'); + src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz2MSHb9EAJwuSzGfuRChQzQ.woff2) format('woff2'); unicode-range: U+0370-03FF; } /* vietnamese */ @@ -35,7 +35,7 @@ font-family: 'Roboto Mono'; font-style: normal; font-weight: 300; - src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(v4/N4duVc9C58uwPiY8_59Fz-pRBTtN4E2_qSPBnw6AgMc.woff2) format('woff2'); + src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz-pRBTtN4E2_qSPBnw6AgMc.woff2) format('woff2'); unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB; } /* latin-ext */ @@ -43,7 +43,7 @@ font-family: 'Roboto Mono'; font-style: normal; font-weight: 300; - src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(v4/N4duVc9C58uwPiY8_59Fz9Dnm4qiMZlH5rhYv_7LI2Y.woff2) format('woff2'); + src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz9Dnm4qiMZlH5rhYv_7LI2Y.woff2) format('woff2'); unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; } /* latin */ @@ -51,6 +51,6 @@ font-family: 'Roboto Mono'; font-style: normal; font-weight: 300; - src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(v4/N4duVc9C58uwPiY8_59Fz9TIkQYohD4BpHvJ3NvbHoA.woff2) format('woff2'); + src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz9TIkQYohD4BpHvJ3NvbHoA.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; } -- GitLab From 43ce5bef7e156b9560d6e5ad6a24992e6deaf4cf Mon Sep 17 00:00:00 2001 From: NikVolf Date: Thu, 16 Feb 2017 18:47:58 +0300 Subject: [PATCH 059/246] file list hash and test --- ethstore/src/dir/disk.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index 023db1ac20..c8afcc9109 100755 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -22,6 +22,7 @@ use {json, SafeAccount, Error}; use json::Uuid; use super::{KeyDirectory, VaultKeyDirectory, VaultKeyDirectoryProvider, VaultKey}; use super::vault::{VAULT_FILE_NAME, VaultDiskDirectory}; +use util::H256; const IGNORED_FILES: &'static [&'static str] = &[ "thumbs.db", @@ -109,6 +110,13 @@ impl DiskDirectory where T: KeyFileManager { ) } + pub fn files_hash(&self) -> Result { + use util::Hashable; + let files = self.files()?; + let file_strs: Vec<&str> = files.iter().map(|fp| fp.to_str().unwrap_or("")).collect(); + Ok(file_strs.sha3()) + } + /// all accounts found in keys directory fn files_content(&self) -> Result, Error> { // it's not done using one iterator cause @@ -283,7 +291,6 @@ mod test { let account = SafeAccount::create(&keypair, [0u8; 16], password, 1024, "Test".to_owned(), "{}".to_owned()); let res = directory.insert(account); - // then assert!(res.is_ok(), "Should save account succesfuly."); assert!(res.unwrap().filename.is_some(), "Filename has been assigned."); @@ -340,4 +347,25 @@ mod test { assert!(vaults.iter().any(|v| &*v == "vault1")); assert!(vaults.iter().any(|v| &*v == "vault2")); } + + #[test] + fn hash_of_files() { + let temp_path = RandomTempPath::new(); + let directory = RootDiskDirectory::create(&temp_path).unwrap(); + + let hash = directory.files_hash().expect("Files hash should be calculated ok"); + assert_eq!( + hash, + "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470".parse().unwrap() + ); + + let keypair = Random.generate().unwrap(); + let password = "test pass"; + let account = SafeAccount::create(&keypair, [0u8; 16], password, 1024, "Test".to_owned(), "{}".to_owned()); + directory.insert(account).expect("Account should be inserted ok"); + + let new_hash = directory.files_hash().expect("New files hash should be calculated ok"); + + assert!(new_hash != hash, "hash of the file list should change once directory content changed"); + } } -- GitLab From 5bd1cf352b97216d0b2c8e8898c466a7cc083ef2 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 16 Feb 2017 15:50:43 +0000 Subject: [PATCH 060/246] [ci skip] js-precompiled 20170216-154550 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c51c85ee0a..4b21ca7ef5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1617,7 +1617,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#8cfa973a48243b57279f2f0b2cbe3f010c6c5a34" +source = "git+https://github.com/ethcore/js-precompiled.git#e1d75910126030f2d9980b3301297baf107ad2fe" 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 cdc8611940..33a560b055 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.89", + "version": "0.3.90", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", -- GitLab From 4cd4572417acbd617a0bf2487d2c8b215202a60f Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Thu, 16 Feb 2017 20:03:13 +0400 Subject: [PATCH 061/246] add tag ENV in Dockerfile --- docker/hub/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/hub/Dockerfile b/docker/hub/Dockerfile index f78bbd217b..9e8dba9ef8 100644 --- a/docker/hub/Dockerfile +++ b/docker/hub/Dockerfile @@ -57,12 +57,13 @@ g++ -v # build parity RUN git clone https://github.com/ethcore/parity && \ cd parity && \ + git checkout $CI_BUILD_REF_NAME git pull && \ cargo build --release --features final && \ ls /build/parity/target/release/parity && \ strip /build/parity/target/release/parity -RUN file /build/parity/target/release/parity&&cp /build/parity/target/release/parity /parity +RUN file /build/parity/target/release/parity&&mkdir -p /parity&&cp /build/parity/target/release/parity /parity #cleanup Docker image RUN rm -rf /root/.cargo&&rm -rf /root/.multirust&&rm -rf /root/.rustup&&rm -rf /build -- GitLab From aa83603af8177944223a898c585e432a34a0c7cd Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Thu, 16 Feb 2017 20:04:28 +0400 Subject: [PATCH 062/246] typo fix in Dockerfile --- docker/hub/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/hub/Dockerfile b/docker/hub/Dockerfile index 9e8dba9ef8..7de63691b9 100644 --- a/docker/hub/Dockerfile +++ b/docker/hub/Dockerfile @@ -57,10 +57,10 @@ g++ -v # build parity RUN git clone https://github.com/ethcore/parity && \ cd parity && \ - git checkout $CI_BUILD_REF_NAME + git checkout $CI_BUILD_REF_NAME && \ git pull && \ cargo build --release --features final && \ - ls /build/parity/target/release/parity && \ + ls /build/parity/target/release/parity && \ strip /build/parity/target/release/parity RUN file /build/parity/target/release/parity&&mkdir -p /parity&&cp /build/parity/target/release/parity /parity -- GitLab From 00c843afea33f25af9563804aa84a7806107a59d Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 16 Feb 2017 19:42:01 +0300 Subject: [PATCH 063/246] Added vaults support to `ethstore-cli` (#4532) * added vaults support to ethstore-cli * improved error message --- ethstore/README.md | 179 +++++++++++++++++++++++++++++++---- ethstore/src/bin/ethstore.rs | 126 +++++++++++++++++++----- 2 files changed, 263 insertions(+), 42 deletions(-) diff --git a/ethstore/README.md b/ethstore/README.md index 121515943e..1986da72bc 100644 --- a/ethstore/README.md +++ b/ethstore/README.md @@ -16,23 +16,33 @@ Ethereum key management. Copyright 2016, 2017 Parity Technologies (UK) Ltd Usage: - ethstore insert [--dir DIR] - ethstore change-pwd
[--dir DIR] - ethstore list [--dir DIR] + ethstore insert [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore change-pwd
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore list [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] ethstore import [--src DIR] [--dir DIR] - ethstore import-wallet [--dir DIR] - ethstore remove
[--dir DIR] - ethstore sign
[--dir DIR] + ethstore import-wallet [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore remove
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore sign
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore public
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore list-vaults [--dir DIR] + ethstore create-vault [--dir DIR] + ethstore change-vault-pwd [--dir DIR] + ethstore move-to-vault
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore move-from-vault
[--dir DIR] ethstore [-h | --help] Options: - -h, --help Display this message and exit. - --dir DIR Specify the secret store directory. It may be either - parity, parity-test, geth, geth-test - or a path [default: parity]. - --src DIR Specify import source. It may be either - parity, parity-test, get, geth-test - or a path [default: geth]. + -h, --help Display this message and exit. + --dir DIR Specify the secret store directory. It may be either + parity, parity-test, geth, geth-test + or a path [default: parity]. + --vault VAULT Specify vault to use in this operation. + --vault-pwd VAULTPWD Specify vault password to use in this operation. Please note + that this option is required when vault option is set. + Otherwise it is ignored. + --src DIR Specify import source. It may be either + parity, parity-test, get, geth-test + or a path [default: geth]. Commands: insert Save account with password. @@ -42,16 +52,24 @@ Commands: import-wallet Import presale wallet. remove Remove account. sign Sign message. + public Displays public key for an address. + list-vaults List vaults. + create-vault Create new vault. + change-vault-pwd Change vault password. + move-to-vault Move account to vault from another vault/root directory. + move-from-vault Move account to root directory from given vault or root. ``` ### Examples -#### `insert [--dir DIR]` +#### `insert [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` *Encrypt secret with a password and save it in secret store.* - `` - ethereum secret, 32 bytes long - `` - account password, file path - `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity +- `[--vault VAULT]` - vault to use in this operation +- `[--vault-pwd VAULTPWD]` - vault password to use in this operation, file path ``` ethstore insert 7d29fab185a33e2cd955812397354c472d2b84615b645aa135ff539f6b0d70d5 password.txt @@ -73,13 +91,15 @@ ethstore insert `ethkey generate random -s` "this is sparta" -- -#### `change-pwd
[--dir DIR]` +#### `change-pwd
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` *Change account password.* - `
` - ethereum address, 20 bytes long - `` - old account password, file path - `` - new account password, file path - `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity +- `[--vault VAULT]` - vault to use in this operation +- `[--vault-pwd VAULTPWD]` - vault password to use in this operation, file path ``` ethstore change-pwd a8fa5dd30a87bb9e3288d604eb74949c515ab66e old_pwd.txt new_pwd.txt @@ -91,10 +111,12 @@ true -- -#### `list [--dir DIR]` +#### `list [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` *List secret store accounts.* - `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity +- `[--vault VAULT]` - vault to use in this operation +- `[--vault-pwd VAULTPWD]` - vault password to use in this operation, file path ``` ethstore list @@ -125,12 +147,14 @@ ethstore import -- -#### `import-wallet [--dir DIR]` +#### `import-wallet [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` *Import account from presale wallet.* - `` - presale wallet path - `` - account password, file path - `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity +- `[--vault VAULT]` - vault to use in this operation +- `[--vault-pwd VAULTPWD]` - vault password to use in this operation, file path ``` ethstore import-wallet ethwallet.json password.txt @@ -142,12 +166,14 @@ e6a3d25a7cb7cd21cb720df5b5e8afd154af1bbb -- -#### `remove
[--dir DIR]` +#### `remove
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` *Remove account from secret store.* - `
` - ethereum address, 20 bytes long - `` - account password, file path - `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity +- `[--vault VAULT]` - vault to use in this operation +- `[--vault-pwd VAULTPWD]` - vault password to use in this operation, file path ``` ethstore remove a8fa5dd30a87bb9e3288d604eb74949c515ab66e password.txt @@ -159,13 +185,15 @@ true -- -#### `sign
[--dir DIR]` +#### `sign
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` *Sign message with account's secret.* - `
` - ethereum address, 20 bytes long - `` - account password, file path - `` - message to sign, 32 bytes long - `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity +- `[--vault VAULT]` - vault to use in this operation +- `[--vault-pwd VAULTPWD]` - vault password to use in this operation, file path ``` ethstore sign 24edfff680d536a5f6fe862d36df6f8f6f40f115 password.txt 7d29fab185a33e2cd955812397354c472d2b84615b645aa135ff539f6b0d70d5 @@ -177,6 +205,119 @@ c6649f9555232d90ff716d7e552a744c5af771574425a74860e12f763479eb1b708c1f3a7dc0a0a7 -- +#### `public
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` +*Displays public key for an address.* + +- `
` - ethereum address, 20 bytes long +- `` - account password, file path +- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity +- `[--vault VAULT]` - vault to use in this operation +- `[--vault-pwd VAULTPWD]` - vault password to use in this operation, file path + +``` +ethstore public 00e63fdb87ceb815ec96ae185b8f7381a0b4a5ea account_password.txt --vault vault_name --vault-pwd vault_password.txt +``` + +``` +0x84161d8c05a996a534efbec50f24485cfcc07458efaef749a1b22156d7836c903eeb39bf2df74676e702eacc4cfdde069e5fd86692b5ef6ef81ba906e9e77d82 +``` + +-- + +#### `list-vaults [--dir DIR]` +*List vaults.* + +- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity + +``` +ethstore list-vaults +``` + +``` +vault1 +vault2 +vault3 +``` + +-- + +#### `create-vault [--dir DIR]` +*Create new vault.* + +- `` - name of new vault. This can only contain letters, digits, whitespaces, dashes and underscores +- `` - vault password, file path +- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity + +``` +ethstore create-vault vault3 vault3_password.txt +``` + +``` +OK +``` + +-- + +#### `change-vault-pwd [--dir DIR]` +*Change vault password.* + +- `` - name of existing vault +- `` - old vault password, file path +- `` - new vault password, file path +- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity + +``` +ethstore change-vault-pwd vault3 vault3_password.txt new_vault3_password.txt +``` + +``` +OK +``` + +-- + +#### `move-to-vault
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` +*Move account to vault from another vault/root directory.* + +- `
` - ethereum address, 20 bytes long +- `` - name of existing vault to move account to +- `` - password of existing `` to move account to, file path +- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity +- `[--vault VAULT]` - current vault of the `
` argument, if set +- `[--vault-pwd VAULTPWD]` - password for the current vault of the `
` argument, if any. file path + + +``` +ethstore move-to-vault 00e63fdb87ceb815ec96ae185b8f7381a0b4a5ea vault3 vault3_password.txt +ethstore move-to-vault 00e63fdb87ceb815ec96ae185b8f7381a0b4a5ea vault1 vault1_password.txt --vault vault3 --vault-pwd vault3_password.txt +``` + +``` +OK +OK +``` + +-- + +#### `move-from-vault
[--dir DIR]` +*Move account to root directory from given vault.* + +- `
` - ethereum address, 20 bytes long +- `` - name of existing vault to move account to +- `` - password of existing `` to move account to, file path +- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity + + +``` +ethstore move-from-vault 00e63fdb87ceb815ec96ae185b8f7381a0b4a5ea vault1 vault1_password.txt +``` + +``` +OK +``` + +-- + # Ethcore toolchain *this project is a part of the ethcore toolchain* diff --git a/ethstore/src/bin/ethstore.rs b/ethstore/src/bin/ethstore.rs index 06a0b40a86..20411a629d 100644 --- a/ethstore/src/bin/ethstore.rs +++ b/ethstore/src/bin/ethstore.rs @@ -31,24 +31,33 @@ Ethereum key management. Copyright 2016, 2017 Parity Technologies (UK) Ltd Usage: - ethstore insert [--dir DIR] - ethstore change-pwd
[--dir DIR] - ethstore list [--dir DIR] + ethstore insert [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore change-pwd
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore list [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] ethstore import [--src DIR] [--dir DIR] - ethstore import-wallet [--dir DIR] - ethstore remove
[--dir DIR] - ethstore sign
[--dir DIR] - ethstore public
+ ethstore import-wallet [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore remove
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore sign
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore public
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore list-vaults [--dir DIR] + ethstore create-vault [--dir DIR] + ethstore change-vault-pwd [--dir DIR] + ethstore move-to-vault
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore move-from-vault
[--dir DIR] ethstore [-h | --help] Options: - -h, --help Display this message and exit. - --dir DIR Specify the secret store directory. It may be either - parity, parity-test, geth, geth-test - or a path [default: parity]. - --src DIR Specify import source. It may be either - parity, parity-test, get, geth-test - or a path [default: geth]. + -h, --help Display this message and exit. + --dir DIR Specify the secret store directory. It may be either + parity, parity-test, geth, geth-test + or a path [default: parity]. + --vault VAULT Specify vault to use in this operation. + --vault-pwd VAULTPWD Specify vault password to use in this operation. Please note + that this option is required when vault option is set. + Otherwise it is ignored. + --src DIR Specify import source. It may be either + parity, parity-test, get, geth-test + or a path [default: geth]. Commands: insert Save account with password. @@ -59,6 +68,11 @@ Commands: remove Remove account. sign Sign message. public Displays public key for an address. + list-vaults List vaults. + create-vault Create new vault. + change-vault-pwd Change vault password. + move-to-vault Move account to vault from another vault/root directory. + move-from-vault Move account to root directory from given vault. "#; #[derive(Debug, RustcDecodable)] @@ -71,6 +85,11 @@ struct Args { cmd_remove: bool, cmd_sign: bool, cmd_public: bool, + cmd_list_vaults: bool, + cmd_create_vault: bool, + cmd_change_vault_pwd: bool, + cmd_move_to_vault: bool, + cmd_move_from_vault: bool, arg_secret: String, arg_password: String, arg_old_pwd: String, @@ -78,8 +97,11 @@ struct Args { arg_address: String, arg_message: String, arg_path: String, + arg_vault: String, flag_src: String, flag_dir: String, + flag_vault: String, + flag_vault_pwd: String, } fn main() { @@ -104,6 +126,23 @@ fn key_dir(location: &str) -> Result, Error> { Ok(dir) } +fn open_args_vault(store: &EthStore, args: &Args) -> Result { + if args.flag_vault.is_empty() { + return Ok(SecretVaultRef::Root); + } + + let vault_pwd = load_password(&args.flag_vault_pwd)?; + store.open_vault(&args.flag_vault, &vault_pwd)?; + Ok(SecretVaultRef::Vault(args.flag_vault.clone())) +} + +fn open_args_vault_account(store: &EthStore, address: Address, args: &Args) -> Result { + match open_args_vault(store, args)? { + SecretVaultRef::Root => Ok(StoreAccountRef::root(address)), + SecretVaultRef::Vault(name) => Ok(StoreAccountRef::vault(&name, address)), + } +} + fn format_accounts(accounts: &[Address]) -> String { accounts.iter() .enumerate() @@ -112,10 +151,14 @@ fn format_accounts(accounts: &[Address]) -> String { .join("\n") } +fn format_vaults(vaults: &[String]) -> String { + vaults.join("\n") +} + fn load_password(path: &str) -> Result { - let mut file = fs::File::open(path)?; + let mut file = fs::File::open(path).map_err(|e| Error::Custom(format!("Error opening password file {}: {}", path, e)))?; let mut password = String::new(); - file.read_to_string(&mut password)?; + file.read_to_string(&mut password).map_err(|e| Error::Custom(format!("Error reading password file {}: {}", path, e)))?; // drop EOF let _ = password.pop(); Ok(password) @@ -131,17 +174,24 @@ fn execute(command: I) -> Result where I: IntoIterator = accounts.into_iter().map(|a| a.address).collect(); + let accounts: Vec<_> = accounts + .into_iter() + .filter(|a| &a.vault == &vault_ref) + .map(|a| a.address) + .collect(); Ok(format_accounts(&accounts)) } else if args.cmd_import { let src = key_dir(&args.flag_src)?; @@ -152,24 +202,54 @@ fn execute(command: I) -> Result where I: IntoIterator Date: Thu, 16 Feb 2017 17:42:19 +0100 Subject: [PATCH 064/246] Better display of tags (#4564) * WIP * Update accounts on whitelist change in Parity Bar * Fix AccountCard width in Parity Bar (Signer) * Added AccountCard Example * Use horizontal tags * Better Tags display * Scrollable tags * Update PR Grumbles * Fix tests (add tags) * PR Grumble --- js/src/api/subscriptions/personal.js | 1 + js/src/playground/playground.css | 3 +- js/src/playground/playground.js | 2 + js/src/ui/AccountCard/accountCard.css | 46 ++++++-- js/src/ui/AccountCard/accountCard.example.js | 110 +++++++++++++++++++ js/src/ui/AccountCard/accountCard.js | 53 ++++++--- js/src/ui/AccountCard/accountCard.spec.js | 4 +- js/src/ui/Tags/tags.css | 31 +++++- js/src/ui/Tags/tags.js | 25 ++++- js/src/views/ParityBar/accountStore.js | 10 +- js/src/views/ParityBar/parityBar.css | 25 +++-- js/src/views/ParityBar/parityBar.js | 1 + 12 files changed, 262 insertions(+), 49 deletions(-) create mode 100644 js/src/ui/AccountCard/accountCard.example.js diff --git a/js/src/api/subscriptions/personal.js b/js/src/api/subscriptions/personal.js index 4f386b7c84..715f4cfe57 100644 --- a/js/src/api/subscriptions/personal.js +++ b/js/src/api/subscriptions/personal.js @@ -126,6 +126,7 @@ export default class Personal { case 'parity_setDappsAddresses': case 'parity_setNewDappsWhitelist': this._defaultAccount(true); + this._listAccounts(); return; } }); diff --git a/js/src/playground/playground.css b/js/src/playground/playground.css index f4e6e55d42..92556ae94a 100644 --- a/js/src/playground/playground.css +++ b/js/src/playground/playground.css @@ -74,7 +74,7 @@ $codeColor: #93a1a1; flex: 1; overflow: auto; padding: 0.5em; - background-color: #$codeBackground; + background-color: $codeBackground; color: $codeColor; font-size: 0.75em; @@ -86,5 +86,6 @@ $codeColor: #93a1a1; .component { flex: 3; padding-left: 0.5em; + overflow: auto; } } diff --git a/js/src/playground/playground.js b/js/src/playground/playground.js index a1790ed32b..9aa32f5c05 100644 --- a/js/src/playground/playground.js +++ b/js/src/playground/playground.js @@ -17,6 +17,7 @@ import { observer } from 'mobx-react'; import React, { Component } from 'react'; +import AccountCard from '~/ui/AccountCard/accountCard.example'; import CurrencySymbol from '~/ui/CurrencySymbol/currencySymbol.example'; import QrCode from '~/ui/QrCode/qrCode.example'; import SectionList from '~/ui/SectionList/sectionList.example'; @@ -25,6 +26,7 @@ import Portal from '~/ui/Portal/portal.example'; import PlaygroundStore from './store'; import styles from './playground.css'; +PlaygroundStore.register(); PlaygroundStore.register(); PlaygroundStore.register(); PlaygroundStore.register(); diff --git a/js/src/ui/AccountCard/accountCard.css b/js/src/ui/AccountCard/accountCard.css index fcf1522f5d..4799b13103 100644 --- a/js/src/ui/AccountCard/accountCard.css +++ b/js/src/ui/AccountCard/accountCard.css @@ -16,18 +16,14 @@ */ .account { - padding: 1em; - margin: 0.5em 0; - - display: flex; - flex-direction: column; - align-items: flex-start; - + align-items: stretch; background-color: rgba(0, 0, 0, 0.8); - + display: flex; + flex-direction: row; + margin: 0.5em 0; + overflow: hidden; transition: transform ease-out 0.1s; transform: scale(1); - overflow: hidden; &.copied { animation-duration: 0.25s; @@ -53,6 +49,38 @@ } } +.mainContainer { + flex: 1 1 auto; + overflow: hidden; + padding: 1em; +} + +.tagsContainer { + flex: 0 0 auto; + position: relative; + width: 3em; +} + +.tags { + background-color: rgba(0, 0, 0, 0.4); + box-sizing: content-box; + height: calc(100% - 0.5em); + overflow-x: hidden; + overflow-y: scroll; + padding: 0.25em; + padding-right: 2em; + position: absolute; + right: -2.5em; + transition: background-color 0.2s ease-out; + width: calc(100% + 0.25em); + + &:hover { + background-color: rgba(0, 0, 0, 0.8); + padding-left: 0.5em; + width: auto; + } +} + .infoContainer { display: flex; flex-direction: row; diff --git a/js/src/ui/AccountCard/accountCard.example.js b/js/src/ui/AccountCard/accountCard.example.js new file mode 100644 index 0000000000..8c4ea9a4b6 --- /dev/null +++ b/js/src/ui/AccountCard/accountCard.example.js @@ -0,0 +1,110 @@ +// 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 . + +import React, { Component } from 'react'; + +import PlaygroundExample from '~/playground/playgroundExample'; + +import AccountCard from './accountCard'; + +export default class AccountCardExample extends Component { + render () { + const account = { + address: '0x639ba260535db072a41115c472830846e4e9ad0f', + description: 'This is a description for the main account', + meta: { + tags: [ 'important', 'zargo' ] + }, + name: 'Main Account' + }; + + const balance = { + tokens: [ + { + value: 100000000000000000000, + token: { + tag: 'ETH' + } + } + ] + }; + + const accountManyTags = { + ...account, + meta: { tags: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((n) => `tag #${n}`) } + }; + + const accountNoTags = { + ...account, + meta: { tags: [] } + }; + + return ( +
+ + + + + +
+ +
+
+ + +
+ +
+
+ + +
+ +
+
+ + +
+
+ +
+
+ +
+
+
+
+ ); + } +} diff --git a/js/src/ui/AccountCard/accountCard.js b/js/src/ui/AccountCard/accountCard.js index 61d88a37fe..9167f38e6b 100644 --- a/js/src/ui/AccountCard/accountCard.js +++ b/js/src/ui/AccountCard/accountCard.js @@ -58,28 +58,45 @@ export default class AccountCard extends Component { onFocus={ this.onFocus } onKeyDown={ this.handleKeyDown } > -
- -
-
- +
+
+ +
+
+ +
+ { this.renderDescription(description) } + { this.renderAddress(address) }
- { this.renderDescription(description) } - { this.renderAddress(address) }
+ +
- - + { + tags && tags.length > 0 + ? ( +
+
+ +
+
+ ) : null + } +
); } diff --git a/js/src/ui/AccountCard/accountCard.spec.js b/js/src/ui/AccountCard/accountCard.spec.js index 7a5b684285..cecce7a890 100644 --- a/js/src/ui/AccountCard/accountCard.spec.js +++ b/js/src/ui/AccountCard/accountCard.spec.js @@ -33,7 +33,9 @@ function render (props = {}) { address: TEST_ADDRESS, description: 'testDescription', name: TEST_NAME, - meta: {} + meta: { + tags: [ 'tag 1', 'tag 2' ] + } }; } diff --git a/js/src/ui/Tags/tags.css b/js/src/ui/Tags/tags.css index 6e160efa33..9fbbaaace0 100644 --- a/js/src/ui/Tags/tags.css +++ b/js/src/ui/Tags/tags.css @@ -18,16 +18,39 @@ .tags { display: flex; flex-wrap: wrap; - position: absolute; - right: 0.25rem; - top: 0; + + &.floating { + position: absolute; + right: 0.25rem; + top: 0; + } + + &.horizontal { + flex-direction: column; + } +} + +.floating .tag { + margin-top: 0.75em; +} + +.horizontal .tag { + margin: 0.25em 0; + + .text { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 150%; + } } .tag { font-size: 0.75rem; background: rgba(255, 255, 255, 0.07); border-radius: 16px; - margin: 0.75em 0.5em 0 0; + margin: 0 0.5em; padding: 0.25em 1em; opacity: 1; transition: opacity 0.2s ease-out; diff --git a/js/src/ui/Tags/tags.js b/js/src/ui/Tags/tags.js index 5c44127c49..13ce0c15f9 100644 --- a/js/src/ui/Tags/tags.js +++ b/js/src/ui/Tags/tags.js @@ -22,20 +22,37 @@ import styles from './tags.css'; export default class Tags extends Component { static propTypes = { + floating: PropTypes.bool, + horizontal: PropTypes.bool, handleAddSearchToken: PropTypes.func, setRefs: PropTypes.func, tags: arrayOrObjectProptype() - } + }; + + static defaultProps = { + horizontal: false, + floating: true + }; render () { - const { tags } = this.props; + const { floating, horizontal, tags } = this.props; if (!tags || tags.length === 0) { return null; } + const classes = [ styles.tags ]; + + if (floating) { + classes.push(styles.floating); + } + + if (horizontal) { + classes.push(styles.horizontal); + } + return ( -
+
{ this.renderTags() }
); @@ -66,7 +83,7 @@ export default class Tags extends Component { onClick={ onClick } ref={ setRef } > - { tag } + { tag }
); }); diff --git a/js/src/views/ParityBar/accountStore.js b/js/src/views/ParityBar/accountStore.js index f1d704d505..bf854e1946 100644 --- a/js/src/views/ParityBar/accountStore.js +++ b/js/src/views/ParityBar/accountStore.js @@ -111,10 +111,18 @@ export default class AccountStore { } subscribeDefaultAccount () { - return this._api.subscribe('parity_defaultAccount', (error, defaultAccount) => { + const promiseDefaultAccount = this._api.subscribe('parity_defaultAccount', (error, defaultAccount) => { if (!error) { this.setDefaultAccount(defaultAccount); } }); + + const promiseEthAccounts = this._api.subscribe('eth_accounts', (error) => { + if (!error) { + this.loadAccounts(); + } + }); + + return Promise.all([ promiseDefaultAccount, promiseEthAccounts ]); } } diff --git a/js/src/views/ParityBar/parityBar.css b/js/src/views/ParityBar/parityBar.css index 64b0b35212..265bf7894a 100644 --- a/js/src/views/ParityBar/parityBar.css +++ b/js/src/views/ParityBar/parityBar.css @@ -84,7 +84,6 @@ $modalZ: 10001; .parityBg { position: fixed; - transition-property: left, top, right, bottom; transition-duration: 0.25s; transition-timing-function: ease; @@ -95,20 +94,25 @@ $modalZ: 10001; } } +.accountsSection { + padding: 0 1em; + width: 920px; +} + .expanded { border-radius: 4px 4px 0 0; display: flex; - flex-direction: column; + flex-direction: row; min-height: 30vh; max-height: 80vh; - max-width: calc(100vw - 1em); + max-width: calc(100vw - 2em); .content { flex: 1; - overflow-y: auto; - overflow-x: hidden; + overflow: auto; display: flex; background: rgba(0, 0, 0, 0.8); + max-width: calc(100vw - 2em); } } @@ -130,7 +134,8 @@ $modalZ: 10001; color: white !important; display: inline-block; - img, svg { + img, + svg { height: 24px !important; width: 24px !important; margin: 2px 0.5em 0 0; @@ -160,7 +165,7 @@ $modalZ: 10001; min-width: 2em !important; img { - margin: 6px 0.5em 0 0.5em; + margin: 6px 0.5em 0; } } @@ -177,7 +182,7 @@ $modalZ: 10001; .labelBubble { position: absolute; - top: 0px; + top: 0; right: -10px; } @@ -187,7 +192,7 @@ $modalZ: 10001; background: rgba(0, 0, 0, 0.25); margin-bottom: 0; - &:after { + &::after { clear: both; } } @@ -243,11 +248,9 @@ $modalZ: 10001; width: 1em; height: 1em; margin-left: 0.5em; - background-color: white; opacity: 0.25; border-radius: 50%; - transition-property: opacity; transition-duration: 0.1s; transition-timing-function: ease-in-out; diff --git a/js/src/views/ParityBar/parityBar.js b/js/src/views/ParityBar/parityBar.js index fe737a8732..d9d584a342 100644 --- a/js/src/views/ParityBar/parityBar.js +++ b/js/src/views/ParityBar/parityBar.js @@ -328,6 +328,7 @@ class ParityBar extends Component { displayType === DISPLAY_ACCOUNTS ? ( Date: Thu, 16 Feb 2017 17:43:27 +0100 Subject: [PATCH 065/246] remove vertx from Webpack config (#4576) --- js/webpack/npm.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/webpack/npm.js b/js/webpack/npm.js index 3f440c6985..b1f41d8058 100644 --- a/js/webpack/npm.js +++ b/js/webpack/npm.js @@ -48,8 +48,7 @@ module.exports = { umdNamedDefine: true }, externals: { - 'node-fetch': 'node-fetch', - 'vertx': 'vertx' + 'node-fetch': 'node-fetch' }, module: { noParse: [ -- GitLab From 301a707eaa510dca86372b5692dbe30081f9ec26 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Thu, 16 Feb 2017 17:45:12 +0100 Subject: [PATCH 066/246] Ledger signing fixed (#4578) --- hw/src/ledger.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/src/ledger.rs b/hw/src/ledger.rs index 50a9ee5f37..255fa85154 100644 --- a/hw/src/ledger.rs +++ b/hw/src/ledger.rs @@ -230,7 +230,7 @@ impl Manager { if result.len() != 65 { return Err(Error::Protocol("Signature packet size mismatch")); } - let v = result[0]; + let v = (result[0] + 1) % 2; let r = H256::from_slice(&result[1..33]); let s = H256::from_slice(&result[33..65]); Ok(Signature::from_rsv(&r, &s, v)) @@ -289,7 +289,7 @@ impl Manager { let mut chunk: [u8; HID_PACKET_SIZE] = [0; HID_PACKET_SIZE]; let chunk_size = handle.read(&mut chunk)?; trace!("read {:?}", &chunk[..]); - if chunk_size < 5 || chunk[1] != 0x01 || chunk[1] != 0x01 || chunk[2] != APDU_TAG { + if chunk_size < 5 || chunk[0] != 0x01 || chunk[1] != 0x01 || chunk[2] != APDU_TAG { return Err(Error::Protocol("Unexpected chunk header")); } let seq = (chunk[3] as usize) << 8 | (chunk[4] as usize); -- GitLab From 062c55d51b2fffbe6eed08dbfb659700511b993a Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 16 Feb 2017 17:06:15 +0000 Subject: [PATCH 067/246] [ci skip] js-precompiled 20170216-165434 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b21ca7ef5..35babcec37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1617,7 +1617,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#e1d75910126030f2d9980b3301297baf107ad2fe" +source = "git+https://github.com/ethcore/js-precompiled.git#e2c249df5ee41a8e67356b81fdbbc7237120837e" 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 33a560b055..c524029122 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.90", + "version": "0.3.91", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", -- GitLab From 3b9741e9d83b787495ae8c1450b656b8f416952a Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 16 Feb 2017 18:07:28 +0100 Subject: [PATCH 068/246] 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 0000000000..a64c9076ef --- /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 94d267c7aa..53d32f44e6 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; -- GitLab From da696e4a1f7282e3c5a6e228e3121f3c41e5ba06 Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Thu, 16 Feb 2017 18:39:13 +0100 Subject: [PATCH 069/246] Added support for contract code by hash. This is done by requests sending CID with raw binary codec (0x55). Note: this functionality is exactly the same as fetching state-trie due to how db internals work in Parity atm. --- ipfs/src/error.rs | 2 ++ ipfs/src/handler.rs | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/ipfs/src/error.rs b/ipfs/src/error.rs index 774763786e..f379f254b4 100644 --- a/ipfs/src/error.rs +++ b/ipfs/src/error.rs @@ -36,6 +36,7 @@ pub enum Error { BlockNotFound, TransactionNotFound, StateRootNotFound, + ContractNotFound, } /// Convert Error into Out, handy when switching from Rust's Result-based @@ -51,6 +52,7 @@ impl From for Out { BlockNotFound => Out::NotFound("Block not found"), TransactionNotFound => Out::NotFound("Transaction not found"), StateRootNotFound => Out::NotFound("State root not found"), + ContractNotFound => Out::NotFound("Contract not found"), } } } diff --git a/ipfs/src/handler.rs b/ipfs/src/handler.rs index 5197e8a1a8..543792fa55 100644 --- a/ipfs/src/handler.rs +++ b/ipfs/src/handler.rs @@ -86,6 +86,7 @@ impl IpfsHandler { Codec::EthereumBlockList => self.block_list(hash), Codec::EthereumTx => self.transaction(hash), Codec::EthereumStateTrie => self.state_trie(hash), + Codec::Raw => self.contract_code(hash), _ => return Err(Error::UnsupportedCid), } } @@ -119,6 +120,13 @@ impl IpfsHandler { Ok(Out::OctetStream(data)) } + + /// Get state trie node by hash and return as raw binary. + fn contract_code(&self, hash: H256) -> Result { + let data = self.client.state_data(&hash).ok_or(Error::ContractNotFound)?; + + Ok(Out::OctetStream(data)) + } } /// Get a query parameter's value by name. @@ -192,6 +200,16 @@ mod tests { assert_eq!(Err(Error::StateRootNotFound), handler.route_cid(&cid)); } + #[test] + fn cid_route_contract_code() { + let handler = get_mocked_handler(); + + // `raw` with Keccak-256 + let cid = "zb34WAp1Q5fhtLGZ3w3jhnTWaNbVV5ZZvGq4vuJQzERj6Pu3H"; + + assert_eq!(Err(Error::ContractNotFound), handler.route_cid(&cid)); + } + #[test] fn cid_route_invalid_hash() { let handler = get_mocked_handler(); -- GitLab From a9cc9ce1404efa85d70306a30571504a0a2c95e5 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Thu, 16 Feb 2017 22:58:41 +0400 Subject: [PATCH 070/246] docker build `no-cache` [ci-skip] --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 37df0bc054..50816582a7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -486,7 +486,7 @@ docker-build: script: - cd docker/hub - docker login -u $Docker_Hub_User -p $Docker_Hub_Pass - - docker build --tag ethcore/parity:$CI_BUILD_REF_NAME . + - docker build --no-cache=true --tag ethcore/parity:$CI_BUILD_REF_NAME . - docker push ethcore/parity:$CI_BUILD_REF_NAME tags: - docker -- GitLab From 513cc6261ad3f9fa9337e6196c4f11c708d3cb82 Mon Sep 17 00:00:00 2001 From: NikVolf Date: Thu, 16 Feb 2017 22:10:29 +0300 Subject: [PATCH 071/246] plug to store --- ethstore/src/dir/disk.rs | 4 ++++ ethstore/src/dir/mod.rs | 3 +++ ethstore/src/ethstore.rs | 20 +++++++++++++++++--- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index c8afcc9109..ccc84077bb 100755 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -219,6 +219,10 @@ impl KeyDirectory for DiskDirectory where T: KeyFileManager { fn as_vault_provider(&self) -> Option<&VaultKeyDirectoryProvider> { Some(self) } + + fn hash(&self) -> Result { + self.files_hash() + } } impl VaultKeyDirectoryProvider for DiskDirectory where T: KeyFileManager { diff --git a/ethstore/src/dir/mod.rs b/ethstore/src/dir/mod.rs index 6e43269684..11a1a2f23f 100755 --- a/ethstore/src/dir/mod.rs +++ b/ethstore/src/dir/mod.rs @@ -16,6 +16,7 @@ use std::path::{PathBuf}; use {SafeAccount, Error}; +use util::{H256, FixedHash}; mod disk; mod geth; @@ -62,6 +63,8 @@ pub trait KeyDirectory: Send + Sync { fn path(&self) -> Option<&PathBuf> { None } /// Return vault provider, if available fn as_vault_provider(&self) -> Option<&VaultKeyDirectoryProvider> { None } + /// Returns hash of the directory content, if supported + fn hash(&self) -> Result { Ok(H256::zero()) } } /// Vaults provider diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index 86b6dce461..eecc361e43 100755 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -27,6 +27,7 @@ use account::SafeAccount; use presale::PresaleWallet; use json::{self, Uuid}; use {import, Error, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation}; +use util::H256; pub struct EthStore { store: EthMultiStore, @@ -230,6 +231,7 @@ pub struct EthMultiStore { // order lock: cache, then vaults cache: RwLock>>, vaults: Mutex>>, + dir_hash: RwLock>, } impl EthMultiStore { @@ -244,11 +246,23 @@ impl EthMultiStore { vaults: Mutex::new(HashMap::new()), iterations: iterations, cache: Default::default(), + dir_hash: Default::default(), }; store.reload_accounts()?; Ok(store) } + fn reload_if_changed(&self) -> Result<(), Error> { + let mut last_dir_hash = self.dir_hash.write(); + let dir_hash = Some(self.dir.hash()?); + if *last_dir_hash == dir_hash { + return Ok(()) + } + self.reload_accounts()?; + *last_dir_hash = dir_hash; + Ok(()) + } + fn reload_accounts(&self) -> Result<(), Error> { let mut cache = self.cache.write(); @@ -284,7 +298,7 @@ impl EthMultiStore { } } - self.reload_accounts()?; + self.reload_if_changed()?; let cache = self.cache.read(); let accounts = cache.get(account).ok_or(Error::InvalidAccount)?; if accounts.is_empty() { @@ -431,7 +445,7 @@ impl SimpleSecretStore for EthMultiStore { } fn account_ref(&self, address: &Address) -> Result { - self.reload_accounts()?; + self.reload_if_changed()?; self.cache.read().keys() .find(|r| &r.address == address) .cloned() @@ -439,7 +453,7 @@ impl SimpleSecretStore for EthMultiStore { } fn accounts(&self) -> Result, Error> { - self.reload_accounts()?; + self.reload_if_changed()?; Ok(self.cache.read().keys().cloned().collect()) } -- GitLab From 48cf591e6694eae2911819f552772f6b1d6052b0 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 16 Feb 2017 20:46:59 +0100 Subject: [PATCH 072/246] 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 a64c9076ef..defa247ecb 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 c34e2d9225..ec3b758ce5 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(); + 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(); - self.dispatch_header_by_number(ctx, req, sender); + 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 944b419f7f..b739959c55 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() ) -- GitLab From 444065e2942e0963b5ee8f3b584066b08c843d98 Mon Sep 17 00:00:00 2001 From: NikVolf Date: Thu, 16 Feb 2017 22:53:58 +0300 Subject: [PATCH 073/246] refactor hashing --- ethstore/src/dir/disk.rs | 19 ++++++++++++------- ethstore/src/dir/mod.rs | 3 +-- ethstore/src/ethstore.rs | 5 ++--- util/src/sha3.rs | 29 ----------------------------- 4 files changed, 15 insertions(+), 41 deletions(-) diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index ccc84077bb..f8d8345e77 100755 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -22,7 +22,6 @@ use {json, SafeAccount, Error}; use json::Uuid; use super::{KeyDirectory, VaultKeyDirectory, VaultKeyDirectoryProvider, VaultKey}; use super::vault::{VAULT_FILE_NAME, VaultDiskDirectory}; -use util::H256; const IGNORED_FILES: &'static [&'static str] = &[ "thumbs.db", @@ -110,11 +109,17 @@ impl DiskDirectory where T: KeyFileManager { ) } - pub fn files_hash(&self) -> Result { - use util::Hashable; + pub fn files_hash(&self) -> Result { + use std::collections::hash_map::DefaultHasher; + use std::hash::Hasher; + + let mut hasher = DefaultHasher::new(); let files = self.files()?; - let file_strs: Vec<&str> = files.iter().map(|fp| fp.to_str().unwrap_or("")).collect(); - Ok(file_strs.sha3()) + for file in files { + hasher.write(file.to_str().unwrap_or("").as_bytes()) + } + + Ok(hasher.finish()) } /// all accounts found in keys directory @@ -220,7 +225,7 @@ impl KeyDirectory for DiskDirectory where T: KeyFileManager { Some(self) } - fn hash(&self) -> Result { + fn hash(&self) -> Result { self.files_hash() } } @@ -360,7 +365,7 @@ mod test { let hash = directory.files_hash().expect("Files hash should be calculated ok"); assert_eq!( hash, - "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470".parse().unwrap() + 15130871412783076140 ); let keypair = Random.generate().unwrap(); diff --git a/ethstore/src/dir/mod.rs b/ethstore/src/dir/mod.rs index 11a1a2f23f..5fbf5fb8b1 100755 --- a/ethstore/src/dir/mod.rs +++ b/ethstore/src/dir/mod.rs @@ -16,7 +16,6 @@ use std::path::{PathBuf}; use {SafeAccount, Error}; -use util::{H256, FixedHash}; mod disk; mod geth; @@ -64,7 +63,7 @@ pub trait KeyDirectory: Send + Sync { /// Return vault provider, if available fn as_vault_provider(&self) -> Option<&VaultKeyDirectoryProvider> { None } /// Returns hash of the directory content, if supported - fn hash(&self) -> Result { Ok(H256::zero()) } + fn hash(&self) -> Result { Ok(0u64) } } /// Vaults provider diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index eecc361e43..401630ba86 100755 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -27,7 +27,6 @@ use account::SafeAccount; use presale::PresaleWallet; use json::{self, Uuid}; use {import, Error, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation}; -use util::H256; pub struct EthStore { store: EthMultiStore, @@ -231,7 +230,7 @@ pub struct EthMultiStore { // order lock: cache, then vaults cache: RwLock>>, vaults: Mutex>>, - dir_hash: RwLock>, + dir_hash: Mutex>, } impl EthMultiStore { @@ -253,7 +252,7 @@ impl EthMultiStore { } fn reload_if_changed(&self) -> Result<(), Error> { - let mut last_dir_hash = self.dir_hash.write(); + let mut last_dir_hash = self.dir_hash.lock(); let dir_hash = Some(self.dir.hash()?); if *last_dir_hash == dir_hash { return Ok(()) diff --git a/util/src/sha3.rs b/util/src/sha3.rs index 4d35023135..45d6a34d50 100644 --- a/util/src/sha3.rs +++ b/util/src/sha3.rs @@ -68,24 +68,6 @@ impl Hashable for T where T: AsRef<[u8]> { } } -impl Hashable for [T] where T: Hashable { - fn sha3(&self) -> H256 { - use std::ops::BitXor; - - let mut sha3 = SHA3_EMPTY; - for t in self.iter() { - sha3 = sha3.bitxor(t.sha3()); - }; - - sha3 - } - // todo: optimize? - fn sha3_into(&self, dest: &mut [u8]) { - let sha3 = self.sha3(); - dest.copy_from_slice(&*sha3); - } -} - /// Calculate SHA3 of given stream. pub fn sha3(r: &mut io::BufRead) -> Result { let mut output = [0u8; 32]; @@ -138,15 +120,4 @@ mod tests { // then assert_eq!(format!("{:?}", hash), "68371d7e884c168ae2022c82bd837d51837718a7f7dfb7aa3f753074a35e1d87"); } - - #[test] - fn should_sha3_strs() { - let strs = vec!["abc".to_owned(), "gdc".to_owned()]; - let hash = strs.sha3(); - assert_eq!(hash, "b8f24d705171c55892a34c7b863c258f4d47e6864f7a7da45f84155597a3b338".parse().unwrap()); - - let strs = vec!["abc".to_owned(), "gdc_".to_owned()]; - let hash = strs.sha3(); - assert_eq!(hash, "41bd661b8e02faccad55cdbb28db974dd5c9ae41825b89907fcf25db793b8b09".parse().unwrap()); - } } -- GitLab From 92d8edc1a6b06886cb0ed8eee274de158cfa772f Mon Sep 17 00:00:00 2001 From: NikVolf Date: Thu, 16 Feb 2017 23:04:39 +0300 Subject: [PATCH 074/246] unique_repr, no default impl --- ethstore/src/dir/disk.rs | 2 +- ethstore/src/dir/geth.rs | 4 ++++ ethstore/src/dir/memory.rs | 7 +++++++ ethstore/src/dir/mod.rs | 4 ++-- ethstore/src/dir/parity.rs | 4 ++++ ethstore/src/ethstore.rs | 2 +- ethstore/tests/util/transient_dir.rs | 4 ++++ 7 files changed, 23 insertions(+), 4 deletions(-) diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index f8d8345e77..afca0955dc 100755 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -225,7 +225,7 @@ impl KeyDirectory for DiskDirectory where T: KeyFileManager { Some(self) } - fn hash(&self) -> Result { + fn unique_repr(&self) -> Result { self.files_hash() } } diff --git a/ethstore/src/dir/geth.rs b/ethstore/src/dir/geth.rs index 1058e433f7..0dfa2e6b23 100755 --- a/ethstore/src/dir/geth.rs +++ b/ethstore/src/dir/geth.rs @@ -95,4 +95,8 @@ impl KeyDirectory for GethDirectory { fn remove(&self, account: &SafeAccount) -> Result<(), Error> { self.dir.remove(account) } + + fn unique_repr(&self) -> Result { + self.dir.unique_repr() + } } diff --git a/ethstore/src/dir/memory.rs b/ethstore/src/dir/memory.rs index 941795efc2..87d12794eb 100644 --- a/ethstore/src/dir/memory.rs +++ b/ethstore/src/dir/memory.rs @@ -63,5 +63,12 @@ impl KeyDirectory for MemoryDirectory { } Ok(()) } + + fn unique_repr(&self) -> Result { + let mut val = 0u64; + let accounts = self.accounts.read(); + for acc in accounts.keys() { val = val ^ ::util::FixedHash::low_u64(acc) } + Ok(val) + } } diff --git a/ethstore/src/dir/mod.rs b/ethstore/src/dir/mod.rs index 5fbf5fb8b1..83e9787070 100755 --- a/ethstore/src/dir/mod.rs +++ b/ethstore/src/dir/mod.rs @@ -62,8 +62,8 @@ pub trait KeyDirectory: Send + Sync { fn path(&self) -> Option<&PathBuf> { None } /// Return vault provider, if available fn as_vault_provider(&self) -> Option<&VaultKeyDirectoryProvider> { None } - /// Returns hash of the directory content, if supported - fn hash(&self) -> Result { Ok(0u64) } + /// Unique representation of directory account collection + fn unique_repr(&self) -> Result; } /// Vaults provider diff --git a/ethstore/src/dir/parity.rs b/ethstore/src/dir/parity.rs index 198e501650..df03260d3b 100755 --- a/ethstore/src/dir/parity.rs +++ b/ethstore/src/dir/parity.rs @@ -74,4 +74,8 @@ impl KeyDirectory for ParityDirectory { fn remove(&self, account: &SafeAccount) -> Result<(), Error> { self.dir.remove(account) } + + fn unique_repr(&self) -> Result { + self.dir.unique_repr() + } } diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index 401630ba86..3cdf0e643c 100755 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -253,7 +253,7 @@ impl EthMultiStore { fn reload_if_changed(&self) -> Result<(), Error> { let mut last_dir_hash = self.dir_hash.lock(); - let dir_hash = Some(self.dir.hash()?); + let dir_hash = Some(self.dir.unique_repr()?); if *last_dir_hash == dir_hash { return Ok(()) } diff --git a/ethstore/tests/util/transient_dir.rs b/ethstore/tests/util/transient_dir.rs index 150ae8108e..45e2aab09d 100755 --- a/ethstore/tests/util/transient_dir.rs +++ b/ethstore/tests/util/transient_dir.rs @@ -74,4 +74,8 @@ impl KeyDirectory for TransientDir { fn remove(&self, account: &SafeAccount) -> Result<(), Error> { self.dir.remove(account) } + + fn unique_repr(&self) -> Result { + self.dir.unique_repr() + } } -- GitLab From 164f1cef81954a950c52c7f55914302be9e031ab Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Fri, 17 Feb 2017 03:56:09 +0400 Subject: [PATCH 075/246] remove `git pul` from hub Docker [ci-skip] --- docker/hub/Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/docker/hub/Dockerfile b/docker/hub/Dockerfile index 7de63691b9..ec19f5e0d5 100644 --- a/docker/hub/Dockerfile +++ b/docker/hub/Dockerfile @@ -58,7 +58,6 @@ g++ -v RUN git clone https://github.com/ethcore/parity && \ cd parity && \ git checkout $CI_BUILD_REF_NAME && \ - git pull && \ cargo build --release --features final && \ ls /build/parity/target/release/parity && \ strip /build/parity/target/release/parity -- GitLab From aca808b021e1a51e43417fbafd9279cd73aed793 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Fri, 17 Feb 2017 13:48:37 +0400 Subject: [PATCH 076/246] update Dockerfile ENTRYPOINT ["/parity/parity"] --- docker/hub/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/hub/Dockerfile b/docker/hub/Dockerfile index ec19f5e0d5..2117492794 100644 --- a/docker/hub/Dockerfile +++ b/docker/hub/Dockerfile @@ -67,4 +67,4 @@ RUN file /build/parity/target/release/parity&&mkdir -p /parity&&cp /build/parity RUN rm -rf /root/.cargo&&rm -rf /root/.multirust&&rm -rf /root/.rustup&&rm -rf /build EXPOSE 8080 8545 8180 -ENTRYPOINT ["/parity"] +ENTRYPOINT ["/parity/parity"] -- GitLab From 54c48d14eceeae96e5120a1dda42161972a0be3f Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 17 Feb 2017 12:00:33 +0100 Subject: [PATCH 077/246] Deprecate eth_compile* RPCs (#4577) * Deprecate eth_compile* RPCs * Add deprecation doc comments --- rpc/src/v1/helpers/errors.rs | 18 ++++++------ rpc/src/v1/impls/eth.rs | 52 ++++++---------------------------- rpc/src/v1/tests/mocked/eth.rs | 8 +++--- rpc/src/v1/traits/eth.rs | 4 +++ 4 files changed, 25 insertions(+), 57 deletions(-) diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index b58999f84d..93d23b1aac 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -46,10 +46,10 @@ mod codes { pub const REQUEST_REJECTED: i64 = -32040; pub const REQUEST_REJECTED_LIMIT: i64 = -32041; pub const REQUEST_NOT_FOUND: i64 = -32042; - pub const COMPILATION_ERROR: i64 = -32050; pub const ENCRYPTION_ERROR: i64 = -32055; pub const FETCH_ERROR: i64 = -32060; pub const NO_LIGHT_PEERS: i64 = -32065; + pub const DEPRECATED: i64 = -32070; } pub fn unimplemented(details: Option) -> Error { @@ -92,14 +92,6 @@ pub fn account(error: &str, details: T) -> Error { } } -pub fn compilation(error: T) -> Error { - Error { - code: ErrorCode::ServerError(codes::COMPILATION_ERROR), - message: "Error while compiling code.".into(), - data: Some(Value::String(format!("{:?}", error))), - } -} - pub fn internal(error: &str, data: T) -> Error { Error { code: ErrorCode::InternalError, @@ -317,3 +309,11 @@ pub fn no_light_peers() -> Error { data: None, } } + +pub fn deprecated>>(message: T) -> Error { + Error { + code: ErrorCode::ServerError(codes::DEPRECATED), + message: "Method deprecated".into(), + data: message.into().map(Value::String), + } +} diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index d763db8362..9ad8196fee 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -16,8 +16,6 @@ //! Eth rpc implementation. -use std::io::{Write}; -use std::process::{Command, Stdio}; use std::thread; use std::time::{Instant, Duration}; use std::sync::{Arc, Weak}; @@ -27,7 +25,7 @@ use rlp::{self, UntrustedRlp, View}; use time::get_time; use util::{H160, H256, Address, FixedHash, U256, H64, Uint}; use util::sha3::Hashable; -use util::{FromHex, Mutex}; +use util::Mutex; use ethash::SeedHashCompute; use ethcore::account_provider::{AccountProvider, DappId}; @@ -258,12 +256,6 @@ fn check_known(client: &C, number: BlockNumber) -> Result<(), Error> where C: const MAX_QUEUE_SIZE_TO_MINE_ON: usize = 4; // because uncles go back 6. -#[cfg(windows)] -static SOLC: &'static str = "solc.exe"; - -#[cfg(not(windows))] -static SOLC: &'static str = "solc"; - impl Eth for EthClient where C: MiningBlockChainClient + 'static, SN: SnapshotService + 'static, @@ -509,12 +501,7 @@ impl Eth for EthClient where } fn compilers(&self) -> Result, Error> { - let mut compilers = vec![]; - if Command::new(SOLC).output().is_ok() { - compilers.push("solidity".to_owned()) - } - - Ok(compilers) + Err(errors::deprecated("Compilation functionality is deprecated.".to_string())) } fn logs(&self, filter: Filter) -> Result, Error> { @@ -642,37 +629,14 @@ impl Eth for EthClient where } fn compile_lll(&self, _: String) -> Result { - rpc_unimplemented!() + Err(errors::deprecated("Compilation of LLL via RPC is deprecated".to_string())) } fn compile_serpent(&self, _: String) -> Result { - rpc_unimplemented!() - } - - fn compile_solidity(&self, code: String) -> Result { - let maybe_child = Command::new(SOLC) - .arg("--bin") - .arg("--optimize") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::null()) - .spawn(); - - maybe_child - .map_err(errors::compilation) - .and_then(|mut child| { - child.stdin.as_mut() - .expect("we called child.stdin(Stdio::piped()) before spawn; qed") - .write_all(code.as_bytes()) - .map_err(errors::compilation)?; - let output = child.wait_with_output().map_err(errors::compilation)?; - - let s = String::from_utf8_lossy(&output.stdout); - if let Some(hex) = s.lines().skip_while(|ref l| !l.contains("Binary")).skip(1).next() { - Ok(Bytes::new(hex.from_hex().unwrap_or(vec![]))) - } else { - Err(errors::compilation("Unexpected output.")) - } - }) + Err(errors::deprecated("Compilation of Serpent via RPC is deprecated".to_string())) + } + + fn compile_solidity(&self, _: String) -> Result { + Err(errors::deprecated("Compilation of Solidity via RPC is deprecated".to_string())) } } diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index 3b668b0306..40ae8c38cd 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -1020,7 +1020,7 @@ fn rpc_eth_transaction_receipt_null() { #[test] fn rpc_eth_compilers() { let request = r#"{"jsonrpc": "2.0", "method": "eth_getCompilers", "params": [], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","result":[],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","error":{"code":-32070,"message":"Method deprecated","data":"Compilation functionality is deprecated."},"id":1}"#; assert_eq!(EthTester::default().io.handle_request_sync(request), Some(response.to_owned())); } @@ -1029,7 +1029,7 @@ fn rpc_eth_compilers() { #[test] fn rpc_eth_compile_lll() { let request = r#"{"jsonrpc": "2.0", "method": "eth_compileLLL", "params": [], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","error":{"code":-32603,"message":"Internal error","data":null},"id":1}"#; + let response = r#"{"jsonrpc":"2.0","error":{"code":-32070,"message":"Method deprecated","data":"Compilation of LLL via RPC is deprecated"},"id":1}"#; assert_eq!(EthTester::default().io.handle_request_sync(request), Some(response.to_owned())); } @@ -1038,7 +1038,7 @@ fn rpc_eth_compile_lll() { #[test] fn rpc_eth_compile_solidity() { let request = r#"{"jsonrpc": "2.0", "method": "eth_compileSolidity", "params": [], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","error":{"code":-32603,"message":"Internal error","data":null},"id":1}"#; + let response = r#"{"jsonrpc":"2.0","error":{"code":-32070,"message":"Method deprecated","data":"Compilation of Solidity via RPC is deprecated"},"id":1}"#; assert_eq!(EthTester::default().io.handle_request_sync(request), Some(response.to_owned())); } @@ -1047,7 +1047,7 @@ fn rpc_eth_compile_solidity() { #[test] fn rpc_eth_compile_serpent() { let request = r#"{"jsonrpc": "2.0", "method": "eth_compileSerpent", "params": [], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","error":{"code":-32603,"message":"Internal error","data":null},"id":1}"#; + let response = r#"{"jsonrpc":"2.0","error":{"code":-32070,"message":"Method deprecated","data":"Compilation of Serpent via RPC is deprecated"},"id":1}"#; assert_eq!(EthTester::default().io.handle_request_sync(request), Some(response.to_owned())); } diff --git a/rpc/src/v1/traits/eth.rs b/rpc/src/v1/traits/eth.rs index a50188bf0a..eaf608c602 100644 --- a/rpc/src/v1/traits/eth.rs +++ b/rpc/src/v1/traits/eth.rs @@ -142,18 +142,22 @@ build_rpc_trait! { fn uncle_by_block_number_and_index(&self, BlockNumber, Index) -> Result, Error>; /// Returns available compilers. + /// @deprecated #[rpc(name = "eth_getCompilers")] fn compilers(&self) -> Result, Error>; /// Compiles lll code. + /// @deprecated #[rpc(name = "eth_compileLLL")] fn compile_lll(&self, String) -> Result; /// Compiles solidity. + /// @deprecated #[rpc(name = "eth_compileSolidity")] fn compile_solidity(&self, String) -> Result; /// Compiles serpent. + /// @deprecated #[rpc(name = "eth_compileSerpent")] fn compile_serpent(&self, String) -> Result; -- GitLab From c2540dc2153857aa84140dc1ee249f4787941762 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Fri, 17 Feb 2017 12:20:25 +0100 Subject: [PATCH 078/246] Check for matching session before deregistering (#4563) --- util/network/src/connection.rs | 5 ++++- util/network/src/error.rs | 3 +++ util/network/src/host.rs | 11 ++++++++--- util/network/src/session.rs | 4 ++-- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/util/network/src/connection.rs b/util/network/src/connection.rs index 5cc31d6bde..c54c951fae 100644 --- a/util/network/src/connection.rs +++ b/util/network/src/connection.rs @@ -354,7 +354,10 @@ impl EncryptedConnection { /// Send a packet pub fn send_packet(&mut self, io: &IoContext, payload: &[u8]) -> Result<(), NetworkError> where Message: Send + Clone + Sync + 'static { let mut header = RlpStream::new(); - let len = payload.len() as usize; + let len = payload.len(); + if len >= (1 << 24) { + return Err(NetworkError::OversizedPacket); + } header.append_raw(&[(len >> 16) as u8, (len >> 8) as u8, len as u8], 1); header.append_raw(&[0xc2u8, 0x80u8, 0x80u8], 1); //TODO: ger rid of vectors here diff --git a/util/network/src/error.rs b/util/network/src/error.rs index afb2e8ba8c..11cf8fdf00 100644 --- a/util/network/src/error.rs +++ b/util/network/src/error.rs @@ -106,6 +106,8 @@ pub enum NetworkError { AddressResolve(Option<::std::io::Error>), /// Error concerning the Rust standard library's IO subsystem. StdIo(::std::io::Error), + /// Packet size is over the protocol limit. + OversizedPacket, } impl fmt::Display for NetworkError { @@ -124,6 +126,7 @@ impl fmt::Display for NetworkError { AddressResolve(_) => "Failed to resolve network address.".into(), StdIo(ref err) => format!("{}", err), Util(ref err) => format!("{}", err), + OversizedPacket => "Packet is too large".into(), }; f.write_fmt(format_args!("Network error ({})", msg)) diff --git a/util/network/src/host.rs b/util/network/src/host.rs index c3f5ab8cf8..b64301369c 100644 --- a/util/network/src/host.rs +++ b/util/network/src/host.rs @@ -844,7 +844,8 @@ impl Host { // only proceed if the connecting peer is reserved. if !self.reserved_nodes.read().contains(&id) { s.disconnect(io, DisconnectReason::TooManyPeers); - return; + kill = true; + break; } } ready_id = Some(id); @@ -895,6 +896,7 @@ impl Host { if duplicate { trace!(target: "network", "Rejected duplicate connection: {}", token); session.lock().disconnect(io, DisconnectReason::DuplicatePeer); + self.kill_connection(token, io, false); return; } for p in ready_data { @@ -1159,8 +1161,11 @@ impl IoHandler for Host { FIRST_SESSION ... LAST_SESSION => { let mut connections = self.sessions.write(); if let Some(connection) = connections.get(stream).cloned() { - connection.lock().deregister_socket(event_loop).expect("Error deregistering socket"); - connections.remove(stream); + let c = connection.lock(); + if c.expired() { // make sure it is the same connection that the event was generated for + c.deregister_socket(event_loop).expect("Error deregistering socket"); + connections.remove(stream); + } } } DISCOVERY => (), diff --git a/util/network/src/session.rs b/util/network/src/session.rs index 4c40010156..dc4a5464cd 100644 --- a/util/network/src/session.rs +++ b/util/network/src/session.rs @@ -241,7 +241,7 @@ impl Session { /// Check if this session is expired. pub fn expired(&self) -> bool { match self.state { - State::Handshake(ref h) => h.expired(), + State::Handshake(ref h) => self.expired || h.expired(), _ => self.expired, } } @@ -407,7 +407,7 @@ impl Session { let rlp = UntrustedRlp::new(&packet.data[1..]); let reason: u8 = rlp.val_at(0)?; if self.had_hello { - debug!("Disconnected: {}: {:?}", self.token(), DisconnectReason::from_u8(reason)); + debug!(target:"network", "Disconnected: {}: {:?}", self.token(), DisconnectReason::from_u8(reason)); } Err(From::from(NetworkError::Disconnect(DisconnectReason::from_u8(reason)))) } -- GitLab From b35e852cb6fa89f6500394f859b794fd56ca1ad0 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Fri, 17 Feb 2017 15:46:43 +0400 Subject: [PATCH 079/246] cargo fix --- .gitlab-ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 50816582a7..23840c0c75 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,7 +22,9 @@ linux-stable: - triggers script: - cargo build -j $(nproc) --release --features final $CARGOFLAGS - - cargo build -j $(nproc) --release -p evmbin ethstore ethkey + - 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/evmbin - strip target/release/ethstore -- GitLab From 59315b0cb7c290d9bd3bbb1145a4967cd713d55a Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 17 Feb 2017 15:16:28 +0100 Subject: [PATCH 080/246] 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 720b808694..b67154f7b5 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 0000000000..99e81c9e78 --- /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 c4c08ddc8a..ccfca525bf 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()); } } -- GitLab From a3bbdce61324c0cdeabd35669c2fae42534da03a Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Fri, 17 Feb 2017 19:03:40 +0400 Subject: [PATCH 081/246] update Dockerfile for hub refactoring + small fix --- docker/hub/Dockerfile | 69 ++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/docker/hub/Dockerfile b/docker/hub/Dockerfile index 2117492794..8973ed63f8 100644 --- a/docker/hub/Dockerfile +++ b/docker/hub/Dockerfile @@ -1,4 +1,5 @@ FROM ubuntu:14.04 +MAINTAINER Parity Technologies WORKDIR /build # install tools and dependencies RUN apt-get update && \ @@ -25,46 +26,54 @@ RUN apt-get update && \ # evmjit dependencies zlib1g-dev \ libedit-dev \ - libudev-dev - -# cmake and llvm ppas. then update ppas -RUN add-apt-repository -y "ppa:george-edison55/cmake-3.x" && \ + libudev-dev &&\ +# cmake and llvm ppa's. then update ppa's + add-apt-repository -y "ppa:george-edison55/cmake-3.x" && \ add-apt-repository "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.7 main" && \ apt-get update && \ - apt-get install -y --force-yes cmake llvm-3.7-dev - + apt-get install -y --force-yes cmake llvm-3.7-dev && \ # install evmjit -RUN git clone https://github.com/debris/evmjit && \ + git clone https://github.com/debris/evmjit && \ cd evmjit && \ mkdir build && cd build && \ - cmake .. && make && make install && cd - + cmake .. && make && make install && cd && \ # install rustup -RUN curl https://sh.rustup.rs -sSf | sh -s -- -y - + curl https://sh.rustup.rs -sSf | sh -s -- -y && \ # rustup directory -ENV PATH /root/.cargo/bin:$PATH - + PATH=/root/.cargo/bin:$PATH && \ # show backtraces -ENV RUST_BACKTRACE 1 - -# show tools -RUN rustc -vV && \ -cargo -V && \ -gcc -v &&\ -g++ -v - + RUST_BACKTRACE=1 && \ # build parity -RUN git clone https://github.com/ethcore/parity && \ + cd /build&&git clone https://github.com/ethcore/parity && \ cd parity && \ - git checkout $CI_BUILD_REF_NAME && \ - cargo build --release --features final && \ - ls /build/parity/target/release/parity && \ - strip /build/parity/target/release/parity - -RUN file /build/parity/target/release/parity&&mkdir -p /parity&&cp /build/parity/target/release/parity /parity + git pull&& \ + git checkout $CI_BUILD_REF_NAME && \ + cargo build --verbose --release --features final && \ + #ls /build/parity/target/release/parity && \ + strip /build/parity/target/release/parity && \ + file /build/parity/target/release/parity&&mkdir -p /parity&& cp /build/parity/target/release/parity /parity&&\ #cleanup Docker image -RUN rm -rf /root/.cargo&&rm -rf /root/.multirust&&rm -rf /root/.rustup&&rm -rf /build - + rm -rf /root/.cargo&&rm -rf /root/.multirust&&rm -rf /root/.rustup&&rm -rf /build&&\ + apt-get purge -y \ + # make + build-essential \ + # add-apt-repository + software-properties-common \ + make \ + curl \ + wget \ + git \ + g++ \ + gcc \ + binutils \ + file \ + pkg-config \ + dpkg-dev \ + # evmjit dependencies + zlib1g-dev \ + libedit-dev \ + cmake llvm-3.7-dev&&\ + rm -rf /var/lib/apt/lists/* +# setup ENTRYPOINT EXPOSE 8080 8545 8180 ENTRYPOINT ["/parity/parity"] -- GitLab From 7a857a24aed9ee55f8ddbabf37899924120d53e9 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 17 Feb 2017 16:18:31 +0100 Subject: [PATCH 082/246] 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 c51c85ee0a..8119910b60 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 442f8b7859..c8a1c7fb53 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 dce708b3a8..6e1ea9d313 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 be5247340c..3a56db51b3 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 6c2c02c2d8..e239eec5ba 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 8b8f9ecd7a..91058b9901 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 030a037021..201f41c221 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 e8fbf9b765..64f5b69af4 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 431c34e84f..8dbb4578fc 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 6cec96469c..55d8ae835c 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 ccfca525bf..01c988232f 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) } } -- GitLab From 3b023c82b75d858bd4e27f67f8598b24d7c704cf Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 17 Feb 2017 17:08:46 +0100 Subject: [PATCH 083/246] 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 | 84 ++++++++++++++++++++---- 7 files changed, 135 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8119910b60..6269a85f69 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 d2444dd59a..9e10449fbc 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 defa247ecb..185007a1b6 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 403d3555d8..575938cd5b 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 bebe89e0ea..5701fc6066 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 53d32f44e6..b6e06a02b5 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 64f5b69af4..13ae9a0cad 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; + 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(); + 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, + } + }; - 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() + // 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) -- GitLab From 04f3ee90d82b2dc085b1a34bd9340f3b37ca9810 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Fri, 17 Feb 2017 18:11:10 +0100 Subject: [PATCH 084/246] Revert Double Click (#4590) --- js/src/views/ParityBar/parityBar.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/js/src/views/ParityBar/parityBar.js b/js/src/views/ParityBar/parityBar.js index d9d584a342..690a6792dc 100644 --- a/js/src/views/ParityBar/parityBar.js +++ b/js/src/views/ParityBar/parityBar.js @@ -345,21 +345,16 @@ class ParityBar extends Component { renderAccount = (account) => { const makeDefaultAccount = () => { + this.toggleAccountsDisplay(); return this.accountStore .makeDefaultAccount(account.address) .then(() => this.accountStore.loadAccounts()); }; - const onDoubleClick = () => { - this.toggleAccountsDisplay(); - makeDefaultAccount(); - }; - return (
Date: Fri, 17 Feb 2017 17:24:47 +0000 Subject: [PATCH 085/246] [ci skip] js-precompiled 20170217-171830 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 957ea8270b..308364fca2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1663,7 +1663,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#e2c249df5ee41a8e67356b81fdbbc7237120837e" +source = "git+https://github.com/ethcore/js-precompiled.git#b0b680999ceab7a8aa67f6977220070ccdf044e3" 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 c524029122..55ca4ad423 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.91", + "version": "0.3.92", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", -- GitLab From 163e2af5682f66ceb8444b77602f3206e8d49f14 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Fri, 17 Feb 2017 22:11:35 +0400 Subject: [PATCH 086/246] update Dockerfile for hub nightly->latest --- .gitlab-ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 23840c0c75..3747a68964 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -487,9 +487,10 @@ docker-build: - docker info script: - cd docker/hub + - if [ "$CI_BUILD_REF_NAME" == "nightly" ]; then DOCKER_TAG="latest"; else DOCKER_TAG=$CI_BUILD_REF_NAME; fi - docker login -u $Docker_Hub_User -p $Docker_Hub_Pass - - docker build --no-cache=true --tag ethcore/parity:$CI_BUILD_REF_NAME . - - docker push ethcore/parity:$CI_BUILD_REF_NAME + - docker build --no-cache=true --tag ethcore/parity:$DOCKER_TAG . + - docker push ethcore/parity:$DOCKER_TAG tags: - docker test-darwin: -- GitLab From 32023f1ea089fb3dffab4d5cf05311558f092e1a Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Fri, 17 Feb 2017 23:33:14 +0400 Subject: [PATCH 087/246] fix build path to `tools` --- .gitlab-ci.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3747a68964..d79818f1d7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -26,16 +26,16 @@ linux-stable: - cargo build -j $(nproc) --release -p ethstore - cargo build -j $(nproc) --release -p ethkey - strip target/release/parity - - strip target/release/evmbin - - strip target/release/ethstore - - strip target/release/ethkey + - strip target/release/parity/evmbin + - strip target/release/parity/ethstore + - strip target/release/parity/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/evmbin deb/usr/bin/evmbin - - cp target/release/ethstore deb/usr/bin/ethstore - - cp target/release/ethkey deb/usr/bin/ethkey + - cp target/release/parity/evmbin deb/usr/bin/evmbin + - cp target/release/parity/ethstore deb/usr/bin/ethstore + - cp target/release/parity/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" @@ -55,6 +55,9 @@ linux-stable: artifacts: paths: - target/release/parity + - target/release/parity/evmbin + - target/release/parity/ethstore + - target/release/parity/ethkey name: "stable-x86_64-unknown-linux-gnu_parity" linux-stable-debian: stage: build -- GitLab From 9316eb4ad34417de1fc3567a8e8a1000ef1f4fc3 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 17 Feb 2017 21:38:43 +0100 Subject: [PATCH 088/246] (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 898934965d..eb5677cfa7 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 c9f2787767..9a26b24b1e 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 8ca6a64f6f..d17a863f51 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 13ae9a0cad..0bea7f9a19 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 b58999f84d..e187c4df6e 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 1772d5b585..71a3a497da 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 0000000000..3373243959 --- /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 8dbb4578fc..dea1551687 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 d5ecbd5e61..10e3b54bd3 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 9b1ace73b5..4cdc9d37a1 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 8ea6705f2c..6cd4fade5c 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}; -- GitLab From 9e761ba2ea870693d9f61c7e9b9a4c75d1bb19b1 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 17 Feb 2017 21:43:15 +0100 Subject: [PATCH 089/246] 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 71a3a497da..3a09076a97 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 0000000000..4741f2bc00 --- /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)) + } +} -- GitLab From 4de208786da0d7e4ca53728938fde36837f1e369 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 17 Feb 2017 22:21:43 +0100 Subject: [PATCH 090/246] 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 b739959c55..029766a31c 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 3a09076a97..8c2e6d240d 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 4741f2bc00..720af0dd99 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 0000000000..5f785ed1b9 --- /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)) + } +} -- GitLab From e86837b8787604f43d049a87d945063c4e092ed5 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Mon, 20 Feb 2017 15:25:53 +0400 Subject: [PATCH 091/246] target `evmbin` -> `evm` --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d79818f1d7..11eb4dfb69 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -26,14 +26,14 @@ linux-stable: - cargo build -j $(nproc) --release -p ethstore - cargo build -j $(nproc) --release -p ethkey - strip target/release/parity - - strip target/release/parity/evmbin + - strip target/release/parity/evm - strip target/release/parity/ethstore - strip target/release/parity/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/parity/evmbin deb/usr/bin/evmbin + - 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 - export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n") -- GitLab From 3fc29b9ae4aaef02dbcbf7ece29cb07c78df1d27 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Mon, 20 Feb 2017 13:34:33 +0100 Subject: [PATCH 092/246] Handle invalid ABI retrieved from address_book gracefully (#4606) * Handle invalid ABI gracefully * Also include failed abi in log --- js/src/ui/MethodDecoding/methodDecodingStore.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/js/src/ui/MethodDecoding/methodDecodingStore.js b/js/src/ui/MethodDecoding/methodDecodingStore.js index 379617f341..da23f916c7 100644 --- a/js/src/ui/MethodDecoding/methodDecodingStore.js +++ b/js/src/ui/MethodDecoding/methodDecodingStore.js @@ -54,9 +54,19 @@ export default class MethodDecodingStore { } loadFromAbi (_abi, contractAddress) { - const abi = new Abi(_abi); + let abi; - if (contractAddress && abi) { + try { + abi = new Abi(_abi); + } catch (error) { + console.warn('loadFromAbi', error, _abi); + } + + if (!abi) { + return; + } + + if (contractAddress) { this._contractsAbi[contractAddress] = abi; } -- GitLab From 31f5a1d2055ff3c204489feb9fdf49ad60e772bf Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Mon, 20 Feb 2017 12:48:32 +0000 Subject: [PATCH 093/246] [ci skip] js-precompiled 20170220-124210 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 308364fca2..9ededba659 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1663,7 +1663,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#b0b680999ceab7a8aa67f6977220070ccdf044e3" +source = "git+https://github.com/ethcore/js-precompiled.git#2de3aee113f561b3d8aa00ddcaf82fedf079172c" 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 55ca4ad423..4139c6f3ff 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.92", + "version": "0.3.93", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", -- GitLab From 68a25d9e1434406832f9a1c3bdd642ceb2594e90 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Mon, 20 Feb 2017 17:16:11 +0400 Subject: [PATCH 094/246] fix path to `tools` --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 11eb4dfb69..b0e4ad009f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -26,9 +26,9 @@ linux-stable: - cargo build -j $(nproc) --release -p ethstore - cargo build -j $(nproc) --release -p ethkey - strip target/release/parity - - strip target/release/parity/evm - - strip target/release/parity/ethstore - - strip target/release/parity/ethkey + - 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 -- GitLab From b9665c7cfee0c8c327f3ab140edd4eda8e5daa36 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 20 Feb 2017 18:13:21 +0300 Subject: [PATCH 095/246] Secret store - initial version (#4567) * initial secret store commit * various fixes * license * (sstore, secstore) -> secretstore * marked KeyServer trait as IPC-ready * fixed style * ignore requests with Origin header * fixed tests * fixed Origin header check --- Cargo.lock | 18 +++ Cargo.toml | 2 + parity/blockchain.rs | 4 +- parity/cli/config.full.toml | 6 + parity/cli/config.toml | 3 + parity/cli/mod.rs | 33 +++++- parity/cli/usage.txt | 10 ++ parity/configuration.rs | 32 +++++- parity/dir.rs | 8 +- parity/main.rs | 5 + parity/run.rs | 10 +- parity/secretstore.rs | 105 ++++++++++++++++++ secret_store/Cargo.toml | 25 +++++ secret_store/build.rs | 22 ++++ secret_store/src/acl_storage.rs | 51 +++++++++ secret_store/src/http_listener.rs | 176 ++++++++++++++++++++++++++++++ secret_store/src/key_server.rs | 124 +++++++++++++++++++++ secret_store/src/key_storage.rs | 115 +++++++++++++++++++ secret_store/src/lib.rs | 52 +++++++++ secret_store/src/traits.rs | 24 ++++ secret_store/src/types/all.rs | 77 +++++++++++++ secret_store/src/types/mod.rs | 20 ++++ secret_store/src/types/mod.rs.in | 17 +++ 23 files changed, 931 insertions(+), 8 deletions(-) create mode 100644 parity/secretstore.rs create mode 100644 secret_store/Cargo.toml create mode 100644 secret_store/build.rs create mode 100644 secret_store/src/acl_storage.rs create mode 100644 secret_store/src/http_listener.rs create mode 100644 secret_store/src/key_server.rs create mode 100644 secret_store/src/key_storage.rs create mode 100644 secret_store/src/lib.rs create mode 100644 secret_store/src/traits.rs create mode 100644 secret_store/src/types/all.rs create mode 100644 secret_store/src/types/mod.rs create mode 100644 secret_store/src/types/mod.rs.in diff --git a/Cargo.lock b/Cargo.lock index 9ededba659..44989c92cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,7 @@ dependencies = [ "ethcore-light 1.6.0", "ethcore-logger 1.6.0", "ethcore-rpc 1.6.0", + "ethcore-secretstore 1.0.0", "ethcore-signer 1.6.0", "ethcore-stratum 1.6.0", "ethcore-util 1.6.0", @@ -635,6 +636,23 @@ dependencies = [ "transient-hashmap 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ethcore-secretstore" +version = "1.0.0" +dependencies = [ + "ethcore-devtools 1.6.0", + "ethcore-ipc 1.6.0", + "ethcore-ipc-codegen 1.6.0", + "ethcore-ipc-nano 1.6.0", + "ethcore-util 1.6.0", + "ethcrypto 0.1.0", + "ethkey 0.2.0", + "hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ethcore-signer" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index 45179ef10e..3b6bd7e97c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ parity-updater = { path = "updater" } parity-reactor = { path = "util/reactor" } ethcore-dapps = { path = "dapps", optional = true } clippy = { version = "0.0.103", optional = true} +ethcore-secretstore = { path = "secret_store", optional = true } [dev-dependencies] ethcore-ipc-tests = { path = "ipc/tests" } @@ -83,6 +84,7 @@ evm-debug = ["ethcore/evm-debug"] evm-debug-tests = ["ethcore/evm-debug-tests"] slow-blocks = ["ethcore/slow-blocks"] final = ["ethcore-util/final"] +secretstore = ["ethcore-secretstore"] [[bin]] path = "parity/main.rs" diff --git a/parity/blockchain.rs b/parity/blockchain.rs index 59cfd0a59a..1eeb3d71e9 100644 --- a/parity/blockchain.rs +++ b/parity/blockchain.rs @@ -185,7 +185,7 @@ fn execute_import(cmd: ImportBlockchain) -> Result<(), String> { execute_upgrades(&cmd.dirs.base, &db_dirs, algorithm, cmd.compaction.compaction_profile(db_dirs.db_root_path().as_path()))?; // create dirs used by parity - cmd.dirs.create_dirs(false, false)?; + cmd.dirs.create_dirs(false, false, false)?; // prepare client config let mut client_config = to_client_config( @@ -356,7 +356,7 @@ fn start_client( execute_upgrades(&dirs.base, &db_dirs, algorithm, compaction.compaction_profile(db_dirs.db_root_path().as_path()))?; // create dirs used by parity - dirs.create_dirs(false, false)?; + dirs.create_dirs(false, false, false)?; // prepare client config let client_config = to_client_config( diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index 0983bf7924..a8b3c4fc11 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -67,6 +67,12 @@ path = "$HOME/.parity/dapps" user = "test_user" pass = "test_pass" +[secretstore] +disable = false +port = 8082 +interface = "local" +path = "$HOME/.parity/secretstore" + [ipfs] enable = false port = 5001 diff --git a/parity/cli/config.toml b/parity/cli/config.toml index 288f3b2ed8..9b356a811b 100644 --- a/parity/cli/config.toml +++ b/parity/cli/config.toml @@ -38,6 +38,9 @@ port = 8080 user = "username" pass = "password" +[secretstore] +port = 8082 + [ipfs] enable = false port = 5001 diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index a416aa4ce7..f105c9c127 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -189,6 +189,16 @@ usage! { or |c: &Config| otry!(c.dapps).pass.clone().map(Some), flag_dapps_apis_all: bool = false, or |_| None, + // Secret Store + flag_no_secretstore: bool = false, + or |c: &Config| otry!(c.secretstore).disable.clone(), + flag_secretstore_port: u16 = 8082u16, + or |c: &Config| otry!(c.secretstore).port.clone(), + flag_secretstore_interface: String = "local", + or |c: &Config| otry!(c.secretstore).interface.clone(), + flag_secretstore_path: String = "$BASE/secretstore", + or |c: &Config| otry!(c.secretstore).path.clone(), + // IPFS flag_ipfs_api: bool = false, or |c: &Config| otry!(c.ipfs).enable.clone(), @@ -327,6 +337,7 @@ struct Config { rpc: Option, ipc: Option, dapps: Option, + secretstore: Option, ipfs: Option, mining: Option, footprint: Option, @@ -416,6 +427,14 @@ struct Dapps { pass: Option, } +#[derive(Default, Debug, PartialEq, RustcDecodable)] +struct SecretStore { + disable: Option, + port: Option, + interface: Option, + path: Option, +} + #[derive(Default, Debug, PartialEq, RustcDecodable)] struct Ipfs { enable: Option, @@ -495,7 +514,8 @@ struct Misc { mod tests { use super::{ Args, ArgsError, - Config, Operating, Account, Ui, Network, Rpc, Ipc, Dapps, Ipfs, Mining, Footprint, Snapshots, VM, Misc + Config, Operating, Account, Ui, Network, Rpc, Ipc, Dapps, Ipfs, Mining, Footprint, + Snapshots, VM, Misc, SecretStore, }; use toml; @@ -650,6 +670,11 @@ mod tests { flag_dapps_pass: Some("test_pass".into()), flag_dapps_apis_all: false, + flag_no_secretstore: false, + flag_secretstore_port: 8082u16, + flag_secretstore_interface: "local".into(), + flag_secretstore_path: "$HOME/.parity/secretstore".into(), + // IPFS flag_ipfs_api: false, flag_ipfs_api_port: 5001u16, @@ -839,6 +864,12 @@ mod tests { user: Some("username".into()), pass: Some("password".into()) }), + secretstore: Some(SecretStore { + disable: None, + port: Some(8082), + interface: None, + path: None, + }), ipfs: Some(Ipfs { enable: Some(false), port: Some(5001) diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index fd19a80043..430154752b 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -179,6 +179,16 @@ API and Console Options: --ipfs-api-port PORT Configure on which port the IPFS HTTP API should listen. (default: {flag_ipfs_api_port}) +Secret Store Options: + --no-secretstore Disable Secret Store functionality. (default: {flag_no_secretstore}) + --secretstore-port PORT Specify the port portion for Secret Store Key Server + (default: {flag_secretstore_port}). + --secretstore-interface IP Specify the hostname portion for Secret Store Key Server, IP + should be an interface's IP address, or local + (default: {flag_secretstore_interface}). + --secretstore-path PATH Specify directory where Secret Store should save its data. + (default: {flag_secretstore_path}) + Sealing/Mining Options: --author ADDRESS Specify the block author (aka "coinbase") address for sending block rewards from sealed blocks. diff --git a/parity/configuration.rs b/parity/configuration.rs index 34fea453d6..dd1b8546cb 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -39,6 +39,7 @@ use dir::{self, Directories, default_hypervisor_path, default_local_path, defaul use dapps::Configuration as DappsConfiguration; use ipfs::Configuration as IpfsConfiguration; use signer::{Configuration as SignerConfiguration}; +use secretstore::Configuration as SecretStoreConfiguration; use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack}; use run::RunCmd; use blockchain::{BlockchainCmd, ImportBlockchain, ExportBlockchain, KillBlockchain, ExportState, DataFormat}; @@ -121,6 +122,7 @@ impl Configuration { let dapps_conf = self.dapps_config(); let ipfs_conf = self.ipfs_config(); let signer_conf = self.signer_config(); + let secretstore_conf = self.secretstore_config(); let format = self.format()?; let cmd = if self.args.flag_version { @@ -346,6 +348,7 @@ impl Configuration { dapps_conf: dapps_conf, ipfs_conf: ipfs_conf, signer_conf: signer_conf, + secretstore_conf: secretstore_conf, dapp: self.dapp_to_open()?, ui: self.args.cmd_ui, name: self.args.flag_identity, @@ -542,6 +545,15 @@ impl Configuration { } } + fn secretstore_config(&self) -> SecretStoreConfiguration { + SecretStoreConfiguration { + enabled: self.secretstore_enabled(), + interface: self.secretstore_interface(), + port: self.args.flag_secretstore_port, + data_path: self.directories().secretstore, + } + } + fn ipfs_config(&self) -> IpfsConfiguration { IpfsConfiguration { enabled: self.args.flag_ipfs_api, @@ -787,6 +799,7 @@ impl Configuration { let db_path = replace_home_for_db(&data_path, &local_path, &base_db_path); let keys_path = replace_home(&data_path, &self.args.flag_keys_path); let dapps_path = replace_home(&data_path, &self.args.flag_dapps_path); + let secretstore_path = replace_home(&data_path, &self.args.flag_secretstore_path); let ui_path = replace_home(&data_path, &self.args.flag_ui_path); if self.args.flag_geth && !cfg!(windows) { @@ -810,6 +823,7 @@ impl Configuration { db: db_path, dapps: dapps_path, signer: ui_path, + secretstore: secretstore_path, } } @@ -851,6 +865,13 @@ impl Configuration { }.into() } + fn secretstore_interface(&self) -> String { + match self.args.flag_secretstore_interface.as_str() { + "local" => "127.0.0.1", + x => x, + }.into() + } + fn stratum_interface(&self) -> String { match self.args.flag_stratum_interface.as_str() { "local" => "127.0.0.1", @@ -863,6 +884,10 @@ impl Configuration { !self.args.flag_dapps_off && !self.args.flag_no_dapps && cfg!(feature = "dapps") } + fn secretstore_enabled(&self) -> bool { + !self.args.flag_no_secretstore && cfg!(feature = "secretstore") + } + fn ui_enabled(&self) -> bool { if self.args.flag_force_ui { return true; @@ -1083,7 +1108,7 @@ mod tests { fn test_run_cmd() { let args = vec!["parity"]; let conf = parse(&args); - assert_eq!(conf.into_command().unwrap().cmd, Cmd::Run(RunCmd { + let mut expected = RunCmd { cache_config: Default::default(), dirs: Default::default(), spec: Default::default(), @@ -1113,6 +1138,7 @@ mod tests { dapps_conf: Default::default(), ipfs_conf: Default::default(), signer_conf: Default::default(), + secretstore_conf: Default::default(), ui: false, dapp: None, name: "".into(), @@ -1123,7 +1149,9 @@ mod tests { check_seal: true, download_old_blocks: true, verifier_settings: Default::default(), - })); + }; + expected.secretstore_conf.enabled = cfg!(feature = "secretstore"); + assert_eq!(conf.into_command().unwrap().cmd, Cmd::Run(expected)); } #[test] diff --git a/parity/dir.rs b/parity/dir.rs index f48d052a46..4342547746 100644 --- a/parity/dir.rs +++ b/parity/dir.rs @@ -45,6 +45,7 @@ pub struct Directories { pub keys: String, pub signer: String, pub dapps: String, + pub secretstore: String, } impl Default for Directories { @@ -57,12 +58,13 @@ impl Default for Directories { keys: replace_home(&data_dir, "$BASE/keys"), signer: replace_home(&data_dir, "$BASE/signer"), dapps: replace_home(&data_dir, "$BASE/dapps"), + secretstore: replace_home(&data_dir, "$BASE/secretstore"), } } } impl Directories { - pub fn create_dirs(&self, dapps_enabled: bool, signer_enabled: bool) -> Result<(), String> { + pub fn create_dirs(&self, dapps_enabled: bool, signer_enabled: bool, secretstore_enabled: bool) -> Result<(), String> { fs::create_dir_all(&self.base).map_err(|e| e.to_string())?; fs::create_dir_all(&self.db).map_err(|e| e.to_string())?; fs::create_dir_all(&self.keys).map_err(|e| e.to_string())?; @@ -72,6 +74,9 @@ impl Directories { if dapps_enabled { fs::create_dir_all(&self.dapps).map_err(|e| e.to_string())?; } + if secretstore_enabled { + fs::create_dir_all(&self.secretstore).map_err(|e| e.to_string())?; + } Ok(()) } @@ -241,6 +246,7 @@ mod tests { keys: replace_home(&data_dir, "$BASE/keys"), signer: replace_home(&data_dir, "$BASE/signer"), dapps: replace_home(&data_dir, "$BASE/dapps"), + secretstore: replace_home(&data_dir, "$BASE/secretstore"), }; assert_eq!(expected, Directories::default()); } diff --git a/parity/main.rs b/parity/main.rs index bd4afca6ce..eefd42a94b 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -66,6 +66,10 @@ extern crate log as rlog; #[cfg(feature="stratum")] extern crate ethcore_stratum; + +#[cfg(feature="secretstore")] +extern crate ethcore_secretstore; + #[cfg(feature = "dapps")] extern crate ethcore_dapps; @@ -101,6 +105,7 @@ mod rpc_apis; mod run; mod signer; mod snapshot; +mod secretstore; mod upgrade; mod url; mod user_defaults; diff --git a/parity/run.rs b/parity/run.rs index df9fc7384c..e91a8716bc 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -49,6 +49,7 @@ use user_defaults::UserDefaults; use dapps; use ipfs; use signer; +use secretstore; use modules; use rpc_apis; use rpc; @@ -96,6 +97,7 @@ pub struct RunCmd { pub dapps_conf: dapps::Configuration, pub ipfs_conf: ipfs::Configuration, pub signer_conf: signer::Configuration, + pub secretstore_conf: secretstore::Configuration, pub dapp: Option, pub ui: bool, pub name: String, @@ -190,7 +192,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R execute_upgrades(&cmd.dirs.base, &db_dirs, algorithm, cmd.compaction.compaction_profile(db_dirs.db_root_path().as_path()))?; // create dirs used by parity - cmd.dirs.create_dirs(cmd.dapps_conf.enabled, cmd.signer_conf.enabled)?; + cmd.dirs.create_dirs(cmd.dapps_conf.enabled, cmd.signer_conf.enabled, cmd.secretstore_conf.enabled)?; // run in daemon mode if let Some(pid_file) = cmd.daemon { @@ -422,6 +424,10 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R }; let signer_server = signer::start(cmd.signer_conf.clone(), signer_deps)?; + // secret store key server + let secretstore_deps = secretstore::Dependencies { }; + let secretstore_key_server = secretstore::start(cmd.secretstore_conf.clone(), secretstore_deps); + // the ipfs server let ipfs_server = match cmd.ipfs_conf.enabled { true => Some(ipfs::start_server(cmd.ipfs_conf.port, client.clone())?), @@ -484,7 +490,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R let restart = wait_for_exit(panic_handler, Some(updater), can_restart); // drop this stuff as soon as exit detected. - drop((http_server, ipc_server, dapps_server, signer_server, ipfs_server, event_loop)); + drop((http_server, ipc_server, dapps_server, signer_server, secretstore_key_server, ipfs_server, event_loop)); info!("Finishing work, please wait..."); diff --git a/parity/secretstore.rs b/parity/secretstore.rs new file mode 100644 index 0000000000..79a2095043 --- /dev/null +++ b/parity/secretstore.rs @@ -0,0 +1,105 @@ +// 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 . + +use dir::default_data_path; +use helpers::replace_home; + +#[derive(Debug, PartialEq, Clone)] +/// Secret store configuration +pub struct Configuration { + /// Is secret store functionality enabled? + pub enabled: bool, + /// Interface to listen to + pub interface: String, + /// Port to listen to + pub port: u16, + /// Data directory path for secret store + pub data_path: String, +} + +#[derive(Debug, PartialEq, Clone)] +/// Secret store dependencies +pub struct Dependencies { + // the only dependency will be BlockChainClient +} + +#[cfg(not(feature = "secretstore"))] +mod server { + use super::{Configuration, Dependencies}; + + /// Noop key server implementation + pub struct KeyServer; + + impl KeyServer { + /// Create new noop key server + pub fn new(_conf: Configuration, _deps: Dependencies) -> Result { + Ok(KeyServer) + } + } +} + +#[cfg(feature="secretstore")] +mod server { + use ethcore_secretstore; + use super::{Configuration, Dependencies}; + + /// Key server + pub struct KeyServer { + _key_server: Box, + } + + impl KeyServer { + /// Create new key server + pub fn new(conf: Configuration, _deps: Dependencies) -> Result { + let conf = ethcore_secretstore::ServiceConfiguration { + listener_addr: conf.interface, + listener_port: conf.port, + data_path: conf.data_path, + }; + + let key_server = ethcore_secretstore::start(conf) + .map_err(Into::::into)?; + + Ok(KeyServer { + _key_server: key_server, + }) + } + } +} + +pub use self::server::KeyServer; + +impl Default for Configuration { + fn default() -> Self { + let data_dir = default_data_path(); + Configuration { + enabled: true, + interface: "127.0.0.1".to_owned(), + port: 8082, + data_path: replace_home(&data_dir, "$BASE/secretstore"), + } + } +} + +/// Start secret store-related functionality +pub fn start(conf: Configuration, deps: Dependencies) -> Result, String> { + if !conf.enabled { + return Ok(None); + } + + KeyServer::new(conf, deps) + .map(|s| Some(s)) +} diff --git a/secret_store/Cargo.toml b/secret_store/Cargo.toml new file mode 100644 index 0000000000..111ff5affc --- /dev/null +++ b/secret_store/Cargo.toml @@ -0,0 +1,25 @@ +[package] +description = "Ethcore Secret Store" +name = "ethcore-secretstore" +version = "1.0.0" +license = "GPL-3.0" +authors = ["Parity Technologies "] +build = "build.rs" + +[build-dependencies] +ethcore-ipc-codegen = { path = "../ipc/codegen" } + +[dependencies] +log = "0.3" +parking_lot = "0.3" +hyper = { version = "0.9", default-features = false } +url = "1.0" +ethcore-devtools = { path = "../devtools" } +ethcore-util = { path = "../util" } +ethcore-ipc = { path = "../ipc/rpc" } +ethcore-ipc-nano = { path = "../ipc/nano" } +ethcrypto = { path = "../ethcrypto" } +ethkey = { path = "../ethkey" } + +[profile.release] +debug = true diff --git a/secret_store/build.rs b/secret_store/build.rs new file mode 100644 index 0000000000..b2b27ea1ee --- /dev/null +++ b/secret_store/build.rs @@ -0,0 +1,22 @@ +// 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 . + +extern crate ethcore_ipc_codegen; + +fn main() { + ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap(); + ethcore_ipc_codegen::derive_ipc_cond("src/traits.rs", cfg!(feature="ipc")).unwrap(); +} diff --git a/secret_store/src/acl_storage.rs b/secret_store/src/acl_storage.rs new file mode 100644 index 0000000000..47ec3d44a2 --- /dev/null +++ b/secret_store/src/acl_storage.rs @@ -0,0 +1,51 @@ +// 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 . + +use std::collections::{HashMap, HashSet}; +use parking_lot::RwLock; +use types::all::{Error, DocumentAddress, Public}; + +/// ACL storage of Secret Store +pub trait AclStorage: Send + Sync { + /// Check if requestor with `public` key can access document with hash `document` + fn check(&self, public: &Public, document: &DocumentAddress) -> Result; +} + +/// Dummy ACL storage implementation +#[derive(Default, Debug)] +pub struct DummyAclStorage { + prohibited: RwLock>>, +} + +impl DummyAclStorage { + #[cfg(test)] + /// Prohibit given requestor access to given document + pub fn prohibit(&self, public: Public, document: DocumentAddress) { + self.prohibited.write() + .entry(public) + .or_insert_with(Default::default) + .insert(document); + } +} + +impl AclStorage for DummyAclStorage { + fn check(&self, public: &Public, document: &DocumentAddress) -> Result { + Ok(self.prohibited.read() + .get(public) + .map(|docs| !docs.contains(document)) + .unwrap_or(true)) + } +} diff --git a/secret_store/src/http_listener.rs b/secret_store/src/http_listener.rs new file mode 100644 index 0000000000..92799d2215 --- /dev/null +++ b/secret_store/src/http_listener.rs @@ -0,0 +1,176 @@ +// 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 . + +use std::str::FromStr; +use std::sync::Arc; +use hyper::header; +use hyper::uri::RequestUri; +use hyper::method::Method as HttpMethod; +use hyper::status::StatusCode as HttpStatusCode; +use hyper::server::{Server as HttpServer, Request as HttpRequest, Response as HttpResponse, Handler as HttpHandler, + Listening as HttpListening}; +use url::percent_encoding::percent_decode; + +use util::ToPretty; +use traits::KeyServer; +use types::all::{Error, ServiceConfiguration, RequestSignature, DocumentAddress, DocumentEncryptedKey}; + +/// Key server http-requests listener +pub struct KeyServerHttpListener { + _http_server: HttpListening, + handler: Arc>, +} + +/// Parsed http request +#[derive(Debug, Clone, PartialEq)] +enum Request { + /// Invalid request + Invalid, + /// Request encryption key of given document for given requestor + GetDocumentKey(DocumentAddress, RequestSignature), +} + +/// Cloneable http handler +struct KeyServerHttpHandler { + handler: Arc>, +} + +/// Shared http handler +struct KeyServerSharedHttpHandler { + key_server: T, +} + +impl KeyServerHttpListener where T: KeyServer + 'static { + /// Start KeyServer http listener + pub fn start(config: ServiceConfiguration, key_server: T) -> Result { + let shared_handler = Arc::new(KeyServerSharedHttpHandler { + key_server: key_server, + }); + let handler = KeyServerHttpHandler { + handler: shared_handler.clone(), + }; + + let listener_addr: &str = &format!("{}:{}", config.listener_addr, config.listener_port); + let http_server = HttpServer::http(&listener_addr).unwrap(); + let http_server = http_server.handle(handler).unwrap(); + let listener = KeyServerHttpListener { + _http_server: http_server, + handler: shared_handler, + }; + Ok(listener) + } +} + +impl KeyServer for KeyServerHttpListener where T: KeyServer + 'static { + fn document_key(&self, signature: &RequestSignature, document: &DocumentAddress) -> Result { + self.handler.key_server.document_key(signature, document) + } +} + +impl HttpHandler for KeyServerHttpHandler where T: KeyServer + 'static { + fn handle(&self, req: HttpRequest, mut res: HttpResponse) { + if req.method != HttpMethod::Get { + warn!(target: "secretstore", "Ignoring {}-request {}", req.method, req.uri); + *res.status_mut() = HttpStatusCode::NotFound; + return; + } + + if req.headers.has::() { + warn!(target: "secretstore", "Ignoring {}-request {} with Origin header", req.method, req.uri); + *res.status_mut() = HttpStatusCode::NotFound; + return; + } + + match req.uri { + RequestUri::AbsolutePath(ref path) => match parse_request(&path) { + Request::GetDocumentKey(document, signature) => { + let document_key = self.handler.key_server.document_key(&signature, &document) + .map_err(|err| { + warn!(target: "secretstore", "GetDocumentKey request {} has failed with: {}", req.uri, err); + err + }); + match document_key { + Ok(document_key) => { + let document_key = document_key.to_hex().into_bytes(); + res.headers_mut().set(header::ContentType::plaintext()); + if let Err(err) = res.send(&document_key) { + // nothing to do, but log error + warn!(target: "secretstore", "GetDocumentKey request {} response has failed with: {}", req.uri, err); + } + }, + Err(Error::BadSignature) => *res.status_mut() = HttpStatusCode::BadRequest, + Err(Error::AccessDenied) => *res.status_mut() = HttpStatusCode::Forbidden, + Err(Error::DocumentNotFound) => *res.status_mut() = HttpStatusCode::NotFound, + Err(Error::Database(_)) => *res.status_mut() = HttpStatusCode::InternalServerError, + Err(Error::Internal(_)) => *res.status_mut() = HttpStatusCode::InternalServerError, + } + }, + Request::Invalid => { + warn!(target: "secretstore", "Ignoring invalid {}-request {}", req.method, req.uri); + *res.status_mut() = HttpStatusCode::BadRequest; + }, + }, + _ => { + warn!(target: "secretstore", "Ignoring invalid {}-request {}", req.method, req.uri); + *res.status_mut() = HttpStatusCode::NotFound; + }, + }; + } +} + +fn parse_request(uri_path: &str) -> Request { + let uri_path = match percent_decode(uri_path.as_bytes()).decode_utf8() { + Ok(path) => path, + Err(_) => return Request::Invalid, + }; + + let path: Vec = uri_path.trim_left_matches('/').split('/').map(Into::into).collect(); + if path.len() != 2 || path[0].is_empty() || path[1].is_empty() { + return Request::Invalid; + } + + let document = DocumentAddress::from_str(&path[0]); + let signature = RequestSignature::from_str(&path[1]); + match (document, signature) { + (Ok(document), Ok(signature)) => Request::GetDocumentKey(document, signature), + _ => Request::Invalid, + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + use super::super::RequestSignature; + use super::{parse_request, Request}; + + #[test] + fn parse_request_successful() { + assert_eq!(parse_request("/0000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01"), + Request::GetDocumentKey("0000000000000000000000000000000000000000000000000000000000000001".into(), + RequestSignature::from_str("a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01").unwrap())); + assert_eq!(parse_request("/%30000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01"), + Request::GetDocumentKey("0000000000000000000000000000000000000000000000000000000000000001".into(), + RequestSignature::from_str("a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01").unwrap())); + } + + #[test] + fn parse_request_failed() { + assert_eq!(parse_request("/0000000000000000000000000000000000000000000000000000000000000001"), Request::Invalid); + assert_eq!(parse_request("/0000000000000000000000000000000000000000000000000000000000000001/"), Request::Invalid); + assert_eq!(parse_request("/a/b"), Request::Invalid); + assert_eq!(parse_request("/0000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01/0000000000000000000000000000000000000000000000000000000000000002"), Request::Invalid); + } +} diff --git a/secret_store/src/key_server.rs b/secret_store/src/key_server.rs new file mode 100644 index 0000000000..32ac480312 --- /dev/null +++ b/secret_store/src/key_server.rs @@ -0,0 +1,124 @@ +// 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 . + +use ethcrypto; +use ethkey; +use super::acl_storage::AclStorage; +use super::key_storage::KeyStorage; +use traits::KeyServer; +use types::all::{Error, RequestSignature, DocumentAddress, DocumentEncryptedKey}; + +/// Secret store key server implementation +pub struct KeyServerImpl { + acl_storage: T, + key_storage: U, +} + +impl KeyServerImpl where T: AclStorage, U: KeyStorage { + /// Create new key server instance + pub fn new(acl_storage: T, key_storage: U) -> Self { + KeyServerImpl { + acl_storage: acl_storage, + key_storage: key_storage, + } + } +} + +impl KeyServer for KeyServerImpl where T: AclStorage, U: KeyStorage { + fn document_key(&self, signature: &RequestSignature, document: &DocumentAddress) -> Result { + // recover requestor' public key from signature + let public = ethkey::recover(signature, document) + .map_err(|_| Error::BadSignature)?; + + // check that requestor has access to the document + if !self.acl_storage.check(&public, document)? { + return Err(Error::AccessDenied); + } + + // read unencrypted document key + let document_key = self.key_storage.get(document)?; + // encrypt document key with requestor public key + let document_key = ethcrypto::ecies::encrypt_single_message(&public, &document_key) + .map_err(|err| Error::Internal(format!("Error encrypting document key: {}", err)))?; + Ok(document_key) + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + use ethcrypto; + use ethkey::{self, Secret}; + use acl_storage::DummyAclStorage; + use key_storage::KeyStorage; + use key_storage::tests::DummyKeyStorage; + use super::super::{Error, RequestSignature, DocumentAddress}; + use super::{KeyServer, KeyServerImpl}; + + const DOCUMENT1: &'static str = "0000000000000000000000000000000000000000000000000000000000000001"; + const DOCUMENT2: &'static str = "0000000000000000000000000000000000000000000000000000000000000002"; + const KEY1: &'static str = "key1"; + const PRIVATE1: &'static str = "03055e18a8434dcc9061cc1b81c4ef84dc7cf4574d755e52cdcf0c8898b25b11"; + const PUBLIC2: &'static str = "dfe62f56bb05fbd85b485bac749f3410309e24b352bac082468ce151e9ddb94fa7b5b730027fe1c7c5f3d5927621d269f91aceb5caa3c7fe944677a22f88a318"; + const PRIVATE2: &'static str = "0eb3816f4f705fa0fd952fb27b71b8c0606f09f4743b5b65cbc375bd569632f2"; + + fn create_key_server() -> KeyServerImpl { + let acl_storage = DummyAclStorage::default(); + let key_storage = DummyKeyStorage::default(); + key_storage.insert(DOCUMENT1.into(), KEY1.into()).unwrap(); + acl_storage.prohibit(PUBLIC2.into(), DOCUMENT1.into()); + KeyServerImpl::new(acl_storage, key_storage) + } + + fn make_signature(secret: &str, document: &'static str) -> RequestSignature { + let secret = Secret::from_str(secret).unwrap(); + let document: DocumentAddress = document.into(); + ethkey::sign(&secret, &document).unwrap() + } + + #[test] + fn document_key_succeeds() { + let key_server = create_key_server(); + let signature = make_signature(PRIVATE1, DOCUMENT1); + let document_key = key_server.document_key(&signature, &DOCUMENT1.into()).unwrap(); + let document_key = ethcrypto::ecies::decrypt_single_message(&Secret::from_str(PRIVATE1).unwrap(), &document_key); + assert_eq!(document_key, Ok(KEY1.into())); + } + + #[test] + fn document_key_fails_when_bad_signature() { + let key_server = create_key_server(); + let signature = RequestSignature::default(); + let document_key = key_server.document_key(&signature, &DOCUMENT1.into()); + assert_eq!(document_key, Err(Error::BadSignature)); + } + + #[test] + fn document_key_fails_when_acl_check_fails() { + let key_server = create_key_server(); + let signature = make_signature(PRIVATE2, DOCUMENT1); + let document_key = key_server.document_key(&signature, &DOCUMENT1.into()); + assert_eq!(document_key, Err(Error::AccessDenied)); + } + + #[test] + fn document_key_fails_when_document_not_found() { + let key_server = create_key_server(); + let signature = make_signature(PRIVATE1, DOCUMENT2); + let document_key = key_server.document_key(&signature, &DOCUMENT2.into()); + assert_eq!(document_key, Err(Error::DocumentNotFound)); + } +} diff --git a/secret_store/src/key_storage.rs b/secret_store/src/key_storage.rs new file mode 100644 index 0000000000..fe77774104 --- /dev/null +++ b/secret_store/src/key_storage.rs @@ -0,0 +1,115 @@ +// 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 . + +use std::path::PathBuf; +use util::Database; +use types::all::{Error, ServiceConfiguration, DocumentAddress, DocumentKey}; + +/// Document encryption keys storage +pub trait KeyStorage: Send + Sync { + /// Insert document encryption key + fn insert(&self, document: DocumentAddress, key: DocumentKey) -> Result<(), Error>; + /// Get document encryption key + fn get(&self, document: &DocumentAddress) -> Result; +} + +/// Persistent document encryption keys storage +pub struct PersistentKeyStorage { + db: Database, +} + +impl PersistentKeyStorage { + /// Create new persistent document encryption keys storage + pub fn new(config: &ServiceConfiguration) -> Result { + let mut db_path = PathBuf::from(&config.data_path); + db_path.push("db"); + let db_path = db_path.to_str().ok_or(Error::Database("Invalid secretstore path".to_owned()))?; + + Ok(PersistentKeyStorage { + db: Database::open_default(&db_path).map_err(Error::Database)?, + }) + } +} + +impl KeyStorage for PersistentKeyStorage { + fn insert(&self, document: DocumentAddress, key: DocumentKey) -> Result<(), Error> { + let mut batch = self.db.transaction(); + batch.put(None, &document, &key); + self.db.write(batch).map_err(Error::Database) + } + + fn get(&self, document: &DocumentAddress) -> Result { + self.db.get(None, document) + .map_err(Error::Database)? + .ok_or(Error::DocumentNotFound) + .map(|key| key.to_vec()) + } +} + +#[cfg(test)] +pub mod tests { + use std::collections::HashMap; + use parking_lot::RwLock; + use devtools::RandomTempPath; + use super::super::types::all::{Error, ServiceConfiguration, DocumentAddress, DocumentKey}; + use super::{KeyStorage, PersistentKeyStorage}; + + #[derive(Default)] + /// In-memory document encryption keys storage + pub struct DummyKeyStorage { + keys: RwLock>, + } + + impl KeyStorage for DummyKeyStorage { + fn insert(&self, document: DocumentAddress, key: DocumentKey) -> Result<(), Error> { + self.keys.write().insert(document, key); + Ok(()) + } + + fn get(&self, document: &DocumentAddress) -> Result { + self.keys.read().get(document).cloned().ok_or(Error::DocumentNotFound) + } + } + + #[test] + fn persistent_key_storage() { + let path = RandomTempPath::create_dir(); + let config = ServiceConfiguration { + listener_addr: "0.0.0.0".to_owned(), + listener_port: 8082, + data_path: path.as_str().to_owned(), + }; + + let key1 = DocumentAddress::from(1); + let value1: DocumentKey = vec![0x77, 0x88]; + let key2 = DocumentAddress::from(2); + let value2: DocumentKey = vec![0x11, 0x22]; + let key3 = DocumentAddress::from(3); + + let key_storage = PersistentKeyStorage::new(&config).unwrap(); + key_storage.insert(key1.clone(), value1.clone()).unwrap(); + key_storage.insert(key2.clone(), value2.clone()).unwrap(); + assert_eq!(key_storage.get(&key1), Ok(value1.clone())); + assert_eq!(key_storage.get(&key2), Ok(value2.clone())); + assert_eq!(key_storage.get(&key3), Err(Error::DocumentNotFound)); + drop(key_storage); + + let key_storage = PersistentKeyStorage::new(&config).unwrap(); + assert_eq!(key_storage.get(&key1), Ok(value1)); + assert_eq!(key_storage.get(&key2), Ok(value2)); + assert_eq!(key_storage.get(&key3), Err(Error::DocumentNotFound)); + } +} diff --git a/secret_store/src/lib.rs b/secret_store/src/lib.rs new file mode 100644 index 0000000000..390ae1e5e8 --- /dev/null +++ b/secret_store/src/lib.rs @@ -0,0 +1,52 @@ +// 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 . + +#[macro_use] +extern crate log; +extern crate hyper; +extern crate parking_lot; +extern crate url; + +extern crate ethcore_devtools as devtools; +extern crate ethcore_util as util; +extern crate ethcore_ipc as ipc; +extern crate ethcrypto; +extern crate ethkey; + +mod types; + +mod traits { + #![allow(dead_code, unused_assignments, unused_variables, missing_docs)] // codegen issues + include!(concat!(env!("OUT_DIR"), "/traits.rs")); +} + +mod acl_storage; +mod http_listener; +mod key_server; +mod key_storage; + +pub use types::all::{DocumentAddress, DocumentKey, DocumentEncryptedKey, RequestSignature, Public, + Error, ServiceConfiguration}; +pub use traits::{KeyServer}; + +/// Start new key server instance +pub fn start(config: ServiceConfiguration) -> Result, Error> { + let acl_storage = acl_storage::DummyAclStorage::default(); + let key_storage = key_storage::PersistentKeyStorage::new(&config)?; + let key_server = key_server::KeyServerImpl::new(acl_storage, key_storage); + let listener = http_listener::KeyServerHttpListener::start(config, key_server)?; + Ok(Box::new(listener)) +} diff --git a/secret_store/src/traits.rs b/secret_store/src/traits.rs new file mode 100644 index 0000000000..9a68e9c4d5 --- /dev/null +++ b/secret_store/src/traits.rs @@ -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 . + +use types::all::{Error, RequestSignature, DocumentAddress, DocumentEncryptedKey}; + +#[ipc(client_ident="RemoteKeyServer")] +/// Secret store key server +pub trait KeyServer: Send + Sync { + /// Request encryption key of given document for given requestor + fn document_key(&self, signature: &RequestSignature, document: &DocumentAddress) -> Result; +} diff --git a/secret_store/src/types/all.rs b/secret_store/src/types/all.rs new file mode 100644 index 0000000000..f318e65431 --- /dev/null +++ b/secret_store/src/types/all.rs @@ -0,0 +1,77 @@ +// 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 . + +use std::fmt; + +use ethkey; +use util; + +/// Document address type. +pub type DocumentAddress = util::H256; +/// Document key type. +pub type DocumentKey = util::Bytes; +/// Encrypted key type. +pub type DocumentEncryptedKey = util::Bytes; +/// Request signature type. +pub type RequestSignature = ethkey::Signature; +/// Public key type. +pub use ethkey::Public; + +#[derive(Debug, Clone, PartialEq)] +#[binary] +/// Secret store error +pub enum Error { + /// Bad signature is passed + BadSignature, + /// Access to resource is denied + AccessDenied, + /// Requested document not found + DocumentNotFound, + /// Database-related error + Database(String), + /// Internal error + Internal(String), +} + +#[derive(Debug)] +#[binary] +/// Secret store configuration +pub struct ServiceConfiguration { + /// Interface to listen to + pub listener_addr: String, + /// Port to listen to + pub listener_port: u16, + /// Data directory path for secret store + pub data_path: String, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match *self { + Error::BadSignature => write!(f, "Bad signature"), + Error::AccessDenied => write!(f, "Access dened"), + Error::DocumentNotFound => write!(f, "Document not found"), + Error::Database(ref msg) => write!(f, "Database error: {}", msg), + Error::Internal(ref msg) => write!(f, "Internal error: {}", msg), + } + } +} + +impl Into for Error { + fn into(self) -> String { + format!("{}", self) + } +} diff --git a/secret_store/src/types/mod.rs b/secret_store/src/types/mod.rs new file mode 100644 index 0000000000..584e78f30c --- /dev/null +++ b/secret_store/src/types/mod.rs @@ -0,0 +1,20 @@ +// 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 . + +//! Types used in the public api + +#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues +include!(concat!(env!("OUT_DIR"), "/mod.rs.in")); diff --git a/secret_store/src/types/mod.rs.in b/secret_store/src/types/mod.rs.in new file mode 100644 index 0000000000..0681e28845 --- /dev/null +++ b/secret_store/src/types/mod.rs.in @@ -0,0 +1,17 @@ +// 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 . + +pub mod all; -- GitLab From 0aad8a87ae2e16d78525c6ae89dee68f80263d17 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Mon, 20 Feb 2017 16:19:43 +0100 Subject: [PATCH 096/246] Added pending transaction info to eth_getTransactionByHash (#4570) * Return condition info for pending transactions * Fixed warnings --- ethcore/src/miner/miner.rs | 6 +++--- ethcore/src/miner/mod.rs | 2 +- ethcore/src/miner/transaction_queue.rs | 4 ++-- rpc/src/v1/tests/helpers/miner_service.rs | 4 ++-- util/bigint/src/hash.rs | 1 - util/network/src/discovery.rs | 1 - 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 06949f4bd7..b4a7f2327d 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -984,7 +984,7 @@ impl MinerService for Miner { } } - fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option { + fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option { let queue = self.transaction_queue.lock(); match self.options.pending_set { PendingSet::AlwaysQueue => queue.find(hash), @@ -992,14 +992,14 @@ impl MinerService for Miner { self.from_pending_block( best_block, || queue.find(hash), - |sealing| sealing.transactions().iter().find(|t| &t.hash() == hash).cloned() + |sealing| sealing.transactions().iter().find(|t| &t.hash() == hash).cloned().map(Into::into) ) }, PendingSet::AlwaysSealing => { self.from_pending_block( best_block, || None, - |sealing| sealing.transactions().iter().find(|t| &t.hash() == hash).cloned() + |sealing| sealing.transactions().iter().find(|t| &t.hash() == hash).cloned().map(Into::into) ) }, } diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index d88a261f36..eee9d1c5c7 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -148,7 +148,7 @@ pub trait MinerService : Send + Sync { where F: FnOnce(&ClosedBlock) -> T, Self: Sized; /// Query pending transactions for hash. - fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option; + fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option; /// Get a list of all pending transactions in the queue. fn pending_transactions(&self) -> Vec; diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index ef7094a90c..d937c15fc6 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -1137,8 +1137,8 @@ impl TransactionQueue { } /// Finds transaction in the queue by hash (if any) - pub fn find(&self, hash: &H256) -> Option { - self.by_hash.get(hash).map(|tx| tx.transaction.clone()) + pub fn find(&self, hash: &H256) -> Option { + self.by_hash.get(hash).map(|tx| PendingTransaction { transaction: tx.transaction.clone(), condition: tx.condition.clone() }) } /// Removes all elements (in any state) from the queue diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 82b776d006..75ca928b4d 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -200,8 +200,8 @@ impl MinerService for TestMinerService { Some(f(&open_block.close())) } - fn transaction(&self, _best_block: BlockNumber, hash: &H256) -> Option { - self.pending_transactions.lock().get(hash).cloned() + fn transaction(&self, _best_block: BlockNumber, hash: &H256) -> Option { + self.pending_transactions.lock().get(hash).cloned().map(Into::into) } fn pending_transactions(&self) -> Vec { diff --git a/util/bigint/src/hash.rs b/util/bigint/src/hash.rs index 1eca9860cc..cf74ea0cbe 100644 --- a/util/bigint/src/hash.rs +++ b/util/bigint/src/hash.rs @@ -504,7 +504,6 @@ pub type H256FastSet = HashSet>; #[cfg(test)] mod tests { use hash::*; - use bigint::*; use std::str::FromStr; #[test] diff --git a/util/network/src/discovery.rs b/util/network/src/discovery.rs index 5cbe3f09e1..04ad0b7ce4 100644 --- a/util/network/src/discovery.rs +++ b/util/network/src/discovery.rs @@ -556,7 +556,6 @@ impl Discovery { mod tests { use super::*; use std::net::{SocketAddr}; - use util::sha3::Hashable; use util::FixedHash; use node_table::{Node, NodeId, NodeEndpoint}; -- GitLab From 1949d44d0c44a550f16fb93f3ee3ce0e6b7b70f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 20 Feb 2017 16:30:14 +0100 Subject: [PATCH 097/246] Hash-fetch errors in case upstream returns non-200 (#4599) --- hash-fetch/src/client.rs | 139 ++++++++++++++++++++++++++++++++++++++ hash-fetch/src/urlhint.rs | 10 +-- util/fetch/src/client.rs | 33 +++++++++ util/fetch/src/lib.rs | 2 + 4 files changed, 179 insertions(+), 5 deletions(-) diff --git a/hash-fetch/src/client.rs b/hash-fetch/src/client.rs index 453f8925aa..cffc10e636 100644 --- a/hash-fetch/src/client.rs +++ b/hash-fetch/src/client.rs @@ -50,12 +50,31 @@ pub enum Error { /// Computed hash got: H256, }, + /// Server didn't respond with OK status. + InvalidStatus, /// IO Error while validating hash. IO(io::Error), /// Error during fetch. Fetch(FetchError), } +#[cfg(test)] +impl PartialEq for Error { + fn eq(&self, other: &Self) -> bool { + use Error::*; + match (self, other) { + (&HashMismatch { expected, got }, &HashMismatch { expected: e, got: g }) => { + expected == e && got == g + }, + (&NoResolution, &NoResolution) => true, + (&InvalidStatus, &InvalidStatus) => true, + (&IO(_), &IO(_)) => true, + (&Fetch(_), &Fetch(_)) => true, + _ => false, + } + } +} + impl From for Error { fn from(error: FetchError) -> Self { Error::Fetch(error) @@ -115,6 +134,10 @@ impl HashFetch for Client { let future = self.fetch.fetch(&url).then(move |result| { fn validate_hash(path: PathBuf, hash: H256, result: Result) -> Result { let response = result?; + if !response.is_success() { + return Err(Error::InvalidStatus); + } + // Read the response let mut reader = io::BufReader::new(response); let mut writer = io::BufWriter::new(fs::File::create(&path)?); @@ -160,3 +183,119 @@ fn random_temp_path() -> PathBuf { path.push(file); path } + +#[cfg(test)] +mod tests { + use std::sync::{Arc, mpsc}; + use util::{Mutex, FromHex}; + use futures::future; + use fetch::{self, Fetch}; + use parity_reactor::Remote; + use urlhint::tests::{FakeRegistrar, URLHINT}; + use super::{Error, Client, HashFetch}; + + + #[derive(Clone)] + struct FakeFetch { + return_success: bool + } + + impl Fetch for FakeFetch { + type Result = future::Ok; + + fn new() -> Result where Self: Sized { + Ok(FakeFetch { return_success: true }) + } + + fn fetch_with_abort(&self, url: &str, _abort: fetch::Abort) -> Self::Result { + assert_eq!(url, "https://ethcore.io/assets/images/ethcore-black-horizontal.png"); + future::ok(if self.return_success { + let cursor = ::std::io::Cursor::new(b"result"); + fetch::Response::from_reader(cursor) + } else { + fetch::Response::not_found() + }) + } + } + + fn registrar() -> FakeRegistrar { + let mut registrar = FakeRegistrar::new(); + registrar.responses = Mutex::new(vec![ + Ok(format!("000000000000000000000000{}", URLHINT).from_hex().unwrap()), + Ok("00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000deadcafebeefbeefcafedeaddeedfeedffffffff000000000000000000000000000000000000000000000000000000000000003d68747470733a2f2f657468636f72652e696f2f6173736574732f696d616765732f657468636f72652d626c61636b2d686f72697a6f6e74616c2e706e67000000".from_hex().unwrap()), + ]); + registrar + } + + #[test] + fn should_return_error_if_hash_not_found() { + // given + let contract = Arc::new(FakeRegistrar::new()); + let fetch = FakeFetch { return_success: false }; + let client = Client::with_fetch(contract.clone(), fetch, Remote::new_sync()); + + // when + let (tx, rx) = mpsc::channel(); + client.fetch(2.into(), Box::new(move |result| { + tx.send(result).unwrap(); + })); + + // then + let result = rx.recv().unwrap(); + assert_eq!(result.unwrap_err(), Error::NoResolution); + } + + #[test] + fn should_return_error_if_response_is_not_successful() { + // given + let registrar = Arc::new(registrar()); + let fetch = FakeFetch { return_success: false }; + let client = Client::with_fetch(registrar.clone(), fetch, Remote::new_sync()); + + // when + let (tx, rx) = mpsc::channel(); + client.fetch(2.into(), Box::new(move |result| { + tx.send(result).unwrap(); + })); + + // then + let result = rx.recv().unwrap(); + assert_eq!(result.unwrap_err(), Error::InvalidStatus); + } + #[test] + fn should_return_hash_mismatch() { + // given + let registrar = Arc::new(registrar()); + let fetch = FakeFetch { return_success: true }; + let client = Client::with_fetch(registrar.clone(), fetch, Remote::new_sync()); + + // when + let (tx, rx) = mpsc::channel(); + client.fetch(2.into(), Box::new(move |result| { + tx.send(result).unwrap(); + })); + + // then + let result = rx.recv().unwrap(); + let hash = "0x06b0a4f426f6713234b2d4b2468640bc4e0bb72657a920ad24c5087153c593c8".into(); + assert_eq!(result.unwrap_err(), Error::HashMismatch { expected: 2.into(), got: hash }); + } + + #[test] + fn should_return_path_if_hash_matches() { + // given + let registrar = Arc::new(registrar()); + let fetch = FakeFetch { return_success: true }; + let client = Client::with_fetch(registrar.clone(), fetch, Remote::new_sync()); + + // when + let (tx, rx) = mpsc::channel(); + client.fetch("0x06b0a4f426f6713234b2d4b2468640bc4e0bb72657a920ad24c5087153c593c8".into(), Box::new(move |result| { + tx.send(result).unwrap(); + })); + + // then + let result = rx.recv().unwrap(); + assert!(result.is_ok(), "Should return path, got: {:?}", result); + } +} diff --git a/hash-fetch/src/urlhint.rs b/hash-fetch/src/urlhint.rs index 227f24dc33..1588b54823 100644 --- a/hash-fetch/src/urlhint.rs +++ b/hash-fetch/src/urlhint.rs @@ -264,7 +264,7 @@ fn as_string(e: T) -> String { } #[cfg(test)] -mod tests { +pub mod tests { use std::sync::Arc; use std::str::FromStr; use rustc_serialize::hex::FromHex; @@ -273,16 +273,16 @@ mod tests { use super::guess_mime_type; use util::{Bytes, Address, Mutex, ToPretty}; - struct FakeRegistrar { + pub struct FakeRegistrar { pub calls: Arc>>, pub responses: Mutex>>, } - const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2"; - const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000"; + pub const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2"; + pub const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000"; impl FakeRegistrar { - fn new() -> Self { + pub fn new() -> Self { FakeRegistrar { calls: Arc::new(Mutex::new(Vec::new())), responses: Mutex::new( diff --git a/util/fetch/src/client.rs b/util/fetch/src/client.rs index 09fe4741b1..18c8d87d97 100644 --- a/util/fetch/src/client.rs +++ b/util/fetch/src/client.rs @@ -26,10 +26,12 @@ use mime::{self, Mime}; use parking_lot::RwLock; use reqwest; +/// Fetch abort control #[derive(Default, Debug, Clone)] pub struct Abort(Arc); impl Abort { + /// Returns `true` if request is aborted. pub fn is_aborted(&self) -> bool { self.0.load(atomic::Ordering::SeqCst) } @@ -41,9 +43,12 @@ impl From> for Abort { } } +/// Fetch pub trait Fetch: Clone + Send + Sync + 'static { + /// Result type type Result: Future + Send + 'static; + /// Creates new Fetch object. fn new() -> Result where Self: Sized; /// Spawn the future in context of this `Fetch` thread pool. @@ -76,6 +81,7 @@ pub trait Fetch: Clone + Send + Sync + 'static { const CLIENT_TIMEOUT_SECONDS: u64 = 5; +/// Fetch client pub struct Client { client: RwLock<(time::Instant, Arc)>, pool: CpuPool, @@ -189,9 +195,12 @@ impl Future for FetchTask { } } +/// Fetch Error #[derive(Debug)] pub enum Error { + /// Internal fetch error Fetch(reqwest::Error), + /// Request aborted Aborted, } @@ -204,17 +213,20 @@ impl From for Error { enum ResponseInner { Response(reqwest::Response), Reader(Box), + NotFound, } impl fmt::Debug for ResponseInner { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { ResponseInner::Response(ref response) => response.fmt(f), + ResponseInner::NotFound => write!(f, "Not found"), ResponseInner::Reader(_) => write!(f, "io Reader"), } } } +/// A fetch response type. #[derive(Debug)] pub struct Response { inner: ResponseInner, @@ -224,6 +236,7 @@ pub struct Response { } impl Response { + /// Creates new successfuly response reading from a file. pub fn from_reader(reader: R) -> Self { Response { inner: ResponseInner::Reader(Box::new(reader)), @@ -233,13 +246,31 @@ impl Response { } } + /// Creates 404 response (useful for tests) + pub fn not_found() -> Self { + Response { + inner: ResponseInner::NotFound, + abort: Abort::default(), + limit: None, + read: 0, + } + } + + /// Returns status code of this response. pub fn status(&self) -> reqwest::StatusCode { match self.inner { ResponseInner::Response(ref r) => *r.status(), + ResponseInner::NotFound => reqwest::StatusCode::NotFound, _ => reqwest::StatusCode::Ok, } } + /// Returns `true` if response status code is successful. + pub fn is_success(&self) -> bool { + self.status() == reqwest::StatusCode::Ok + } + + /// Returns `true` if content type of this response is `text/html` pub fn is_html(&self) -> bool { match self.content_type() { Some(Mime(mime::TopLevel::Text, mime::SubLevel::Html, _)) => true, @@ -247,6 +278,7 @@ impl Response { } } + /// Returns content type of this response (if present) pub fn content_type(&self) -> Option { match self.inner { ResponseInner::Response(ref r) => { @@ -266,6 +298,7 @@ impl io::Read for Response { let res = match self.inner { ResponseInner::Response(ref mut response) => response.read(buf), + ResponseInner::NotFound => return Ok(0), ResponseInner::Reader(ref mut reader) => reader.read(buf), }; diff --git a/util/fetch/src/lib.rs b/util/fetch/src/lib.rs index 34091f4bce..21905c532c 100644 --- a/util/fetch/src/lib.rs +++ b/util/fetch/src/lib.rs @@ -16,6 +16,8 @@ //! A service to fetch any HTTP / HTTPS content. +#![warn(missing_docs)] + #[macro_use] extern crate log; -- GitLab From 72998d3ce333dcbdd4848a3a6e56e2b7d9db30d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 20 Feb 2017 16:33:12 +0100 Subject: [PATCH 098/246] Proper default accounts RPCs (#4580) * Default accounts setting - account provider * RPC support for default accounts * Updating JS code * Rename whitelist to addresses --- ethcore/src/account_provider/mod.rs | 234 ++++++++++++++++---- ethcore/src/account_provider/stores.rs | 46 +++- js/src/api/rpc/parity/parity.js | 38 +++- js/src/api/subscriptions/personal.js | 6 +- js/src/jsonrpc/interfaces/parity.js | 81 ++++++- js/src/modals/DappPermissions/store.js | 4 +- js/src/modals/DappPermissions/store.spec.js | 12 +- js/src/views/ParityBar/accountStore.js | 4 +- js/src/views/ParityBar/accountStore.spec.js | 8 +- js/src/views/ParityBar/parityBar.test.js | 4 +- json/src/misc/dapps_settings.rs | 9 +- rpc/src/v1/impls/eth.rs | 7 +- rpc/src/v1/impls/light/eth.rs | 2 +- rpc/src/v1/impls/parity.rs | 19 +- rpc/src/v1/impls/parity_accounts.rs | 58 +++-- rpc/src/v1/impls/personal.rs | 2 +- rpc/src/v1/impls/signing.rs | 2 +- rpc/src/v1/impls/signing_unsafe.rs | 2 +- rpc/src/v1/tests/mocked/eth.rs | 6 +- rpc/src/v1/tests/mocked/parity.rs | 10 +- rpc/src/v1/tests/mocked/parity_accounts.rs | 59 ++++- rpc/src/v1/traits/parity_accounts.rs | 55 +++-- 22 files changed, 509 insertions(+), 159 deletions(-) diff --git a/ethcore/src/account_provider/mod.rs b/ethcore/src/account_provider/mod.rs index 568cbd4e3a..2f6d49e758 100755 --- a/ethcore/src/account_provider/mod.rs +++ b/ethcore/src/account_provider/mod.rs @@ -23,7 +23,7 @@ use self::stores::{AddressBook, DappsSettingsStore, NewDappsPolicy}; use std::fmt; use std::collections::{HashMap, HashSet}; use std::time::{Instant, Duration}; -use util::RwLock; +use util::{FixedHash, RwLock}; use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore, random_string, SecretVaultRef, StoreAccountRef}; use ethstore::dir::MemoryDirectory; @@ -241,25 +241,88 @@ impl AccountProvider { Ok(accounts.into_iter().map(|a| a.address).collect()) } - /// Sets a whitelist of accounts exposed for unknown dapps. + /// Sets addresses of accounts exposed for unknown dapps. /// `None` means that all accounts will be visible. - pub fn set_new_dapps_whitelist(&self, accounts: Option>) -> Result<(), Error> { + /// If not `None` or empty it will also override default account. + pub fn set_new_dapps_addresses(&self, accounts: Option>) -> Result<(), Error> { + let current_default = self.new_dapps_default_address()?; + self.dapps_settings.write().set_policy(match accounts { - None => NewDappsPolicy::AllAccounts, + None => NewDappsPolicy::AllAccounts { + default: current_default, + }, Some(accounts) => NewDappsPolicy::Whitelist(accounts), }); Ok(()) } - /// Gets a whitelist of accounts exposed for unknown dapps. + /// Gets addresses of accounts exposed for unknown dapps. /// `None` means that all accounts will be visible. - pub fn new_dapps_whitelist(&self) -> Result>, Error> { + pub fn new_dapps_addresses(&self) -> Result>, Error> { Ok(match self.dapps_settings.read().policy() { - NewDappsPolicy::AllAccounts => None, + NewDappsPolicy::AllAccounts { .. } => None, NewDappsPolicy::Whitelist(accounts) => Some(accounts), }) } + /// Sets a default account for unknown dapps. + /// This account will always be returned as the first one. + pub fn set_new_dapps_default_address(&self, address: Address) -> Result<(), Error> { + if !self.valid_addresses()?.contains(&address) { + return Err(SSError::InvalidAccount.into()); + } + + let mut settings = self.dapps_settings.write(); + let new_policy = match settings.policy() { + NewDappsPolicy::AllAccounts { .. } => NewDappsPolicy::AllAccounts { default: address }, + NewDappsPolicy::Whitelist(list) => NewDappsPolicy::Whitelist(Self::insert_default(list, address)), + }; + settings.set_policy(new_policy); + + Ok(()) + } + + /// Inserts given address as first in the vector, preventing duplicates. + fn insert_default(mut addresses: Vec
, default: Address) -> Vec
{ + if let Some(position) = addresses.iter().position(|address| address == &default) { + addresses.swap(0, position); + } else { + addresses.insert(0, default); + } + + addresses + } + + /// Returns a list of accounts that new dapp should see. + /// First account is always the default account. + fn new_dapps_addresses_list(&self) -> Result, Error> { + match self.dapps_settings.read().policy() { + NewDappsPolicy::AllAccounts { default } => if default.is_zero() { + self.accounts() + } else { + Ok(Self::insert_default(self.accounts()?, default)) + }, + NewDappsPolicy::Whitelist(accounts) => { + let addresses = self.filter_addresses(accounts)?; + if addresses.is_empty() { + Ok(vec![self.accounts()?.get(0).cloned().unwrap_or(0.into())]) + } else { + Ok(addresses) + } + }, + } + } + + /// Gets a default account for new dapps + /// Will return zero address in case the default is not set and there are no accounts configured. + pub fn new_dapps_default_address(&self) -> Result { + Ok(self.new_dapps_addresses_list()? + .get(0) + .cloned() + .unwrap_or(0.into()) + ) + } + /// Gets a list of dapps recently requesting accounts. pub fn recent_dapps(&self) -> Result, Error> { Ok(self.dapps_settings.read().recent_dapps()) @@ -272,41 +335,74 @@ impl AccountProvider { Ok(()) } - /// Gets addresses visile for dapp. - pub fn dapps_addresses(&self, dapp: DappId) -> Result, Error> { - let dapps = self.dapps_settings.read(); + /// Gets addresses visible for given dapp. + pub fn dapp_addresses(&self, dapp: DappId) -> Result, Error> { + let accounts = self.dapps_settings.read().settings().get(&dapp).map(|settings| { + (settings.accounts.clone(), settings.default.clone()) + }); - let accounts = dapps.settings().get(&dapp).map(|settings| settings.accounts.clone()); match accounts { - Some(accounts) => Ok(accounts), - None => match dapps.policy() { - NewDappsPolicy::AllAccounts => self.accounts(), - NewDappsPolicy::Whitelist(accounts) => self.filter_addresses(accounts), - } + Some((Some(accounts), Some(default))) => self.filter_addresses(Self::insert_default(accounts, default)), + Some((Some(accounts), None)) => self.filter_addresses(accounts), + Some((None, Some(default))) => self.filter_addresses(Self::insert_default(self.new_dapps_addresses_list()?, default)), + _ => self.new_dapps_addresses_list(), } } /// Returns default account for particular dapp falling back to other allowed accounts if necessary. - pub fn default_address(&self, dapp: DappId) -> Result { - self.dapps_addresses(dapp)? + pub fn dapp_default_address(&self, dapp: DappId) -> Result { + let dapp_default = self.dapp_addresses(dapp)? .get(0) - .cloned() - .ok_or(SSError::InvalidAccount) + .cloned(); + + match dapp_default { + Some(default) => Ok(default), + None => self.new_dapps_default_address(), + } } - /// Sets addresses visile for dapp. - pub fn set_dapps_addresses(&self, dapp: DappId, addresses: Vec
) -> Result<(), Error> { - let addresses = self.filter_addresses(addresses)?; - self.dapps_settings.write().set_accounts(dapp, addresses); + /// Sets default address for given dapp. + /// Does not alter dapp addresses, but this account will always be returned as the first one. + pub fn set_dapp_default_address(&self, dapp: DappId, address: Address) -> Result<(), Error> { + if !self.valid_addresses()?.contains(&address) { + return Err(SSError::InvalidAccount.into()); + } + + self.dapps_settings.write().set_default(dapp, address); Ok(()) } - /// Removes addresses that are neither accounts nor in address book. - fn filter_addresses(&self, addresses: Vec
) -> Result, Error> { - let valid = self.addresses_info().into_iter() + /// Sets addresses visible for given dapp. + /// If `None` - falls back to dapps addresses + /// If not `None` and not empty it will also override default account. + pub fn set_dapp_addresses(&self, dapp: DappId, addresses: Option>) -> Result<(), Error> { + let (addresses, default) = match addresses { + Some(addresses) => { + let addresses = self.filter_addresses(addresses)?; + let default = addresses.get(0).cloned(); + (Some(addresses), default) + }, + None => (None, None), + }; + + let mut settings = self.dapps_settings.write(); + if let Some(default) = default { + settings.set_default(dapp.clone(), default); + } + settings.set_accounts(dapp, addresses); + Ok(()) + } + + fn valid_addresses(&self) -> Result, Error> { + Ok(self.addresses_info().into_iter() .map(|(address, _)| address) .chain(self.accounts()?) - .collect::>(); + .collect()) + } + + /// Removes addresses that are neither accounts nor in address book. + fn filter_addresses(&self, addresses: Vec
) -> Result, Error> { + let valid = self.valid_addresses()?; Ok(addresses.into_iter() .filter(|a| valid.contains(&a)) @@ -743,44 +839,92 @@ mod tests { } #[test] - fn should_set_dapps_addresses() { + fn should_reset_dapp_addresses_to_default() { // given let ap = AccountProvider::transient_provider(); let app = DappId("app1".into()); + // add accounts to address book + ap.set_address_name(1.into(), "1".into()); + ap.set_address_name(2.into(), "2".into()); // set `AllAccounts` policy - ap.set_new_dapps_whitelist(None).unwrap(); + ap.set_new_dapps_addresses(Some(vec![1.into(), 2.into()])).unwrap(); + assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]); + + // Alter and check + ap.set_dapp_addresses(app.clone(), Some(vec![1.into(), 3.into()])).unwrap(); + assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into()]); + + // Reset back to default + ap.set_dapp_addresses(app.clone(), None).unwrap(); + assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]); + } + + #[test] + fn should_set_dapps_default_address() { + // given + let ap = AccountProvider::transient_provider(); + let app = DappId("app1".into()); + // set `AllAccounts` policy + ap.set_new_dapps_addresses(None).unwrap(); // add accounts to address book ap.set_address_name(1.into(), "1".into()); ap.set_address_name(2.into(), "2".into()); - // when - ap.set_dapps_addresses(app.clone(), vec![1.into(), 2.into(), 3.into()]).unwrap(); + ap.set_dapp_addresses(app.clone(), Some(vec![1.into(), 2.into(), 3.into()])).unwrap(); + assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]); + assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), 1.into()); - // then - assert_eq!(ap.dapps_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]); + // when setting empty list + ap.set_dapp_addresses(app.clone(), Some(vec![])).unwrap(); + + // then default account is intact + assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into()]); + assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), 1.into()); + + // alter default account + ap.set_dapp_default_address("app1".into(), 2.into()).unwrap(); + assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![2.into()]); + assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), 2.into()); } #[test] - fn should_set_dapps_policy() { + fn should_set_dapps_policy_and_default_account() { // given let ap = AccountProvider::transient_provider(); + + // default_account should be always available + assert_eq!(ap.new_dapps_default_address().unwrap(), 0.into()); + let address = ap.new_account("test").unwrap(); ap.set_address_name(1.into(), "1".into()); - // When returning nothing - ap.set_new_dapps_whitelist(Some(vec![])).unwrap(); - assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![]); + // Default account set to first account by default + assert_eq!(ap.new_dapps_default_address().unwrap(), address); + assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), address); + + // Even when returning nothing + ap.set_new_dapps_addresses(Some(vec![])).unwrap(); + // Default account is still returned + assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![address]); // change to all - ap.set_new_dapps_whitelist(None).unwrap(); - assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![address]); + ap.set_new_dapps_addresses(None).unwrap(); + assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![address]); // change to non-existent account - ap.set_new_dapps_whitelist(Some(vec![2.into()])).unwrap(); - assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![]); + ap.set_new_dapps_addresses(Some(vec![2.into()])).unwrap(); + assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![address]); + + // change to a addresses + ap.set_new_dapps_addresses(Some(vec![1.into()])).unwrap(); + assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![1.into()]); + + // it overrides default account + assert_eq!(ap.new_dapps_default_address().unwrap(), 1.into()); + assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), 1.into()); - // change to a whitelist - ap.set_new_dapps_whitelist(Some(vec![1.into()])).unwrap(); - assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![1.into()]); + ap.set_new_dapps_default_address(address).unwrap(); + assert_eq!(ap.new_dapps_default_address().unwrap(), address); + assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), address); } } diff --git a/ethcore/src/account_provider/stores.rs b/ethcore/src/account_provider/stores.rs index e4bd7e1b95..72bc04da65 100644 --- a/ethcore/src/account_provider/stores.rs +++ b/ethcore/src/account_provider/stores.rs @@ -92,13 +92,16 @@ impl AddressBook { #[derive(Debug, Default, Clone, Eq, PartialEq)] pub struct DappsSettings { /// A list of visible accounts - pub accounts: Vec
, + pub accounts: Option>, + /// Default account + pub default: Option
, } impl From for DappsSettings { fn from(s: JsonSettings) -> Self { DappsSettings { - accounts: s.accounts.into_iter().map(Into::into).collect(), + accounts: s.accounts.map(|accounts| accounts.into_iter().map(Into::into).collect()), + default: s.default.map(Into::into), } } } @@ -106,7 +109,8 @@ impl From for DappsSettings { impl From for JsonSettings { fn from(s: DappsSettings) -> Self { JsonSettings { - accounts: s.accounts.into_iter().map(Into::into).collect(), + accounts: s.accounts.map(|accounts| accounts.into_iter().map(Into::into).collect()), + default: s.default.map(Into::into), } } } @@ -114,14 +118,18 @@ impl From for JsonSettings { /// Dapps user settings #[derive(Debug, Clone, Eq, PartialEq)] pub enum NewDappsPolicy { - AllAccounts, + AllAccounts { + default: Address, + }, Whitelist(Vec
), } impl From for NewDappsPolicy { fn from(s: JsonNewDappsPolicy) -> Self { match s { - JsonNewDappsPolicy::AllAccounts => NewDappsPolicy::AllAccounts, + JsonNewDappsPolicy::AllAccounts { default } => NewDappsPolicy::AllAccounts { + default: default.into(), + }, JsonNewDappsPolicy::Whitelist(accounts) => NewDappsPolicy::Whitelist( accounts.into_iter().map(Into::into).collect() ), @@ -132,7 +140,9 @@ impl From for NewDappsPolicy { impl From for JsonNewDappsPolicy { fn from(s: NewDappsPolicy) -> Self { match s { - NewDappsPolicy::AllAccounts => JsonNewDappsPolicy::AllAccounts, + NewDappsPolicy::AllAccounts { default } => JsonNewDappsPolicy::AllAccounts { + default: default.into(), + }, NewDappsPolicy::Whitelist(accounts) => JsonNewDappsPolicy::Whitelist( accounts.into_iter().map(Into::into).collect() ), @@ -230,7 +240,9 @@ impl DappsSettingsStore { /// Returns current new dapps policy pub fn policy(&self) -> NewDappsPolicy { - self.policy.get("default").cloned().unwrap_or(NewDappsPolicy::AllAccounts) + self.policy.get("default").cloned().unwrap_or(NewDappsPolicy::AllAccounts { + default: 0.into(), + }) } /// Returns recent dapps with last accessed timestamp @@ -266,13 +278,22 @@ impl DappsSettingsStore { } /// Sets accounts for specific dapp. - pub fn set_accounts(&mut self, id: DappId, accounts: Vec
) { + pub fn set_accounts(&mut self, id: DappId, accounts: Option>) { { let mut settings = self.settings.entry(id).or_insert_with(DappsSettings::default); settings.accounts = accounts; } self.settings.save(JsonSettings::write); } + + /// Sets a default account for specific dapp. + pub fn set_default(&mut self, id: DappId, default: Address) { + { + let mut settings = self.settings.entry(id).or_insert_with(DappsSettings::default); + settings.default = Some(default); + } + self.settings.save(JsonSettings::write); + } } /// Disk-serializable HashMap @@ -385,13 +406,14 @@ mod tests { let mut b = DappsSettingsStore::new(&path); // when - b.set_accounts("dappOne".into(), vec![1.into(), 2.into()]); + b.set_accounts("dappOne".into(), Some(vec![1.into(), 2.into()])); // then let b = DappsSettingsStore::new(&path); assert_eq!(b.settings(), hash_map![ "dappOne".into() => DappsSettings { - accounts: vec![1.into(), 2.into()], + accounts: Some(vec![1.into(), 2.into()]), + default: None, } ]); } @@ -422,7 +444,9 @@ mod tests { let mut store = DappsSettingsStore::new(&path); // Test default policy - assert_eq!(store.policy(), NewDappsPolicy::AllAccounts); + assert_eq!(store.policy(), NewDappsPolicy::AllAccounts { + default: 0.into(), + }); // when store.set_policy(NewDappsPolicy::Whitelist(vec![1.into(), 2.into()])); diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index d7278d13ab..0f1bd492c0 100644 --- a/js/src/api/rpc/parity/parity.js +++ b/js/src/api/rpc/parity/parity.js @@ -170,18 +170,30 @@ export default class Parity { .execute('parity_generateSecretPhrase'); } - getDappsAddresses (dappId) { + getDappAddresses (dappId) { return this._transport - .execute('parity_getDappsAddresses', dappId) + .execute('parity_getDappAddresses', dappId) .then(outAddresses); } - getNewDappsWhitelist () { + getDappDefaultAddress (dappId) { return this._transport - .execute('parity_getNewDappsWhitelist') + .execute('parity_getDappDefaultAddress', dappId) + .then(outAddress); + } + + getNewDappsAddresses () { + return this._transport + .execute('parity_getNewDappsAddresses') .then((addresses) => addresses ? addresses.map(outAddress) : null); } + getNewDappsDefaultAddress () { + return this._transport + .execute('parity_getNewDappsDefaultAddress') + .then(outAddress); + } + getVaultMeta (vaultName) { return this._transport .execute('parity_getVaultMeta', vaultName) @@ -391,9 +403,14 @@ export default class Parity { .execute('parity_setAuthor', inAddress(address)); } - setDappsAddresses (dappId, addresses) { + setDappAddresses (dappId, addresses) { + return this._transport + .execute('parity_setDappAddresses', dappId, inAddresses(addresses)); + } + + setDappDefaultAddress (dappId, address) { return this._transport - .execute('parity_setDappsAddresses', dappId, inAddresses(addresses)); + .execute('parity_setDappDefaultAddress', dappId, address ? inAddress(address) : null); } setEngineSigner (address, password) { @@ -431,9 +448,14 @@ export default class Parity { .execute('parity_setMode', mode); } - setNewDappsWhitelist (addresses) { + setNewDappsAddresses (addresses) { + return this._transport + .execute('parity_setNewDappsAddresses', addresses ? inAddresses(addresses) : null); + } + + setNewDappsDefaultAddress (address) { return this._transport - .execute('parity_setNewDappsWhitelist', addresses ? inAddresses(addresses) : null); + .execute('parity_setNewDappsDefaultAddress', inAddress(address)); } setTransactionsLimit (quantity) { diff --git a/js/src/api/subscriptions/personal.js b/js/src/api/subscriptions/personal.js index 715f4cfe57..c1f070262c 100644 --- a/js/src/api/subscriptions/personal.js +++ b/js/src/api/subscriptions/personal.js @@ -123,8 +123,10 @@ export default class Personal { this._accountsInfo(); return; - case 'parity_setDappsAddresses': - case 'parity_setNewDappsWhitelist': + case 'parity_setDappAddresses': + case 'parity_setDappDefaultAddress': + case 'parity_setNewDappsAddresses': + case 'parity_setNewDappsDefaultAddress': this._defaultAccount(true); this._listAccounts(); return; diff --git a/js/src/jsonrpc/interfaces/parity.js b/js/src/jsonrpc/interfaces/parity.js index 978c77f88f..3d84ce4b45 100644 --- a/js/src/jsonrpc/interfaces/parity.js +++ b/js/src/jsonrpc/interfaces/parity.js @@ -1186,9 +1186,9 @@ export default { } }, - setDappsAddresses: { + setDappAddresses: { subdoc: SUBDOC_ACCOUNTS, - desc: 'Sets the available addresses for a dapp.', + desc: 'Sets the available addresses for a dapp. When provided with non-empty list changes the default account as well.', params: [ { type: String, @@ -1197,7 +1197,7 @@ export default { }, { type: Array, - desc: 'Array of available accounts available to the dapp.', + desc: 'Array of available accounts available to the dapp or `null` for default list.', example: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1'] } ], @@ -1208,7 +1208,7 @@ export default { } }, - getDappsAddresses: { + getDappAddresses: { subdoc: SUBDOC_ACCOUNTS, desc: 'Returns the list of accounts available to a specific dapp.', params: [ @@ -1225,13 +1225,52 @@ export default { } }, - setNewDappsWhitelist: { + setDappDefaultAddress: { + subdoc: SUBDOC_ACCOUNTS, + desc: 'Changes dapp default address. Does not affect other accounts exposed for this dapp, but default account will always be retured as the first one.', + params: [ + { + type: String, + desc: 'Dapp Id.', + example: 'web' + }, + { + type: Address, + desc: 'Default Address.', + example: '0x407d73d8a49eeb85d32cf465507dd71d507100c1' + } + ], + returns: { + type: Boolean, + desc: '`true` if the call was successful', + example: true + } + }, + + getDappDefaultAddress: { + subdoc: SUBDOC_ACCOUNTS, + desc: 'Returns a default account available to a specific dapp.', + params: [ + { + type: String, + desc: 'Dapp Id.', + example: 'web' + } + ], + returns: { + type: Address, + desc: 'Default Address', + example: '0x407d73d8a49eeb85d32cf465507dd71d507100c1' + } + }, + + setNewDappsAddresses: { subdoc: SUBDOC_ACCOUNTS, desc: 'Sets the list of accounts available to new dapps.', params: [ { type: Array, - desc: 'List of accounts available by default.', + desc: 'List of accounts available by default or `null` for all accounts.', example: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1'] } ], @@ -1242,7 +1281,7 @@ export default { } }, - getNewDappsWhitelist: { + getNewDappsAddresses: { subdoc: SUBDOC_ACCOUNTS, desc: 'Returns the list of accounts available to a new dapps.', params: [], @@ -1253,6 +1292,34 @@ export default { } }, + setNewDappsDefaultAddress: { + subdoc: SUBDOC_ACCOUNTS, + desc: 'Changes global default address. This setting may be overriden for a specific dapp.', + params: [ + { + type: Address, + desc: 'Default Address.', + example: '0x407d73d8a49eeb85d32cf465507dd71d507100c1' + } + ], + returns: { + type: Boolean, + desc: '`true` if the call was successful', + example: true + } + }, + + getNewDappsDefaultAddress: { + subdoc: SUBDOC_ACCOUNTS, + desc: 'Returns a default account available to dapps.', + params: [], + returns: { + type: Address, + desc: 'Default Address', + example: '0x407d73d8a49eeb85d32cf465507dd71d507100c1' + } + }, + listRecentDapps: { subdoc: SUBDOC_ACCOUNTS, desc: 'Returns a list of the most recent active dapps.', diff --git a/js/src/modals/DappPermissions/store.js b/js/src/modals/DappPermissions/store.js index 67f3de2e7e..3239343c55 100644 --- a/js/src/modals/DappPermissions/store.js +++ b/js/src/modals/DappPermissions/store.js @@ -102,7 +102,7 @@ export default class Store { loadWhitelist () { return this._api.parity - .getNewDappsWhitelist() + .getNewDappsAddresses() .then((whitelist) => { this.setWhitelist(whitelist); }) @@ -113,7 +113,7 @@ export default class Store { updateWhitelist (whitelist) { return this._api.parity - .setNewDappsWhitelist(whitelist) + .setNewDappsAddresses(whitelist) .then(() => { this.setWhitelist(whitelist); }) diff --git a/js/src/modals/DappPermissions/store.spec.js b/js/src/modals/DappPermissions/store.spec.js index 1266bd0572..20d9274844 100644 --- a/js/src/modals/DappPermissions/store.spec.js +++ b/js/src/modals/DappPermissions/store.spec.js @@ -31,8 +31,8 @@ let store; function create () { api = { parity: { - getNewDappsWhitelist: sinon.stub().resolves(WHITELIST), - setNewDappsWhitelist: sinon.stub().resolves(true) + getNewDappsAddresses: sinon.stub().resolves(WHITELIST), + setNewDappsAddresses: sinon.stub().resolves(true) } }; @@ -46,7 +46,7 @@ describe('modals/DappPermissions/store', () => { describe('constructor', () => { it('retrieves the whitelist via api', () => { - expect(api.parity.getNewDappsWhitelist).to.be.calledOnce; + expect(api.parity.getNewDappsAddresses).to.be.calledOnce; }); it('sets the retrieved whitelist', () => { @@ -79,12 +79,12 @@ describe('modals/DappPermissions/store', () => { store.closeModal(); }); - it('calls setNewDappsWhitelist', () => { - expect(api.parity.setNewDappsWhitelist).to.have.been.calledOnce; + it('calls setNewDappsAddresses', () => { + expect(api.parity.setNewDappsAddresses).to.have.been.calledOnce; }); it('has the default account in first position', () => { - expect(api.parity.setNewDappsWhitelist).to.have.been.calledWith(['789', '456']); + expect(api.parity.setNewDappsAddresses).to.have.been.calledWith(['789', '456']); }); }); diff --git a/js/src/views/ParityBar/accountStore.js b/js/src/views/ParityBar/accountStore.js index bf854e1946..fef00a1427 100644 --- a/js/src/views/ParityBar/accountStore.js +++ b/js/src/views/ParityBar/accountStore.js @@ -61,7 +61,7 @@ export default class AccountStore { this.setDefaultAccount(address); return this._api.parity - .setNewDappsWhitelist(accounts) + .setNewDappsAddresses(accounts) .catch((error) => { console.warn('makeDefaultAccount', error); }); @@ -78,7 +78,7 @@ export default class AccountStore { return Promise .all([ - this._api.parity.getNewDappsWhitelist(), + this._api.parity.getNewDappsAddresses(), this._api.parity.allAccountsInfo() ]) .then(([whitelist, accounts]) => { diff --git a/js/src/views/ParityBar/accountStore.spec.js b/js/src/views/ParityBar/accountStore.spec.js index c13c62aa91..8b2a9a41a7 100644 --- a/js/src/views/ParityBar/accountStore.spec.js +++ b/js/src/views/ParityBar/accountStore.spec.js @@ -76,8 +76,8 @@ describe('views/ParityBar/AccountStore', () => { store.setAccounts.restore(); }); - it('calls into parity_getNewDappsWhitelist', () => { - expect(api.parity.getNewDappsWhitelist).to.have.been.called; + it('calls into parity_getNewDappsAddresses', () => { + expect(api.parity.getNewDappsAddresses).to.have.been.called; }); it('calls into parity_allAccountsInfo', () => { @@ -104,8 +104,8 @@ describe('views/ParityBar/AccountStore', () => { return store.makeDefaultAccount(ACCOUNT_NEW); }); - it('calls into parity_setNewDappsWhitelist (with ordering)', () => { - expect(api.parity.setNewDappsWhitelist).to.have.been.calledWith([ + it('calls into parity_setNewDappsAddresses (with ordering)', () => { + expect(api.parity.setNewDappsAddresses).to.have.been.calledWith([ ACCOUNT_NEW, ACCOUNT_FIRST, ACCOUNT_DEFAULT ]); }); diff --git a/js/src/views/ParityBar/parityBar.test.js b/js/src/views/ParityBar/parityBar.test.js index 2623e40748..97c6e6251c 100644 --- a/js/src/views/ParityBar/parityBar.test.js +++ b/js/src/views/ParityBar/parityBar.test.js @@ -36,8 +36,8 @@ function createApi () { parity: { defaultAccount: sinon.stub().resolves(ACCOUNT_DEFAULT), allAccountsInfo: sinon.stub().resolves(ACCOUNTS), - getNewDappsWhitelist: sinon.stub().resolves(null), - setNewDappsWhitelist: sinon.stub().resolves(true) + getNewDappsAddresses: sinon.stub().resolves(null), + setNewDappsAddresses: sinon.stub().resolves(true) } }; diff --git a/json/src/misc/dapps_settings.rs b/json/src/misc/dapps_settings.rs index 1e5565bafc..5081c62b28 100644 --- a/json/src/misc/dapps_settings.rs +++ b/json/src/misc/dapps_settings.rs @@ -22,7 +22,9 @@ use hash; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct DappsSettings { /// A list of accounts this Dapp can see. - pub accounts: Vec, + pub accounts: Option>, + /// Default account + pub default: Option, } impl_serialization!(String => DappsSettings); @@ -40,7 +42,10 @@ impl_serialization!(String => DappsHistory); #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum NewDappsPolicy { /// All accounts are exposed by default. - AllAccounts, + AllAccounts { + /// Default account, which should be returned as the first one. + default: hash::Address, + }, /// Only accounts listed here are exposed by default for new dapps. Whitelist(Vec), } diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 9ad8196fee..01627ba28b 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -221,7 +221,7 @@ impl EthClient where let store = take_weak!(self.accounts); store .note_dapp_used(dapp.clone()) - .and_then(|_| store.dapps_addresses(dapp)) + .and_then(|_| store.dapp_addresses(dapp)) .map_err(|e| errors::internal("Could not fetch accounts.", e)) } } @@ -308,10 +308,7 @@ impl Eth for EthClient where let author = move || { let mut miner = take_weak!(self.miner).author(); if miner == 0.into() { - let accounts = self.dapp_accounts(dapp.into())?; - if let Some(address) = accounts.get(0) { - miner = *address; - } + miner = self.dapp_accounts(dapp.into())?.get(0).cloned().unwrap_or_default(); } Ok(RpcH160::from(miner)) diff --git a/rpc/src/v1/impls/light/eth.rs b/rpc/src/v1/impls/light/eth.rs index 944b419f7f..2e129d31ee 100644 --- a/rpc/src/v1/impls/light/eth.rs +++ b/rpc/src/v1/impls/light/eth.rs @@ -187,7 +187,7 @@ impl Eth for EthClient { let accounts = self.accounts .note_dapp_used(dapp.clone()) - .and_then(|_| self.accounts.dapps_addresses(dapp)) + .and_then(|_| self.accounts.dapp_addresses(dapp)) .map_err(|e| errors::internal("Could not fetch accounts.", e)) .map(|accs| accs.into_iter().map(Into::::into).collect()); diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index 431c34e84f..3fcc82c3a1 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::{future, Future, BoxFuture}; use util::{RotatingLogger, Address}; use util::misc::version_data; @@ -118,7 +118,7 @@ impl Parity for ParityClient where let store = take_weak!(self.accounts); let dapp_accounts = store .note_dapp_used(dapp.clone().into()) - .and_then(|_| store.dapps_addresses(dapp.into())) + .and_then(|_| store.dapp_addresses(dapp.into())) .map_err(|e| errors::internal("Could not fetch accounts.", e))? .into_iter().collect::>(); @@ -146,16 +146,13 @@ impl Parity for ParityClient where fn default_account(&self, meta: Self::Metadata) -> BoxFuture { let dapp_id = meta.dapp_id(); - let default_account = move || { - Ok(take_weak!(self.accounts) - .dapps_addresses(dapp_id.into()) + future::ok( + take_weakf!(self.accounts) + .dapp_default_address(dapp_id.into()) + .map(Into::into) .ok() - .and_then(|accounts| accounts.get(0).cloned()) - .map(|acc| acc.into()) - .unwrap_or_default()) - }; - - futures::done(default_account()).boxed() + .unwrap_or_default() + ).boxed() } fn transactions_limit(&self) -> Result { diff --git a/rpc/src/v1/impls/parity_accounts.rs b/rpc/src/v1/impls/parity_accounts.rs index e28ea25101..60b6158978 100644 --- a/rpc/src/v1/impls/parity_accounts.rs +++ b/rpc/src/v1/impls/parity_accounts.rs @@ -142,43 +142,71 @@ impl ParityAccounts for ParityAccountsClient { Ok(true) } - fn set_account_visibility(&self, _address: RpcH160, _dapp: RpcH256, _visible: bool) -> Result { - Ok(false) + fn set_dapp_addresses(&self, dapp: DappId, addresses: Option>) -> Result { + let store = take_weak!(self.accounts); + + store.set_dapp_addresses(dapp.into(), addresses.map(into_vec)) + .map_err(|e| errors::account("Couldn't set dapp addresses.", e)) + .map(|_| true) } - fn set_dapps_addresses(&self, dapp: DappId, addresses: Vec) -> Result { + fn dapp_addresses(&self, dapp: DappId) -> Result, Error> { let store = take_weak!(self.accounts); - store.set_dapps_addresses(dapp.into(), into_vec(addresses)) - .map_err(|e| errors::account("Couldn't set dapps addresses.", e)) + store.dapp_addresses(dapp.into()) + .map_err(|e| errors::account("Couldn't get dapp addresses.", e)) + .map(into_vec) + } + + fn set_dapp_default_address(&self, dapp: DappId, address: RpcH160) -> Result { + let store = take_weak!(self.accounts); + + store.set_dapp_default_address(dapp.into(), address.into()) + .map_err(|e| errors::account("Couldn't set dapp default address.", e)) .map(|_| true) } - fn dapps_addresses(&self, dapp: DappId) -> Result, Error> { + fn dapp_default_address(&self, dapp: DappId) -> Result { let store = take_weak!(self.accounts); - store.dapps_addresses(dapp.into()) - .map_err(|e| errors::account("Couldn't get dapps addresses.", e)) - .map(into_vec) + store.dapp_default_address(dapp.into()) + .map_err(|e| errors::account("Couldn't get dapp default address.", e)) + .map(Into::into) } - fn set_new_dapps_whitelist(&self, whitelist: Option>) -> Result { + fn set_new_dapps_addresses(&self, addresses: Option>) -> Result { let store = take_weak!(self.accounts); store - .set_new_dapps_whitelist(whitelist.map(into_vec)) - .map_err(|e| errors::account("Couldn't set dapps whitelist.", e)) + .set_new_dapps_addresses(addresses.map(into_vec)) + .map_err(|e| errors::account("Couldn't set dapps addresses.", e)) .map(|_| true) } - fn new_dapps_whitelist(&self) -> Result>, Error> { + fn new_dapps_addresses(&self) -> Result>, Error> { let store = take_weak!(self.accounts); - store.new_dapps_whitelist() - .map_err(|e| errors::account("Couldn't get dapps whitelist.", e)) + store.new_dapps_addresses() + .map_err(|e| errors::account("Couldn't get dapps addresses.", e)) .map(|accounts| accounts.map(into_vec)) } + fn set_new_dapps_default_address(&self, address: RpcH160) -> Result { + let store = take_weak!(self.accounts); + + store.set_new_dapps_default_address(address.into()) + .map_err(|e| errors::account("Couldn't set new dapps default address.", e)) + .map(|_| true) + } + + fn new_dapps_default_address(&self) -> Result { + let store = take_weak!(self.accounts); + + store.new_dapps_default_address() + .map_err(|e| errors::account("Couldn't get new dapps default address.", e)) + .map(Into::into) + } + fn recent_dapps(&self) -> Result, Error> { let store = take_weak!(self.accounts); diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 966dc9a743..1f9cad92fb 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -101,7 +101,7 @@ impl Personal for PersonalClient { let default = match request.from.as_ref() { Some(account) => Ok(account.clone().into()), None => accounts - .default_address(meta.dapp_id().into()) + .dapp_default_address(meta.dapp_id().into()) .map_err(|e| errors::account("Cannot find default account.", e)), }; diff --git a/rpc/src/v1/impls/signing.rs b/rpc/src/v1/impls/signing.rs index 5c52df79ae..d737131a65 100644 --- a/rpc/src/v1/impls/signing.rs +++ b/rpc/src/v1/impls/signing.rs @@ -86,7 +86,7 @@ impl SigningQueueClient { let accounts = take_weakf!(self.accounts); let default_account = match default_account { DefaultAccount::Provided(acc) => acc, - DefaultAccount::ForDapp(dapp) => accounts.default_address(dapp).ok().unwrap_or_default(), + DefaultAccount::ForDapp(dapp) => accounts.dapp_default_address(dapp).ok().unwrap_or_default(), }; let dispatcher = self.dispatcher.clone(); diff --git a/rpc/src/v1/impls/signing_unsafe.rs b/rpc/src/v1/impls/signing_unsafe.rs index 1d778404c5..d48935e441 100644 --- a/rpc/src/v1/impls/signing_unsafe.rs +++ b/rpc/src/v1/impls/signing_unsafe.rs @@ -55,7 +55,7 @@ impl SigningUnsafeClient { let accounts = take_weakf!(self.accounts); let default = match account { DefaultAccount::Provided(acc) => acc, - DefaultAccount::ForDapp(dapp) => accounts.default_address(dapp).ok().unwrap_or_default(), + DefaultAccount::ForDapp(dapp) => accounts.dapp_default_address(dapp).ok().unwrap_or_default(), }; let dis = self.dispatcher.clone(); diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index 40ae8c38cd..2432b55e70 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -368,7 +368,7 @@ fn rpc_eth_gas_price() { fn rpc_eth_accounts() { let tester = EthTester::default(); let address = tester.accounts_provider.new_account("").unwrap(); - tester.accounts_provider.set_new_dapps_whitelist(None).unwrap(); + tester.accounts_provider.set_new_dapps_addresses(None).unwrap(); tester.accounts_provider.set_address_name(1.into(), "1".into()); tester.accounts_provider.set_address_name(10.into(), "10".into()); @@ -377,14 +377,14 @@ fn rpc_eth_accounts() { let response = r#"{"jsonrpc":"2.0","result":[""#.to_owned() + &format!("0x{:?}", address) + r#""],"id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); - tester.accounts_provider.set_new_dapps_whitelist(Some(vec![1.into()])).unwrap(); + tester.accounts_provider.set_new_dapps_addresses(Some(vec![1.into()])).unwrap(); // even with some account it should return empty list (no dapp detected) let request = r#"{"jsonrpc": "2.0", "method": "eth_accounts", "params": [], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":["0x0000000000000000000000000000000000000001"],"id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); // when we add visible address it should return that. - tester.accounts_provider.set_dapps_addresses("app1".into(), vec![10.into()]).unwrap(); + tester.accounts_provider.set_dapp_addresses("app1".into(), Some(vec![10.into()])).unwrap(); let request = r#"{"jsonrpc": "2.0", "method": "eth_accounts", "params": [], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":["0x000000000000000000000000000000000000000a"],"id":1}"#; let mut meta = Metadata::default(); diff --git a/rpc/src/v1/tests/mocked/parity.rs b/rpc/src/v1/tests/mocked/parity.rs index 1bf5577172..a587554a39 100644 --- a/rpc/src/v1/tests/mocked/parity.rs +++ b/rpc/src/v1/tests/mocked/parity.rs @@ -110,17 +110,19 @@ fn rpc_parity_accounts_info() { assert_eq!(accounts.len(), 1); let address = accounts[0]; - deps.accounts.set_account_name(address.clone(), "Test".to_owned()).unwrap(); - deps.accounts.set_account_meta(address.clone(), "{foo: 69}".to_owned()).unwrap(); + deps.accounts.set_address_name(1.into(), "XX".into()); + deps.accounts.set_account_name(address.clone(), "Test".into()).unwrap(); + deps.accounts.set_account_meta(address.clone(), "{foo: 69}".into()).unwrap(); let request = r#"{"jsonrpc": "2.0", "method": "parity_accountsInfo", "params": [], "id": 1}"#; let response = format!("{{\"jsonrpc\":\"2.0\",\"result\":{{\"0x{}\":{{\"name\":\"Test\"}}}},\"id\":1}}", address.hex()); assert_eq!(io.handle_request_sync(request), Some(response)); // Change the whitelist - deps.accounts.set_new_dapps_whitelist(Some(vec![1.into()])).unwrap(); + let address = Address::from(1); + deps.accounts.set_new_dapps_addresses(Some(vec![address.clone()])).unwrap(); let request = r#"{"jsonrpc": "2.0", "method": "parity_accountsInfo", "params": [], "id": 1}"#; - let response = format!("{{\"jsonrpc\":\"2.0\",\"result\":{{}},\"id\":1}}"); + let response = format!("{{\"jsonrpc\":\"2.0\",\"result\":{{\"0x{}\":{{\"name\":\"XX\"}}}},\"id\":1}}", address.hex()); assert_eq!(io.handle_request_sync(request), Some(response)); } diff --git a/rpc/src/v1/tests/mocked/parity_accounts.rs b/rpc/src/v1/tests/mocked/parity_accounts.rs index 6c518945f9..304ffd45e0 100644 --- a/rpc/src/v1/tests/mocked/parity_accounts.rs +++ b/rpc/src/v1/tests/mocked/parity_accounts.rs @@ -125,48 +125,87 @@ fn rpc_parity_set_and_get_dapps_accounts() { // given let tester = setup(); tester.accounts.set_address_name(10.into(), "10".into()); - assert_eq!(tester.accounts.dapps_addresses("app1".into()).unwrap(), vec![]); + assert_eq!(tester.accounts.dapp_addresses("app1".into()).unwrap(), vec![]); // when - let request = r#"{"jsonrpc": "2.0", "method": "parity_setDappsAddresses","params":["app1",["0x000000000000000000000000000000000000000a","0x0000000000000000000000000000000000000001"]], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_setDappAddresses","params":["app1",["0x000000000000000000000000000000000000000a","0x0000000000000000000000000000000000000001"]], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); // then - assert_eq!(tester.accounts.dapps_addresses("app1".into()).unwrap(), vec![10.into()]); - let request = r#"{"jsonrpc": "2.0", "method": "parity_getDappsAddresses","params":["app1"], "id": 1}"#; + assert_eq!(tester.accounts.dapp_addresses("app1".into()).unwrap(), vec![10.into()]); + let request = r#"{"jsonrpc": "2.0", "method": "parity_getDappAddresses","params":["app1"], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":["0x000000000000000000000000000000000000000a"],"id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); } +#[test] +fn rpc_parity_set_and_get_dapp_default_address() { + // given + let tester = setup(); + tester.accounts.set_address_name(10.into(), "10".into()); + assert_eq!(tester.accounts.dapp_addresses("app1".into()).unwrap(), vec![]); + + // when + let request = r#"{"jsonrpc": "2.0", "method": "parity_setDappDefaultAddress","params":["app1", "0x000000000000000000000000000000000000000a"], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; + assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); + + // then + assert_eq!(tester.accounts.dapp_addresses("app1".into()).unwrap(), vec![10.into()]); + let request = r#"{"jsonrpc": "2.0", "method": "parity_getDappDefaultAddress","params":["app1"], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":"0x000000000000000000000000000000000000000a","id":1}"#; + assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); +} + #[test] fn rpc_parity_set_and_get_new_dapps_whitelist() { // given let tester = setup(); // when set to whitelist - let request = r#"{"jsonrpc": "2.0", "method": "parity_setNewDappsWhitelist","params":[["0x000000000000000000000000000000000000000a"]], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_setNewDappsAddresses","params":[["0x000000000000000000000000000000000000000a"]], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); // then - assert_eq!(tester.accounts.new_dapps_whitelist().unwrap(), Some(vec![10.into()])); - let request = r#"{"jsonrpc": "2.0", "method": "parity_getNewDappsWhitelist","params":[], "id": 1}"#; + assert_eq!(tester.accounts.new_dapps_addresses().unwrap(), Some(vec![10.into()])); + let request = r#"{"jsonrpc": "2.0", "method": "parity_getNewDappsAddresses","params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":["0x000000000000000000000000000000000000000a"],"id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); // when set to empty - let request = r#"{"jsonrpc": "2.0", "method": "parity_setNewDappsWhitelist","params":[null], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_setNewDappsAddresses","params":[null], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); // then - assert_eq!(tester.accounts.new_dapps_whitelist().unwrap(), None); - let request = r#"{"jsonrpc": "2.0", "method": "parity_getNewDappsWhitelist","params":[], "id": 1}"#; + assert_eq!(tester.accounts.new_dapps_addresses().unwrap(), None); + let request = r#"{"jsonrpc": "2.0", "method": "parity_getNewDappsAddresses","params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":null,"id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); } +#[test] +fn rpc_parity_set_and_get_new_dapps_default_address() { + // given + let tester = setup(); + tester.accounts.set_address_name(10.into(), "10".into()); + assert_eq!(tester.accounts.new_dapps_default_address().unwrap(), 0.into()); + + // when + let request = r#"{"jsonrpc": "2.0", "method": "parity_setNewDappsDefaultAddress","params":["0x000000000000000000000000000000000000000a"], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; + assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); + + // then + assert_eq!(tester.accounts.new_dapps_default_address().unwrap(), 10.into()); + let request = r#"{"jsonrpc": "2.0", "method": "parity_getNewDappsDefaultAddress","params":[], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":"0x000000000000000000000000000000000000000a","id":1}"#; + assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); +} + + #[test] fn rpc_parity_recent_dapps() { // given diff --git a/rpc/src/v1/traits/parity_accounts.rs b/rpc/src/v1/traits/parity_accounts.rs index 5767860738..a3a9a8d9f7 100644 --- a/rpc/src/v1/traits/parity_accounts.rs +++ b/rpc/src/v1/traits/parity_accounts.rs @@ -70,28 +70,51 @@ build_rpc_trait! { #[rpc(name = "parity_setAccountMeta")] fn set_account_meta(&self, H160, String) -> Result; - /// Sets account visibility. - /// @unimplemented - #[rpc(name = "parity_setAccountVisiblity")] - fn set_account_visibility(&self, H160, H256, bool) -> Result; - - /// Sets accounts exposed for particular dapp. - #[rpc(name = "parity_setDappsAddresses")] - fn set_dapps_addresses(&self, DappId, Vec) -> Result; + /// Sets addresses exposed for particular dapp. + /// Setting a non-empty list will also override default account. + /// Setting `None` will resets visible account to what's visible for new dapps + /// (does not affect default account though) + #[rpc(name = "parity_setDappAddresses")] + fn set_dapp_addresses(&self, DappId, Option>) -> Result; /// Gets accounts exposed for particular dapp. - #[rpc(name = "parity_getDappsAddresses")] - fn dapps_addresses(&self, DappId) -> Result, Error>; + #[rpc(name = "parity_getDappAddresses")] + fn dapp_addresses(&self, DappId) -> Result, Error>; + + /// Changes dapp default address. + /// Does not affect other accounts exposed for this dapp, but + /// default account will always be retured as the first one. + #[rpc(name = "parity_setDappDefaultAddress")] + fn set_dapp_default_address(&self, DappId, H160) -> Result; + + /// Returns current dapp default address. + /// If not set explicite for the dapp will return global default. + #[rpc(name = "parity_getDappDefaultAddress")] + fn dapp_default_address(&self, DappId) -> Result; /// Sets accounts exposed for new dapps. - /// `None` means that all accounts will be exposed. - #[rpc(name = "parity_setNewDappsWhitelist")] - fn set_new_dapps_whitelist(&self, Option>) -> Result; + /// Setting a non-empty list will also override default account. + /// Setting `None` exposes all internal-managed accounts. + /// (does not affect default account though) + #[rpc(name = "parity_setNewDappsAddresses")] + fn set_new_dapps_addresses(&self, Option>) -> Result; /// Gets accounts exposed for new dapps. - /// `None` means that all accounts will be exposed. - #[rpc(name = "parity_getNewDappsWhitelist")] - fn new_dapps_whitelist(&self) -> Result>, Error>; + /// `None` means that all accounts are exposes. + #[rpc(name = "parity_getNewDappsAddresses")] + fn new_dapps_addresses(&self) -> Result>, Error>; + + /// Changes default address for new dapps (global default address) + /// Does not affect other accounts exposed for new dapps, but + /// default account will always be retured as the first one. + #[rpc(name = "parity_setNewDappsDefaultAddress")] + fn set_new_dapps_default_address(&self, H160) -> Result; + + /// Returns current default address for new dapps (global default address) + /// In case it's not set explicite will return first available account. + /// If no accounts are available will return `0x0` + #[rpc(name = "parity_getNewDappsDefaultAddress")] + fn new_dapps_default_address(&self) -> Result; /// Returns identified dapps that recently used RPC /// Includes last usage timestamp. -- GitLab From 348559491af0e3ab2efacc50de119a3e526f4efb Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Mon, 20 Feb 2017 16:34:16 +0100 Subject: [PATCH 099/246] Bump CID version to allow compilation on all platforms --- ipfs/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipfs/Cargo.toml b/ipfs/Cargo.toml index d7698ac742..7cc381ee87 100644 --- a/ipfs/Cargo.toml +++ b/ipfs/Cargo.toml @@ -11,5 +11,5 @@ ethcore-util = { path = "../util" } rlp = { path = "../util/rlp" } mime = "0.2" hyper = { default-features = false, git = "https://github.com/ethcore/hyper" } -cid = "0.2" +cid = "0.2.1" multihash = "0.5" -- GitLab From 44769fcd4a065f0a9debb517a6964f6e72745af9 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Mon, 20 Feb 2017 16:34:38 +0100 Subject: [PATCH 100/246] Show only known accounts/wallets/addresses on Home (#4612) * Don't render unknown entries * Only render non-null items * Remove (now) invalid failing test --- js/src/ui/SectionList/sectionList.js | 16 ++++++++++------ js/src/ui/SectionList/sectionList.spec.js | 4 ---- js/src/views/Home/Accounts/accounts.js | 7 ++++++- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/js/src/ui/SectionList/sectionList.js b/js/src/ui/SectionList/sectionList.js index b326b9ed83..f499d8962e 100644 --- a/js/src/ui/SectionList/sectionList.js +++ b/js/src/ui/SectionList/sectionList.js @@ -46,10 +46,18 @@ export default class SectionList extends Component { return null; } + const rendered = items + .map(this.renderItem) + .filter((item) => item); + + if (!rendered.length) { + return null; + } + return (
{ this.renderOverlay() } - { chunkArray(items, ITEMS_PER_ROW).map(this.renderRow) } + { chunkArray(rendered, ITEMS_PER_ROW).map(this.renderRow) }
); } @@ -74,11 +82,7 @@ export default class SectionList extends Component { className={ styles.row } key={ `row_${index}` } > - { - row - .map(this.renderItem) - .filter((item) => item) - } + { row }
); } diff --git a/js/src/ui/SectionList/sectionList.spec.js b/js/src/ui/SectionList/sectionList.spec.js index 9022db79dd..29c6d1d562 100644 --- a/js/src/ui/SectionList/sectionList.spec.js +++ b/js/src/ui/SectionList/sectionList.spec.js @@ -74,10 +74,6 @@ describe('SectionList', () => { it('adds a key for the row', () => { expect(row.key).to.be.ok; }); - - it('calls renderItem for the items', () => { - expect(instance.renderItem).to.have.been.calledTwice; - }); }); describe('renderItem', () => { diff --git a/js/src/views/Home/Accounts/accounts.js b/js/src/views/Home/Accounts/accounts.js index 73ffce43b7..f397994ff3 100644 --- a/js/src/views/Home/Accounts/accounts.js +++ b/js/src/views/Home/Accounts/accounts.js @@ -80,7 +80,12 @@ class Accounts extends Component { return null; } - const account = accountsInfo[history.entry] || { meta: {} }; + const account = accountsInfo[history.entry]; + + if (!account) { + return null; + } + let linkType = 'addresses'; if (account.uuid) { -- GitLab From ac6180a6feebb0be31c06ca77d5594698743bd04 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 20 Feb 2017 15:35:53 +0000 Subject: [PATCH 101/246] seals_internally (#4613) --- ethcore/src/engines/authority_round.rs | 4 +-- ethcore/src/engines/basic_authority.rs | 9 ++++--- ethcore/src/engines/instant_seal.rs | 2 +- ethcore/src/engines/mod.rs | 9 +++---- ethcore/src/engines/tendermint/mod.rs | 4 +-- ethcore/src/miner/miner.rs | 37 +++++++++++++------------- 6 files changed, 33 insertions(+), 32 deletions(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 9c69ce6adb..4f99a644e2 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -220,8 +220,8 @@ impl Engine for AuthorityRound { }); } - fn is_sealer(&self, author: &Address) -> Option { - Some(self.validators.contains(author)) + fn seals_internally(&self) -> Option { + Some(self.validators.contains(&self.signer.address())) } /// Attempt to seal the block internally. diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 5d1abd064f..50051bf7e2 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -103,8 +103,8 @@ impl Engine for BasicAuthority { }); } - fn is_sealer(&self, author: &Address) -> Option { - Some(self.validators.contains(author)) + fn seals_internally(&self) -> Option { + Some(self.validators.contains(&self.signer.address())) } /// Attempt to seal the block internally. @@ -268,7 +268,8 @@ mod tests { let authority = tap.insert_account(Secret::from_slice(&"".sha3()).unwrap(), "").unwrap(); let engine = new_test_authority().engine; - assert!(!engine.is_sealer(&Address::default()).unwrap()); - assert!(engine.is_sealer(&authority).unwrap()); + assert!(!engine.seals_internally().unwrap()); + engine.set_signer(Arc::new(tap), authority, "".into()); + assert!(engine.seals_internally().unwrap()); } } diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index c709398e37..d2deea7026 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -56,7 +56,7 @@ impl Engine for InstantSeal { Schedule::new_post_eip150(usize::max_value(), true, true, true) } - fn is_sealer(&self, _author: &Address) -> Option { Some(true) } + fn seals_internally(&self) -> Option { Some(true) } fn generate_seal(&self, _block: &ExecutedBlock) -> Seal { Seal::Regular(Vec::new()) diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index a22410ceed..2320f49a07 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -128,11 +128,10 @@ pub trait Engine : Sync + Send { /// Block transformation functions, after the transactions. fn on_close_block(&self, _block: &mut ExecutedBlock) {} - /// If Some(true) this author is able to generate seals, generate_seal has to be implemented. - /// None indicates that this Engine never seals internally regardless of author (e.g. PoW). - fn is_sealer(&self, _author: &Address) -> Option { None } - /// Checks if default address is able to seal. - fn is_default_sealer(&self) -> Option { self.is_sealer(&Default::default()) } + /// None means that it requires external input (e.g. PoW) to seal a block. + /// Some(true) means the engine is currently prime for seal generation (i.e. node is the current validator). + /// Some(false) means that the node might seal internally but is not qualified now. + fn seals_internally(&self) -> Option { None } /// Attempt to seal the block internally. /// /// If `Some` is returned, then you get a valid seal. diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index e4bb2ea2f6..bc4786526f 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -410,8 +410,8 @@ impl Engine for Tendermint { } /// Should this node participate. - fn is_sealer(&self, address: &Address) -> Option { - Some(self.is_authority(address)) + fn seals_internally(&self) -> Option { + Some(self.is_authority(&self.signer.address())) } /// Attempt to seal generate a proposal seal. diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index b4a7f2327d..410123c9af 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -215,8 +215,6 @@ pub struct Miner { sealing_block_last_request: Mutex, // for sealing... options: MinerOptions, - /// Does the node perform internal (without work) sealing. - pub seals_internally: bool, gas_range_target: RwLock<(U256, U256)>, author: RwLock
, @@ -275,9 +273,8 @@ impl Miner { queue: UsingQueue::new(options.work_queue_size), enabled: options.force_sealing || !options.new_work_notify.is_empty() - || spec.engine.is_default_sealer().unwrap_or(false) + || spec.engine.seals_internally().is_some() }), - seals_internally: spec.engine.is_default_sealer().is_some(), gas_range_target: RwLock::new((U256::zero(), U256::zero())), author: RwLock::new(Address::default()), extra_data: RwLock::new(Vec::new()), @@ -455,7 +452,7 @@ impl Miner { let last_request = *self.sealing_block_last_request.lock(); let should_disable_sealing = !self.forced_sealing() && !has_local_transactions - && !self.seals_internally + && self.engine.seals_internally().is_none() && best_block > last_request && best_block - last_request > SEALING_TIMEOUT_IN_BLOCKS; @@ -765,21 +762,21 @@ impl MinerService for Miner { } fn set_author(&self, author: Address) { - if self.seals_internally { + if self.engine.seals_internally().is_some() { let mut sealing_work = self.sealing_work.lock(); - sealing_work.enabled = self.engine.is_sealer(&author).unwrap_or(false); + sealing_work.enabled = true; } *self.author.write() = author; } fn set_engine_signer(&self, address: Address, password: String) -> Result<(), AccountError> { - if self.seals_internally { + if self.engine.seals_internally().is_some() { if let Some(ref ap) = self.accounts { ap.sign(address.clone(), Some(password.clone()), Default::default())?; // Limit the scope of the locks. { let mut sealing_work = self.sealing_work.lock(); - sealing_work.enabled = self.engine.is_sealer(&address).unwrap_or(false); + sealing_work.enabled = true; *self.author.write() = address; } // -------------------------------------------------------------------------- @@ -914,7 +911,7 @@ impl MinerService for Miner { if imported.is_ok() && self.options.reseal_on_own_tx && self.tx_reseal_allowed() { // Make sure to do it after transaction is imported and lock is droped. // We need to create pending block and enable sealing. - if self.seals_internally || !self.prepare_work_sealing(chain) { + if self.engine.seals_internally().unwrap_or(false) || !self.prepare_work_sealing(chain) { // If new block has not been prepared (means we already had one) // or Engine might be able to seal internally, // we need to update sealing. @@ -1071,14 +1068,18 @@ impl MinerService for Miner { // -------------------------------------------------------------------------- trace!(target: "miner", "update_sealing: preparing a block"); let (block, original_work_hash) = self.prepare_block(chain); - if self.seals_internally { - trace!(target: "miner", "update_sealing: engine indicates internal sealing"); - if self.seal_and_import_block_internally(chain, block) { - trace!(target: "miner", "update_sealing: imported internally sealed block"); - } - } else { - trace!(target: "miner", "update_sealing: engine does not seal internally, preparing work"); - self.prepare_work(block, original_work_hash); + match self.engine.seals_internally() { + Some(true) => { + trace!(target: "miner", "update_sealing: engine indicates internal sealing"); + if self.seal_and_import_block_internally(chain, block) { + trace!(target: "miner", "update_sealing: imported internally sealed block"); + } + }, + None => { + trace!(target: "miner", "update_sealing: engine does not seal internally, preparing work"); + self.prepare_work(block, original_work_hash) + }, + _ => trace!(target: "miner", "update_sealing: engine is not keen to seal internally right now") } } } -- GitLab From 9e210e5eda80f11a0a5f93a742d9ff8a173b06a2 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Mon, 20 Feb 2017 16:40:01 +0100 Subject: [PATCH 102/246] Vault Management UI (first round) (#4446) * Add RPCs for parity_vault (create, open, list, etc.) * WIP * WIP * WIP * WIP (create should create) * Create & close working * WIP * WIP * WIP * Open & Close now working * WIP * WIP * Merge relevant changes from js-home * Hover actions * WIP (start of account assignment) * Open, Close & Account assignment works * Fix margins * UI updates * Update tests * Add the parity_{get|set}VaultMeta calls * Handle metadata * Adjust padding in Open/Close modals * moveAccounts take both in and out * Adjust padding * Fix stretch * Optimize hover stretch * pre-merge * Cleanup variable naming (duplication) * Rename Vault{Close,Open} -> Vault{Lock,Unlock} * clearVaultFields uses setters * TODO for small Portal sizes * Vaults rendering tests * .only * libusb compile * VaultCard rendering tests * Update message keys (rename gone rouge) * Display passwordHint op vault unlock * Update failing tests * Manually dispatch allAccountsInfo when move completed * Open/Close always shows vault image in colour * Password submit submits modal (PR comment) * Add link to account --- js/src/api/subscriptions/personal.js | 1 - js/src/modals/CreateAccount/createAccount.css | 29 +- js/src/modals/CreateAccount/errors.js | 19 +- js/src/modals/VaultAccounts/index.js | 17 + js/src/modals/VaultAccounts/vaultAccounts.css | 48 ++ js/src/modals/VaultAccounts/vaultAccounts.js | 195 +++++++ .../VaultAccounts/vaultAccounts.spec.js | 179 ++++++ js/src/modals/VaultCreate/index.js | 17 + js/src/modals/VaultCreate/vaultCreate.css | 38 ++ js/src/modals/VaultCreate/vaultCreate.js | 227 ++++++++ js/src/modals/VaultCreate/vaultCreate.spec.js | 162 ++++++ js/src/modals/VaultLock/index.js | 17 + js/src/modals/VaultLock/vaultLock.js | 92 ++++ js/src/modals/VaultLock/vaultLock.spec.js | 131 +++++ js/src/modals/VaultUnlock/index.js | 17 + js/src/modals/VaultUnlock/vaultUnlock.css | 27 + js/src/modals/VaultUnlock/vaultUnlock.js | 118 ++++ js/src/modals/VaultUnlock/vaultUnlock.spec.js | 146 +++++ js/src/modals/index.js | 64 +-- js/src/routes.js | 3 +- js/src/ui/ConfirmDialog/confirmDialog.css | 1 + js/src/ui/ConfirmDialog/confirmDialog.js | 59 +- js/src/ui/ConfirmDialog/confirmDialog.spec.js | 112 ++-- js/src/ui/Container/Title/title.css | 4 + js/src/ui/Container/container.css | 18 +- js/src/ui/Icons/index.js | 3 + js/src/ui/Icons/index.spec.js | 28 + js/src/ui/Page/page.js | 24 +- js/src/ui/Page/page.spec.js | 79 +++ js/src/ui/Portal/portal.css | 29 +- js/src/ui/Portal/portal.js | 6 +- js/src/ui/Title/title.css | 9 +- js/src/ui/Title/title.js | 14 +- js/src/ui/Title/title.spec.js | 90 +++ js/src/ui/VaultCard/Layout/index.js | 17 + js/src/ui/VaultCard/Layout/layout.css | 45 ++ js/src/ui/VaultCard/Layout/layout.js | 66 +++ js/src/ui/VaultCard/Layout/layout.spec.js | 90 +++ js/src/ui/VaultCard/index.js | 17 + js/src/ui/VaultCard/vaultCard.css | 62 +++ js/src/ui/VaultCard/vaultCard.js | 102 ++++ js/src/ui/VaultCard/vaultCard.spec.js | 94 ++++ js/src/ui/index.js | 1 + js/src/views/Accounts/accounts.js | 47 +- js/src/views/Vaults/index.js | 17 + js/src/views/Vaults/store.js | 323 +++++++++++ js/src/views/Vaults/store.spec.js | 516 ++++++++++++++++++ js/src/views/Vaults/vaults.css | 25 + js/src/views/Vaults/vaults.js | 193 +++++++ js/src/views/Vaults/vaults.spec.js | 185 +++++++ js/src/views/Vaults/vaults.test.js | 90 +++ js/src/views/index.js | 1 + 52 files changed, 3722 insertions(+), 192 deletions(-) create mode 100644 js/src/modals/VaultAccounts/index.js create mode 100644 js/src/modals/VaultAccounts/vaultAccounts.css create mode 100644 js/src/modals/VaultAccounts/vaultAccounts.js create mode 100644 js/src/modals/VaultAccounts/vaultAccounts.spec.js create mode 100644 js/src/modals/VaultCreate/index.js create mode 100644 js/src/modals/VaultCreate/vaultCreate.css create mode 100644 js/src/modals/VaultCreate/vaultCreate.js create mode 100644 js/src/modals/VaultCreate/vaultCreate.spec.js create mode 100644 js/src/modals/VaultLock/index.js create mode 100644 js/src/modals/VaultLock/vaultLock.js create mode 100644 js/src/modals/VaultLock/vaultLock.spec.js create mode 100644 js/src/modals/VaultUnlock/index.js create mode 100644 js/src/modals/VaultUnlock/vaultUnlock.css create mode 100644 js/src/modals/VaultUnlock/vaultUnlock.js create mode 100644 js/src/modals/VaultUnlock/vaultUnlock.spec.js create mode 100644 js/src/ui/Icons/index.spec.js create mode 100644 js/src/ui/Page/page.spec.js create mode 100644 js/src/ui/Title/title.spec.js create mode 100644 js/src/ui/VaultCard/Layout/index.js create mode 100644 js/src/ui/VaultCard/Layout/layout.css create mode 100644 js/src/ui/VaultCard/Layout/layout.js create mode 100644 js/src/ui/VaultCard/Layout/layout.spec.js create mode 100644 js/src/ui/VaultCard/index.js create mode 100644 js/src/ui/VaultCard/vaultCard.css create mode 100644 js/src/ui/VaultCard/vaultCard.js create mode 100644 js/src/ui/VaultCard/vaultCard.spec.js create mode 100644 js/src/views/Vaults/index.js create mode 100644 js/src/views/Vaults/store.js create mode 100644 js/src/views/Vaults/store.spec.js create mode 100644 js/src/views/Vaults/vaults.css create mode 100644 js/src/views/Vaults/vaults.js create mode 100644 js/src/views/Vaults/vaults.spec.js create mode 100644 js/src/views/Vaults/vaults.test.js diff --git a/js/src/api/subscriptions/personal.js b/js/src/api/subscriptions/personal.js index c1f070262c..15b037b42c 100644 --- a/js/src/api/subscriptions/personal.js +++ b/js/src/api/subscriptions/personal.js @@ -119,7 +119,6 @@ export default class Personal { case 'parity_removeAddress': case 'parity_setAccountName': case 'parity_setAccountMeta': - case 'parity_changeVault': this._accountsInfo(); return; diff --git a/js/src/modals/CreateAccount/createAccount.css b/js/src/modals/CreateAccount/createAccount.css index 5c8bc7e5c4..d58e0edfd3 100644 --- a/js/src/modals/CreateAccount/createAccount.css +++ b/js/src/modals/CreateAccount/createAccount.css @@ -19,23 +19,24 @@ line-height: 1.618em; } -.password { - flex: 0 1 50%; - width: 50%; - box-sizing: border-box; - - &:nth-child(odd) { - padding-right: 0.25rem; - } - - &:nth-child(even) { - padding-left: 0.25rem; - } -} - +/* TODO: 2 column layout can be made generic, now duplicated in Vaults */ .passwords { display: flex; flex-wrap: wrap; + + .password { + box-sizing: border-box; + flex: 0 1 50%; + width: 50%; + + &:nth-child(odd) { + padding-right: 0.25rem; + } + + &:nth-child(even) { + padding-left: 0.25rem; + } + } } .identities, .selector { diff --git a/js/src/modals/CreateAccount/errors.js b/js/src/modals/CreateAccount/errors.js index 487bf97293..bb5708275f 100644 --- a/js/src/modals/CreateAccount/errors.js +++ b/js/src/modals/CreateAccount/errors.js @@ -18,37 +18,44 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; export default { + duplicateName: ( + + ), + noFile: ( ), noKey: ( ), noMatchPassword: ( ), noName: ( ), invalidKey: ( ) diff --git a/js/src/modals/VaultAccounts/index.js b/js/src/modals/VaultAccounts/index.js new file mode 100644 index 0000000000..506a569f39 --- /dev/null +++ b/js/src/modals/VaultAccounts/index.js @@ -0,0 +1,17 @@ +// 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 from './vaultAccounts'; diff --git a/js/src/modals/VaultAccounts/vaultAccounts.css b/js/src/modals/VaultAccounts/vaultAccounts.css new file mode 100644 index 0000000000..1960376f3a --- /dev/null +++ b/js/src/modals/VaultAccounts/vaultAccounts.css @@ -0,0 +1,48 @@ +/* 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 . +*/ + +/* TODO: These overlap with DappPermissions now, make DRY */ +/* (selection component or just styles?) */ +.iconDisabled { + opacity: 0.15; +} + +.item { + display: flex; + flex: 1; + position: relative; + + .overlay { + position: absolute; + right: 0.5em; + top: 0.5em; + } +} + +.selected, +.unselected { + margin-bottom: 0.25em; + width: 100%; + + &:focus { + outline: none; + } +} + +.selected { + background: rgba(255, 255, 255, 0.15) !important; +} diff --git a/js/src/modals/VaultAccounts/vaultAccounts.js b/js/src/modals/VaultAccounts/vaultAccounts.js new file mode 100644 index 0000000000..96f170f791 --- /dev/null +++ b/js/src/modals/VaultAccounts/vaultAccounts.js @@ -0,0 +1,195 @@ +// 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 . + +import { observer } from 'mobx-react'; +import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; + +import { newError } from '~/redux/actions'; +import { personalAccountsInfo } from '~/redux/providers/personalActions'; +import { AccountCard, Button, Portal, SectionList } from '~/ui'; +import { CancelIcon, CheckIcon } from '~/ui/Icons'; + +import styles from './vaultAccounts.css'; + +@observer +class VaultAccounts extends Component { + static contextTypes = { + api: PropTypes.object.isRequired + }; + + static propTypes = { + accounts: PropTypes.object.isRequired, + newError: PropTypes.func.isRequired, + personalAccountsInfo: PropTypes.func.isRequired, + vaultStore: PropTypes.object.isRequired + }; + + render () { + const { accounts } = this.props; + const { isBusyAccounts, isModalAccountsOpen, selectedAccounts } = this.props.vaultStore; + + if (!isModalAccountsOpen) { + return null; + } + + const vaultAccounts = Object + .keys(accounts) + .filter((address) => accounts[address].uuid) + .map((address) => accounts[address]); + + return ( + } + key='cancel' + label={ + + } + onClick={ this.onClose } + />, +