From 5d054f08c33df9ec6d17664f3529cc06758ab625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 9 Dec 2016 15:05:03 +0100 Subject: [PATCH 01/44] Clearing old transactions --- ethcore/src/miner/transaction_queue.rs | 41 +++++++++++++++++++++++++- util/table/src/lib.rs | 6 ++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index cd2d3ba47..84981b893 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -81,6 +81,8 @@ //! 3. `remove_all` is used to inform the queue about client (state) nonce changes. //! - It removes all transactions (either from `current` or `future`) with nonce < client nonce //! - It moves matching `future` transactions to `current` +//! 4. `remove_old` is used periodically to clear the transactions if node goes out of sync +//! (in such case `remove_all` is not invoked because we are syncing the chain) use std::ops::Deref; use std::cmp::Ordering; @@ -765,6 +767,20 @@ impl TransactionQueue { assert_eq!(self.future.by_priority.len() + self.current.by_priority.len(), self.by_hash.len()); } + /// Checks the current nonce for all transactions' senders in the queue and removes the old transactions. + pub fn remove_old(&mut self, fetch_account: &F) where + F: Fn(&Address) -> AccountDetails, + { + let senders = self.current.by_address + .keys() + .map(|key| (*key, fetch_account(key).nonce)) + .collect::>(); + + for (sender, nonce) in senders { + self.remove_all(sender, nonce); + } + } + /// Penalize transactions from sender of transaction with given hash. /// I.e. it should change the priority of the transaction in the queue. /// @@ -2438,7 +2454,7 @@ mod test { } #[test] - fn should_reject_transactions_below_bas_gas() { + fn should_reject_transactions_below_base_gas() { // given let mut txq = TransactionQueue::default(); let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); @@ -2457,4 +2473,27 @@ mod test { } + #[test] + fn should_clear_all_old_transactions() { + // given + let mut txq = TransactionQueue::default(); + let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); + let (tx3, tx4) = new_tx_pair_default(1.into(), 0.into()); + let nonce1 = tx1.nonce; + let new_details = |_a: &Address| AccountDetails { nonce: nonce1 + U256::one(), balance: !U256::zero() }; + + // Insert all transactions + txq.add(tx1, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx3, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx4, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + assert_eq!(txq.top_transactions().len(), 4); + + // when + txq.remove_old(&new_details); + + // then + assert_eq!(txq.top_transactions().len(), 2); + } + } diff --git a/util/table/src/lib.rs b/util/table/src/lib.rs index a13beab11..f65c1e171 100644 --- a/util/table/src/lib.rs +++ b/util/table/src/lib.rs @@ -18,6 +18,7 @@ use std::hash::Hash; use std::collections::HashMap; +use std::collections::hash_map::Keys; /// Structure to hold double-indexed values /// @@ -41,6 +42,11 @@ impl Table } } + /// Returns keys iterator for this Table. + pub fn keys(&self) -> Keys> { + self.map.keys() + } + /// Removes all elements from this Table pub fn clear(&mut self) { self.map.clear(); From cee07fef747cb5146a170e066aeb2ca2b5cc129e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 9 Dec 2016 15:54:13 +0100 Subject: [PATCH 02/44] Trigger remove_old on new block --- ethcore/src/miner/miner.rs | 60 +++++++++----------------- ethcore/src/miner/transaction_queue.rs | 42 ++++++++++++------ 2 files changed, 48 insertions(+), 54 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 8d1f55567..cebd35b09 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -1083,20 +1083,6 @@ impl MinerService for Miner { fn chain_new_blocks(&self, chain: &MiningBlockChainClient, _imported: &[H256], _invalid: &[H256], enacted: &[H256], retracted: &[H256]) { trace!(target: "miner", "chain_new_blocks"); - fn fetch_transactions(chain: &MiningBlockChainClient, hash: &H256) -> Vec { - let block = chain - .block(BlockID::Hash(*hash)) - // Client should send message after commit to db and inserting to chain. - .expect("Expected in-chain blocks."); - let block = BlockView::new(&block); - let txs = block.transactions(); - // populate sender - for tx in &txs { - let _sender = tx.sender(); - } - txs - } - // 1. We ignore blocks that were `imported` (because it means that they are not in canon-chain, and transactions // should be still available in the queue. // 2. We ignore blocks that are `invalid` because it doesn't have any meaning in terms of the transactions that @@ -1107,35 +1093,29 @@ impl MinerService for Miner { // Then import all transactions... { - let out_of_chain = retracted - .par_iter() - .map(|h| fetch_transactions(chain, h)); - out_of_chain.for_each(|txs| { - let mut transaction_queue = self.transaction_queue.lock(); - let _ = self.add_transactions_to_queue( - chain, txs, TransactionOrigin::RetractedBlock, &mut transaction_queue - ); - }); + retracted.par_iter() + .map(|hash| { + let block = chain.block(BlockID::Hash(*hash)) + .expect("Client is sending message after commit to db and inserting to chain; the block is available; qed"); + let block = BlockView::new(&block); + let txs = block.transactions(); + // populate sender + for tx in &txs { + let _sender = tx.sender(); + } + txs + }).for_each(|txs| { + let mut transaction_queue = self.transaction_queue.lock(); + let _ = self.add_transactions_to_queue( + chain, txs, TransactionOrigin::RetractedBlock, &mut transaction_queue + ); + }); } - // ...and at the end remove old ones + // ...and at the end remove the old ones { - let in_chain = enacted - .par_iter() - .map(|h: &H256| fetch_transactions(chain, h)); - - in_chain.for_each(|mut txs| { - let mut transaction_queue = self.transaction_queue.lock(); - - let to_remove = txs.drain(..) - .map(|tx| { - tx.sender().expect("Transaction is in block, so sender has to be defined.") - }) - .collect::>(); - for sender in to_remove { - transaction_queue.remove_all(sender, chain.latest_nonce(&sender)); - } - }); + let mut transaction_queue = self.transaction_queue.lock(); + transaction_queue.remove_old(|sender| chain.latest_nonce(sender)); } if enacted.len() > 0 { diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 84981b893..03a9da8e3 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -79,10 +79,10 @@ //! we check if the transactions should go to `current` (comparing state nonce) //! - When it's removed from `current` - all transactions from this sender (`current` & `future`) are recalculated. //! 3. `remove_all` is used to inform the queue about client (state) nonce changes. -//! - It removes all transactions (either from `current` or `future`) with nonce < client nonce -//! - It moves matching `future` transactions to `current` -//! 4. `remove_old` is used periodically to clear the transactions if node goes out of sync -//! (in such case `remove_all` is not invoked because we are syncing the chain) +//! - It removes all transactions (either from `current` or `future`) with nonce < client nonce +//! - It moves matching `future` transactions to `current` +//! 4. `remove_old` is used as convenient method to update the state nonce for all senders in the queue. +//! - Invokes `remove_all` with latest state nonce for all senders. use std::ops::Deref; use std::cmp::Ordering; @@ -754,6 +754,21 @@ impl TransactionQueue { /// Removes all transactions from particular sender up to (excluding) given client (state) nonce. /// Client (State) Nonce = next valid nonce for this sender. pub fn remove_all(&mut self, sender: Address, client_nonce: U256) { + // Check if there is anything in current... + let should_check_in_current = self.current.by_address.row(&sender) + // If nonce == client_nonce nothing is changed + .and_then(|by_nonce| by_nonce.keys().find(|nonce| *nonce < &client_nonce)) + .map(|_| ()); + // ... or future + let should_check_in_future = self.future.by_address.row(&sender) + // if nonce == client_nonce we need to promote to current + .and_then(|by_nonce| by_nonce.keys().find(|nonce| *nonce <= &client_nonce)) + .map(|_| ()); + + if should_check_in_current.or(should_check_in_future).is_none() { + return; + } + // We will either move transaction to future or remove it completely // so there will be no transactions from this sender in current self.last_nonces.remove(&sender); @@ -768,16 +783,16 @@ impl TransactionQueue { } /// Checks the current nonce for all transactions' senders in the queue and removes the old transactions. - pub fn remove_old(&mut self, fetch_account: &F) where - F: Fn(&Address) -> AccountDetails, + pub fn remove_old(&mut self, fetch_nonce: F) where + F: Fn(&Address) -> U256, { - let senders = self.current.by_address - .keys() - .map(|key| (*key, fetch_account(key).nonce)) - .collect::>(); + let senders = self.current.by_address.keys() + .chain(self.future.by_address.keys()) + .cloned() + .collect::>(); - for (sender, nonce) in senders { - self.remove_all(sender, nonce); + for sender in senders { + self.remove_all(sender, fetch_nonce(&sender)); } } @@ -2480,7 +2495,6 @@ mod test { let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); let (tx3, tx4) = new_tx_pair_default(1.into(), 0.into()); let nonce1 = tx1.nonce; - let new_details = |_a: &Address| AccountDetails { nonce: nonce1 + U256::one(), balance: !U256::zero() }; // Insert all transactions txq.add(tx1, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); @@ -2490,7 +2504,7 @@ mod test { assert_eq!(txq.top_transactions().len(), 4); // when - txq.remove_old(&new_details); + txq.remove_old(|_| nonce1 + U256::one()); // then assert_eq!(txq.top_transactions().len(), 2); From c91a614c3dd5503a7a8c52b01671fc9422ee3108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 9 Dec 2016 20:24:33 +0100 Subject: [PATCH 03/44] Fixing tests --- ethcore/src/miner/transaction_queue.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 03a9da8e3..b4cc93a9c 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -769,6 +769,11 @@ impl TransactionQueue { return; } + self.remove_all_internal(sender, client_nonce); + } + + /// Always updates future and moves transactions from current to future. + fn remove_all_internal(&mut self, sender: Address, client_nonce: U256) { // We will either move transaction to future or remove it completely // so there will be no transactions from this sender in current self.last_nonces.remove(&sender); @@ -878,7 +883,7 @@ impl TransactionQueue { if order.is_some() { // This will keep consistency in queue // Moves all to future and then promotes a batch from current: - self.remove_all(sender, current_nonce); + self.remove_all_internal(sender, current_nonce); assert_eq!(self.future.by_priority.len() + self.current.by_priority.len(), self.by_hash.len()); return; } From f6564dcc2f61f892d9a85f057209db5f802cf5b0 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 01:32:39 +0100 Subject: [PATCH 04/44] Fix dapps separation --- js/src/views/Dapps/dapps.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/js/src/views/Dapps/dapps.js b/js/src/views/Dapps/dapps.js index cf847202a..b0b731b93 100644 --- a/js/src/views/Dapps/dapps.js +++ b/js/src/views/Dapps/dapps.js @@ -72,9 +72,17 @@ export default class Dapps extends Component { ] } /> - { this.renderList(this.store.visibleLocal) } - { this.renderList(this.store.visibleBuiltin) } - { this.renderList(this.store.visibleNetwork, externalOverlay) } +
+ { this.renderList(this.store.visibleLocal) } +
+ +
+ { this.renderList(this.store.visibleBuiltin) } +
+ +
+ { this.renderList(this.store.visibleNetwork, externalOverlay) } +
); From 0d9b1882a31346ab5ab831ea373767c22723b2f9 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 01:56:38 +0100 Subject: [PATCH 05/44] Treat tabs as real link (enable Ctrl+Click for new Tab) --- js/src/views/Application/TabBar/tabBar.css | 31 +++++++---- js/src/views/Application/TabBar/tabBar.js | 64 +++++----------------- 2 files changed, 33 insertions(+), 62 deletions(-) diff --git a/js/src/views/Application/TabBar/tabBar.css b/js/src/views/Application/TabBar/tabBar.css index b11df4e40..7b74622d3 100644 --- a/js/src/views/Application/TabBar/tabBar.css +++ b/js/src/views/Application/TabBar/tabBar.css @@ -30,24 +30,31 @@ } } -.tabs button, +.tabLink { + display: flex; + + > * { + flex: 1; + } + + &:hover { + background: rgba(0, 0, 0, 0.4) !important; + } + + &.tabactive, &.tabactive:hover { + color: white !important; + background: rgba(0, 0, 0, 0.25) !important; + border-radius: 4px 4px 0 0; + } +} + +.tabLink, .settings, .logo, .last { background: rgba(0, 0, 0, 0.5) !important; /* rgba(0, 0, 0, 0.25) !important; */ } -.tabs button:hover { - background: rgba(0, 0, 0, 0.4) !important; -} - -button.tabactive, -button.tabactive:hover { - color: white !important; - background: rgba(0, 0, 0, 0.25) !important; - border-radius: 4px 4px 0 0; -} - .tabbarTooltip { left: 3.3em; top: 0.5em; diff --git a/js/src/views/Application/TabBar/tabBar.js b/js/src/views/Application/TabBar/tabBar.js index 9d9f55874..ccf1290c5 100644 --- a/js/src/views/Application/TabBar/tabBar.js +++ b/js/src/views/Application/TabBar/tabBar.js @@ -16,6 +16,7 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; +import { Link } from 'react-router'; import { bindActionCreators } from 'redux'; import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar'; import { Tab as MUITab } from 'material-ui/Tabs'; @@ -40,8 +41,7 @@ class Tab extends Component { active: PropTypes.bool, view: PropTypes.object, children: PropTypes.node, - pendings: PropTypes.number, - onChange: PropTypes.func + pendings: PropTypes.number }; shouldComponentUpdate (nextProps) { @@ -60,7 +60,6 @@ class Tab extends Component { selected={ active } icon={ view.icon } label={ label } - onTouchTap={ this.handleClick } > { children } @@ -118,11 +117,6 @@ class Tab extends Component { return this.renderLabel(label, null); } - - handleClick = () => { - const { onChange, view } = this.props; - onChange(view); - } } class TabBar extends Component { @@ -142,34 +136,12 @@ class TabBar extends Component { pending: [] }; - state = { - activeViewId: '' - }; - - setActiveView (props = this.props) { - const { hash, views } = props; - const view = views.find((view) => view.value === hash); - - this.setState({ activeViewId: view.id }); - } - - componentWillMount () { - this.setActiveView(); - } - - componentWillReceiveProps (nextProps) { - if (nextProps.hash !== this.props.hash) { - this.setActiveView(nextProps); - } - } - shouldComponentUpdate (nextProps, nextState) { const prevViews = this.props.views.map((v) => v.id).sort(); const nextViews = nextProps.views.map((v) => v.id).sort(); return (nextProps.hash !== this.props.hash) || (nextProps.pending.length !== this.props.pending.length) || - (nextState.activeViewId !== this.state.activeViewId) || (!isEqual(prevViews, nextViews)); } @@ -206,7 +178,6 @@ class TabBar extends Component { renderTabs () { const { views, pending } = this.props; - const { activeViewId } = this.state; const items = views .map((view, index) => { @@ -216,36 +187,29 @@ class TabBar extends Component { ) : null; - const active = activeViewId === view.id; - return ( - - { body } - + + { body } + + ); }); return ( -
+
{ items }
); } - - onChange = (view) => { - const { router } = this.context; - - router.push(view.route); - this.setState({ activeViewId: view.id }); - } } function mapStateToProps (state) { From 51b9034a5e7a0839945e79e237d154f470c20466 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 12:44:48 +0100 Subject: [PATCH 06/44] Don't show addresses while loading balances (fix flash of unsorted) --- js/src/views/Addresses/addresses.js | 37 +++++++++++++++++++---------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/js/src/views/Addresses/addresses.js b/js/src/views/Addresses/addresses.js index 5e0ed4e18..a3e52ec55 100644 --- a/js/src/views/Addresses/addresses.js +++ b/js/src/views/Addresses/addresses.js @@ -23,7 +23,7 @@ import { uniq, isEqual } from 'lodash'; import List from '../Accounts/List'; import Summary from '../Accounts/Summary'; import { AddAddress } from '~/modals'; -import { Actionbar, ActionbarExport, ActionbarImport, ActionbarSearch, ActionbarSort, Button, Page } from '~/ui'; +import { Actionbar, ActionbarExport, ActionbarImport, ActionbarSearch, ActionbarSort, Button, Page, Loading } from '~/ui'; import { setVisibleAccounts } from '~/redux/providers/personalActions'; import styles from './addresses.css'; @@ -72,27 +72,40 @@ class Addresses extends Component { } render () { - const { balances, contacts, hasContacts } = this.props; - const { searchValues, sortOrder } = this.state; - return (
{ this.renderActionbar() } { this.renderAddAddress() } - + { this.renderAccountsList() }
); } + renderAccountsList () { + const { balances, contacts, hasContacts } = this.props; + const { searchValues, sortOrder } = this.state; + + if (hasContacts && Object.keys(balances).length === 0) { + return ( + + ); + } + + return ( + + ); + } + renderSortButton () { const onChange = (sortOrder) => { this.setState({ sortOrder }); From 591d086f42d4ba5e92936df277f14054e42050d2 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 14:19:15 +0100 Subject: [PATCH 07/44] Better use of React-Router (maintaining old routes) --- js/src/main.js | 59 +++++++++++++++++++----- js/src/views/Accounts/Summary/summary.js | 2 +- js/src/views/Addresses/addresses.js | 2 +- js/src/views/Contracts/contracts.js | 2 +- 4 files changed, 51 insertions(+), 14 deletions(-) diff --git a/js/src/main.js b/js/src/main.js index d508c50fc..c1dda9d57 100644 --- a/js/src/main.js +++ b/js/src/main.js @@ -15,7 +15,7 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; -import { Redirect, Router, Route } from 'react-router'; +import { Redirect, Router, Route, IndexRoute } from 'react-router'; import { Accounts, Account, Addresses, Address, Application, Contract, Contracts, WriteContract, Wallet, Dapp, Dapps, Settings, SettingsBackground, SettingsParity, SettingsProxy, SettingsViews, Signer, Status } from '~/views'; @@ -26,6 +26,23 @@ export default class MainApplication extends Component { routerHistory: PropTypes.any.isRequired }; + handleDeprecatedRoute = (nextState, replace) => { + const { address } = nextState.params; + const redirectMap = { + account: 'accounts', + address: 'addresses', + contract: 'contracts' + }; + + const oldRoute = nextState.routes[0].path; + const newRoute = Object.keys(redirectMap).reduce((newRoute, key) => { + return newRoute.replace(new RegExp(`^/${key}`), '/' + redirectMap[key]); + }, oldRoute); + + console.warn(`Route "${oldRoute}" is deprecated. Please use "${newRoute}"`); + replace(newRoute.replace(':address', address)); + } + render () { const { routerHistory } = this.props; @@ -34,26 +51,46 @@ export default class MainApplication extends Component { + + { /** Backward Compatible links */ } + + + + - - - - - + + + + + + + + + + + - - - + + + + + + + + - - + + + + + ); diff --git a/js/src/views/Accounts/Summary/summary.js b/js/src/views/Accounts/Summary/summary.js index 3183a2903..a19b9a9de 100644 --- a/js/src/views/Accounts/Summary/summary.js +++ b/js/src/views/Accounts/Summary/summary.js @@ -153,7 +153,7 @@ export default class Summary extends Component { const { link, noLink, account, name } = this.props; const { address } = account; - const viewLink = `/${link || 'account'}/${address}`; + const viewLink = `/${link || 'accounts'}/${address}`; const content = ( diff --git a/js/src/views/Addresses/addresses.js b/js/src/views/Addresses/addresses.js index a3e52ec55..fd26d94e5 100644 --- a/js/src/views/Addresses/addresses.js +++ b/js/src/views/Addresses/addresses.js @@ -95,7 +95,7 @@ class Addresses extends Component { return ( Date: Sat, 10 Dec 2016 14:20:34 +0100 Subject: [PATCH 08/44] Network connectivity fixes --- ethcore/res/ethereum/frontier.json | 3 --- sync/src/chain.rs | 2 +- sync/src/sync_io.rs | 4 ++++ util/network/src/host.rs | 14 +++++++++----- util/network/src/session.rs | 8 ++++---- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/ethcore/res/ethereum/frontier.json b/ethcore/res/ethereum/frontier.json index 2c520c46a..3a9dce456 100644 --- a/ethcore/res/ethereum/frontier.json +++ b/ethcore/res/ethereum/frontier.json @@ -170,9 +170,6 @@ "enode://cadc6e573b6bc2a9128f2f635ac0db3353e360b56deef239e9be7e7fce039502e0ec670b595f6288c0d2116812516ad6b6ff8d5728ff45eba176989e40dead1e@37.128.191.230:30303", "enode://595a9a06f8b9bc9835c8723b6a82105aea5d55c66b029b6d44f229d6d135ac3ecdd3e9309360a961ea39d7bee7bac5d03564077a4e08823acc723370aace65ec@46.20.235.22:30303", "enode://029178d6d6f9f8026fc0bc17d5d1401aac76ec9d86633bba2320b5eed7b312980c0a210b74b20c4f9a8b0b2bf884b111fa9ea5c5f916bb9bbc0e0c8640a0f56c@216.158.85.185:30303", - "enode://84f5d5957b4880a8b0545e32e05472318898ad9fc8ebe1d56c90c12334a98e12351eccfdf3a2bf72432ac38b57e9d348400d17caa083879ade3822390f89773f@10.1.52.78:30303", - "enode://f90dc9b9bf7b8db97726b7849e175f1eb2707f3d8f281c929336e398dd89b0409fc6aeceb89e846278e9d3ecc3857cebfbe6758ff352ece6fe5d42921ee761db@10.1.173.87:30303", - "enode://6a868ced2dec399c53f730261173638a93a40214cf299ccf4d42a76e3fa54701db410669e8006347a4b3a74fa090bb35af0320e4bc8d04cf5b7f582b1db285f5@10.3.149.199:30303", "enode://fdd1b9bb613cfbc200bba17ce199a9490edc752a833f88d4134bf52bb0d858aa5524cb3ec9366c7a4ef4637754b8b15b5dc913e4ed9fdb6022f7512d7b63f181@212.47.247.103:30303", "enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303", "enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303", diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 2d53ad5ee..9f054bec8 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -1678,7 +1678,7 @@ impl ChainSync { pub fn on_packet(&mut self, io: &mut SyncIo, peer: PeerId, packet_id: u8, data: &[u8]) { if packet_id != STATUS_PACKET && !self.peers.contains_key(&peer) { - debug!(target:"sync", "Unexpected packet from unregistered peer: {}:{}", peer, io.peer_info(peer)); + debug!(target:"sync", "Unexpected packet {} from unregistered peer: {}:{}", packet_id, peer, io.peer_info(peer)); return; } let rlp = UntrustedRlp::new(data); diff --git a/sync/src/sync_io.rs b/sync/src/sync_io.rs index 8dc8c65c0..e9e2d3396 100644 --- a/sync/src/sync_io.rs +++ b/sync/src/sync_io.rs @@ -131,6 +131,10 @@ impl<'s, 'h> SyncIo for NetSyncIo<'s, 'h> { fn protocol_version(&self, protocol: &ProtocolId, peer_id: PeerId) -> u8 { self.network.protocol_version(*protocol, peer_id).unwrap_or(0) } + + fn peer_info(&self, peer_id: PeerId) -> String { + self.network.peer_client_version(peer_id) + } } diff --git a/util/network/src/host.rs b/util/network/src/host.rs index f75158e4a..975fb87b8 100644 --- a/util/network/src/host.rs +++ b/util/network/src/host.rs @@ -22,7 +22,7 @@ use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering as AtomicOrdering}; use std::ops::*; use std::cmp::min; use std::path::{Path, PathBuf}; -use std::io::{Read, Write}; +use std::io::{Read, Write, ErrorKind}; use std::fs; use ethkey::{KeyPair, Secret, Random, Generator}; use mio::*; @@ -381,8 +381,6 @@ pub struct Host { impl Host { /// Create a new instance pub fn new(mut config: NetworkConfiguration, stats: Arc) -> Result { - trace!(target: "host", "Creating new Host object"); - let mut listen_address = match config.listen_address { None => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), DEFAULT_PORT)), Some(addr) => addr, @@ -405,6 +403,7 @@ impl Host { // Setup the server socket let tcp_listener = try!(TcpListener::bind(&listen_address)); listen_address = SocketAddr::new(listen_address.ip(), try!(tcp_listener.local_addr()).port()); + debug!(target: "network", "Listening at {:?}", listen_address); let udp_port = config.udp_port.unwrap_or(listen_address.port()); let local_endpoint = NodeEndpoint { address: listen_address, udp_port: udp_port }; @@ -707,7 +706,10 @@ impl Host { } }; match TcpStream::connect(&address) { - Ok(socket) => socket, + Ok(socket) => { + trace!(target: "network", "Connecting to {:?}", address); + socket + }, Err(e) => { debug!(target: "network", "Can't connect to address {:?}: {:?}", address, e); return; @@ -749,7 +751,9 @@ impl Host { let socket = match self.tcp_listener.lock().accept() { Ok((sock, _addr)) => sock, Err(e) => { - debug!(target: "network", "Error accepting connection: {:?}", e); + if e.kind() != ErrorKind::WouldBlock { + debug!(target: "network", "Error accepting connection: {:?}", e); + } break }, }; diff --git a/util/network/src/session.rs b/util/network/src/session.rs index 9c8bed9da..3aab05d9a 100644 --- a/util/network/src/session.rs +++ b/util/network/src/session.rs @@ -435,16 +435,16 @@ impl Session { // map to protocol let protocol = self.info.capabilities[i].protocol; - let pid = packet_id - self.info.capabilities[i].id_offset; + let protocol_packet_id = packet_id - self.info.capabilities[i].id_offset; match *self.protocol_states.entry(protocol).or_insert_with(|| ProtocolState::Pending(Vec::new())) { ProtocolState::Connected => { - trace!(target: "network", "Packet {} mapped to {:?}:{}, i={}, capabilities={:?}", packet_id, protocol, pid, i, self.info.capabilities); - Ok(SessionData::Packet { data: packet.data, protocol: protocol, packet_id: pid } ) + trace!(target: "network", "Packet {} mapped to {:?}:{}, i={}, capabilities={:?}", packet_id, protocol, protocol_packet_id, i, self.info.capabilities); + Ok(SessionData::Packet { data: packet.data, protocol: protocol, packet_id: protocol_packet_id } ) } ProtocolState::Pending(ref mut pending) => { trace!(target: "network", "Packet {} deferred until protocol connection event completion", packet_id); - pending.push((packet.data, packet_id)); + pending.push((packet.data, protocol_packet_id)); Ok(SessionData::Continue) } From 13607d48be99d52240912138c1cb1479d7d63abb Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 14:26:35 +0100 Subject: [PATCH 09/44] Better use of Tab Bar --- js/src/views/Application/TabBar/tabBar.js | 59 +++++++++++------------ 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/js/src/views/Application/TabBar/tabBar.js b/js/src/views/Application/TabBar/tabBar.js index ccf1290c5..63f569f1f 100644 --- a/js/src/views/Application/TabBar/tabBar.js +++ b/js/src/views/Application/TabBar/tabBar.js @@ -17,7 +17,6 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { Link } from 'react-router'; -import { bindActionCreators } from 'redux'; import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar'; import { Tab as MUITab } from 'material-ui/Tabs'; import { isEqual } from 'lodash'; @@ -27,37 +26,24 @@ import { Badge, Tooltip } from '~/ui'; import styles from './tabBar.css'; import imagesEthcoreBlock from '../../../../assets/images/parity-logo-white-no-text.svg'; -const TABMAP = { - accounts: 'account', - wallet: 'account', - addresses: 'address', - apps: 'app', - contracts: 'contract', - deploy: 'contract' -}; - class Tab extends Component { static propTypes = { - active: PropTypes.bool, view: PropTypes.object, children: PropTypes.node, pendings: PropTypes.number }; shouldComponentUpdate (nextProps) { - return nextProps.active !== this.props.active || - (nextProps.view.id === 'signer' && nextProps.pendings !== this.props.pendings); + return (nextProps.view.id === 'signer' && nextProps.pendings !== this.props.pendings); } render () { - const { active, view, children } = this.props; + const { view, children } = this.props; const label = this.getLabel(view); return ( @@ -126,7 +112,6 @@ class TabBar extends Component { static propTypes = { views: PropTypes.array.isRequired, - hash: PropTypes.string.isRequired, pending: PropTypes.array, isTest: PropTypes.bool, netChain: PropTypes.string @@ -140,8 +125,7 @@ class TabBar extends Component { const prevViews = this.props.views.map((v) => v.id).sort(); const nextViews = nextProps.views.map((v) => v.id).sort(); - return (nextProps.hash !== this.props.hash) || - (nextProps.pending.length !== this.props.pending.length) || + return (nextProps.pending.length !== this.props.pending.length) || (!isEqual(prevViews, nextViews)); } @@ -212,28 +196,41 @@ class TabBar extends Component { } } -function mapStateToProps (state) { - const { views } = state.settings; +function mapStateToProps (initState) { + const { views } = initState.settings; - const filteredViews = Object + let filteredViewIds = Object .keys(views) - .filter((id) => views[id].fixed || views[id].active) + .filter((id) => views[id].fixed || views[id].active); + + let filteredViews = filteredViewIds .map((id) => ({ ...views[id], id })); - const windowHash = (window.location.hash || '').split('?')[0].split('/')[1]; - const hash = TABMAP[windowHash] || windowHash; + return (state) => { + const { views } = state.settings; - return { views: filteredViews, hash }; -} + const viewIds = Object + .keys(views) + .filter((id) => views[id].fixed || views[id].active); -function mapDispatchToProps (dispatch) { - return bindActionCreators({}, dispatch); + if (isEqual(viewIds, filteredViewIds)) { + return { views: filteredViews }; + } + + filteredViewIds = viewIds; + filteredViews = viewIds + .map((id) => ({ + ...views[id], + id + })); + + return { views: filteredViews }; + }; } export default connect( - mapStateToProps, - mapDispatchToProps + mapStateToProps )(TabBar); From 81c5085b35ad05313d9999ff80018e375d5c8413 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 14:31:20 +0100 Subject: [PATCH 10/44] Don't create new Contracts instance if already exists --- js/src/contracts/contracts.js | 4 ++++ js/src/views/Account/account.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/js/src/contracts/contracts.js b/js/src/contracts/contracts.js index f61a63690..a8020b825 100644 --- a/js/src/contracts/contracts.js +++ b/js/src/contracts/contracts.js @@ -62,6 +62,10 @@ export default class Contracts { } static create (api) { + if (instance) { + return instance; + } + return new Contracts(api); } diff --git a/js/src/views/Account/account.js b/js/src/views/Account/account.js index cbacd5280..840de05b9 100644 --- a/js/src/views/Account/account.js +++ b/js/src/views/Account/account.js @@ -26,7 +26,7 @@ import VerifyIcon from 'material-ui/svg-icons/action/verified-user'; import { EditMeta, DeleteAccount, Shapeshift, SMSVerification, Transfer, PasswordManager } from '~/modals'; import { Actionbar, Button, Page } from '~/ui'; -import shapeshiftBtn from '../../../assets/images/shapeshift-btn.png'; +import shapeshiftBtn from '~/../assets/images/shapeshift-btn.png'; import Header from './Header'; import Transactions from './Transactions'; From 65f586ed1478f8dc4fc389b94d9e582319703f92 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 14:32:54 +0100 Subject: [PATCH 11/44] Fix tab bar active style --- js/src/views/Application/TabBar/tabBar.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/js/src/views/Application/TabBar/tabBar.css b/js/src/views/Application/TabBar/tabBar.css index 7b74622d3..172750c8f 100644 --- a/js/src/views/Application/TabBar/tabBar.css +++ b/js/src/views/Application/TabBar/tabBar.css @@ -42,9 +42,12 @@ } &.tabactive, &.tabactive:hover { - color: white !important; background: rgba(0, 0, 0, 0.25) !important; border-radius: 4px 4px 0 0; + + * { + color: white !important; + } } } From ef93262311c97ef7f1b0f845a45652894c3c5121 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 15:19:05 +0100 Subject: [PATCH 12/44] See addresses outside address book + Save them --- js/src/modals/AddAddress/addAddress.js | 9 ++ js/src/views/Account/Header/header.css | 4 + js/src/views/Account/Header/header.js | 27 ++++-- js/src/views/Address/address.js | 109 +++++++++++++++++++------ 4 files changed, 119 insertions(+), 30 deletions(-) diff --git a/js/src/modals/AddAddress/addAddress.js b/js/src/modals/AddAddress/addAddress.js index e44cb0b3c..a72158cc7 100644 --- a/js/src/modals/AddAddress/addAddress.js +++ b/js/src/modals/AddAddress/addAddress.js @@ -28,6 +28,7 @@ export default class AddAddress extends Component { static propTypes = { contacts: PropTypes.object.isRequired, + address: PropTypes.string, onClose: PropTypes.func }; @@ -39,6 +40,12 @@ export default class AddAddress extends Component { description: '' }; + componentWillMount () { + if (this.props.address) { + this.onEditAddress(null, this.props.address); + } + } + render () { return (
- } /> -
+ { this.renderName(address) } + +
{ address }
+ { uuidText }
{ meta.description }
{ this.renderTxCount() }
+
@@ -89,6 +94,18 @@ export default class Header extends Component { ); } + renderName (address) { + const { hideName } = this.props; + + if (hideName) { + return null; + } + + return ( + } /> + ); + } + renderTxCount () { const { balance, isContract } = this.props; diff --git a/js/src/views/Address/address.js b/js/src/views/Address/address.js index c1427b2be..9c39203ba 100644 --- a/js/src/views/Address/address.js +++ b/js/src/views/Address/address.js @@ -19,8 +19,9 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import ActionDelete from 'material-ui/svg-icons/action/delete'; import ContentCreate from 'material-ui/svg-icons/content/create'; +import ContentAdd from 'material-ui/svg-icons/content/add'; -import { EditMeta } from '~/modals'; +import { EditMeta, AddAddress } from '~/modals'; import { Actionbar, Button, Page } from '~/ui'; import Header from '../Account/Header'; @@ -32,7 +33,7 @@ class Address extends Component { static contextTypes = { api: PropTypes.object.isRequired, router: PropTypes.object.isRequired - } + }; static propTypes = { setVisibleAccounts: PropTypes.func.isRequired, @@ -40,12 +41,13 @@ class Address extends Component { contacts: PropTypes.object, balances: PropTypes.object, params: PropTypes.object - } + }; state = { showDeleteDialog: false, - showEditDialog: false - } + showEditDialog: false, + showAdd: false + }; componentDidMount () { this.setVisibleAccounts(); @@ -73,32 +75,69 @@ class Address extends Component { render () { const { contacts, balances } = this.props; const { address } = this.props.params; - const { showDeleteDialog } = this.state; + + if (Object.keys(contacts).length === 0) { + return null; + } const contact = (contacts || {})[address]; const balance = (balances || {})[address]; - if (!contact) { + return ( +
+ { this.renderAddAddress(contact, address) } + { this.renderEditDialog(contact) } + { this.renderActionbar(contact) } + { this.renderDelete(contact) } + +
+ + +
+ ); + } + + renderAddAddress (contact, address) { + if (contact) { + return null; + } + + const { contacts } = this.props; + const { showAdd } = this.state; + + if (!showAdd) { return null; } return ( -
- { this.renderEditDialog(contact) } - { this.renderActionbar(contact) } - - -
- - -
+ + ); + } + + renderDelete (contact) { + if (!contact) { + return null; + } + + const { showDeleteDialog } = this.state; + + return ( + ); } @@ -116,17 +155,27 @@ class Address extends Component { onClick={ this.showDeleteDialog } /> ]; + const addToBook = ( +
); diff --git a/js/src/modals/Transfer/store.js b/js/src/modals/Transfer/store.js index 3a8f54f92..e08d7203d 100644 --- a/js/src/modals/Transfer/store.js +++ b/js/src/modals/Transfer/store.js @@ -54,6 +54,7 @@ export default class TransferStore { @observable sender = ''; @observable senderError = null; + @observable sendersBalances = {}; @observable total = '0.0'; @observable totalError = null; @@ -66,8 +67,6 @@ export default class TransferStore { onClose = null; senders = null; - sendersBalances = null; - isWallet = false; wallet = null; diff --git a/js/src/modals/Transfer/transfer.js b/js/src/modals/Transfer/transfer.js index 0c96a1168..57dc569f2 100644 --- a/js/src/modals/Transfer/transfer.js +++ b/js/src/modals/Transfer/transfer.js @@ -155,8 +155,8 @@ class Transfer extends Component { renderDetailsPage () { const { account, balance, images, senders } = this.props; - const { valueAll, extras, recipient, recipientError, sender, senderError } = this.store; - const { tag, total, totalError, value, valueError } = this.store; + const { recipient, recipientError, sender, senderError, sendersBalances } = this.store; + const { valueAll, extras, tag, total, totalError, value, valueError } = this.store; return (
tok.token.tag === tag); + const tokenIndex = nextTokens.findIndex((tok) => tok.token && tok.token.tag === tag); if (tokenIndex === -1) { nextTokens.push({ diff --git a/js/src/ui/Form/AddressSelect/addressSelect.css b/js/src/ui/Form/AddressSelect/addressSelect.css index 30671db73..01bc8901d 100644 --- a/js/src/ui/Form/AddressSelect/addressSelect.css +++ b/js/src/ui/Form/AddressSelect/addressSelect.css @@ -15,7 +15,9 @@ /* along with Parity. If not, see . */ .account { - padding: 4px 0 0 0; + padding: 0.25em 0; + display: flex; + align-items: center; } .name { @@ -27,6 +29,11 @@ padding: 0 0 0 1em; } +.balance { + color: #aaa; + padding-left: 1em; +} + .image { display: inline-block; height: 32px; diff --git a/js/src/ui/Form/AddressSelect/addressSelect.js b/js/src/ui/Form/AddressSelect/addressSelect.js index d0f331c34..2fbcc80bf 100644 --- a/js/src/ui/Form/AddressSelect/addressSelect.js +++ b/js/src/ui/Form/AddressSelect/addressSelect.js @@ -21,6 +21,8 @@ import AutoComplete from '../AutoComplete'; import IdentityIcon from '../../IdentityIcon'; import IdentityName from '../../IdentityName'; +import { fromWei } from '~/api/util/wei'; + import styles from './addressSelect.css'; export default class AddressSelect extends Component { @@ -40,7 +42,8 @@ export default class AddressSelect extends Component { value: PropTypes.string, tokens: PropTypes.object, onChange: PropTypes.func.isRequired, - allowInput: PropTypes.bool + allowInput: PropTypes.bool, + balances: PropTypes.object } state = { @@ -129,7 +132,34 @@ export default class AddressSelect extends Component { }; } + renderBalance (address) { + const { balances = {} } = this.props; + const balance = balances[address]; + + if (!balance) { + return null; + } + + const ethToken = balance.tokens.find((t) => t.token.tag.toLowerCase() === 'eth'); + + if (!ethToken) { + return null; + } + + const value = fromWei(ethToken.value); + + return ( +
+ { value.toFormat(3) } { 'ETH' } +
+ ); + } + renderMenuItem (address) { + const balance = this.props.balances + ? this.renderBalance(address) + : null; + const item = (
+ + { balance }
); @@ -155,11 +187,10 @@ export default class AddressSelect extends Component { getSearchText () { const entry = this.getEntry(); - const { value } = this.state; return entry && entry.name ? entry.name.toUpperCase() - : value; + : this.state.value; } getEntry () { From 84116130f6b93ad6cfaa094818e28d459998174a Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 16:58:03 +0100 Subject: [PATCH 15/44] Add sender balances to contract (exec/deploy) --- .../DeployContract/DetailsStep/detailsStep.js | 3 +++ .../modals/DeployContract/deployContract.js | 22 +++++++++++++++++-- .../DetailsStep/detailsStep.js | 15 ++++++++----- .../modals/ExecuteContract/executeContract.js | 14 +++++++++--- 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/js/src/modals/DeployContract/DetailsStep/detailsStep.js b/js/src/modals/DeployContract/DetailsStep/detailsStep.js index aa0a30e55..9db223793 100644 --- a/js/src/modals/DeployContract/DetailsStep/detailsStep.js +++ b/js/src/modals/DeployContract/DetailsStep/detailsStep.js @@ -37,6 +37,7 @@ export default class DetailsStep extends Component { onParamsChange: PropTypes.func.isRequired, onInputsChange: PropTypes.func.isRequired, + balances: PropTypes.object, fromAddress: PropTypes.string, fromAddressError: PropTypes.string, name: PropTypes.string, @@ -77,6 +78,7 @@ export default class DetailsStep extends Component { render () { const { accounts, + balances, readOnly, fromAddress, fromAddressError, @@ -97,6 +99,7 @@ export default class DetailsStep extends Component { value={ fromAddress } error={ fromAddressError } accounts={ accounts } + balances={ balances } onChange={ this.onFromAddressChange } /> . import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; import ContentClear from 'material-ui/svg-icons/content/clear'; +import { pick } from 'lodash'; import { BusyStep, CompletedStep, CopyToClipboard, Button, IdentityIcon, Modal, TxHash } from '~/ui'; import { ERRORS, validateAbi, validateCode, validateName } from '~/util/validation'; @@ -36,7 +38,7 @@ const STEPS = { COMPLETED: { title: 'completed' } }; -export default class DeployContract extends Component { +class DeployContract extends Component { static contextTypes = { api: PropTypes.object.isRequired, store: PropTypes.object.isRequired @@ -45,6 +47,7 @@ export default class DeployContract extends Component { static propTypes = { accounts: PropTypes.object.isRequired, onClose: PropTypes.func.isRequired, + balances: PropTypes.object, abi: PropTypes.string, code: PropTypes.string, readOnly: PropTypes.bool, @@ -192,7 +195,7 @@ export default class DeployContract extends Component { } renderStep () { - const { accounts, readOnly } = this.props; + const { accounts, readOnly, balances } = this.props; const { address, deployError, step, deployState, txhash, rejected } = this.state; if (deployError) { @@ -216,6 +219,7 @@ export default class DeployContract extends Component { { + const balances = pick(state.balances.balances, fromAddresses); + return { balances }; + }; +} + +export default connect( + mapStateToProps +)(DeployContract); + diff --git a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js index 3ffb929a9..7bbe7be84 100644 --- a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js +++ b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js @@ -35,22 +35,24 @@ export default class DetailsStep extends Component { amount: PropTypes.string, amountError: PropTypes.string, onAmountChange: PropTypes.func.isRequired, + onFromAddressChange: PropTypes.func.isRequired, + onValueChange: PropTypes.func.isRequired, + values: PropTypes.array.isRequired, + valuesError: PropTypes.array.isRequired, + + balances: PropTypes.object, fromAddress: PropTypes.string, fromAddressError: PropTypes.string, gasEdit: PropTypes.bool, - onFromAddressChange: PropTypes.func.isRequired, func: PropTypes.object, funcError: PropTypes.string, onFuncChange: PropTypes.func, onGasEditClick: PropTypes.func, - values: PropTypes.array.isRequired, - valuesError: PropTypes.array.isRequired, - warning: PropTypes.string, - onValueChange: PropTypes.func.isRequired + warning: PropTypes.string } render () { - const { accounts, amount, amountError, fromAddress, fromAddressError, gasEdit, onGasEditClick, onFromAddressChange, onAmountChange } = this.props; + const { accounts, amount, amountError, balances, fromAddress, fromAddressError, gasEdit, onGasEditClick, onFromAddressChange, onAmountChange } = this.props; return (
@@ -61,6 +63,7 @@ export default class DetailsStep extends Component { value={ fromAddress } error={ fromAddressError } accounts={ accounts } + balances={ balances } onChange={ onFromAddressChange } /> { this.renderFunctionSelect() } { this.renderParameters() } diff --git a/js/src/modals/ExecuteContract/executeContract.js b/js/src/modals/ExecuteContract/executeContract.js index 7b4e8ccd2..c3ac96490 100644 --- a/js/src/modals/ExecuteContract/executeContract.js +++ b/js/src/modals/ExecuteContract/executeContract.js @@ -18,6 +18,8 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { observer } from 'mobx-react'; +import { pick } from 'lodash'; + import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; import ContentClear from 'material-ui/svg-icons/content/clear'; import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; @@ -57,6 +59,7 @@ class ExecuteContract extends Component { isTest: PropTypes.bool, fromAddress: PropTypes.string, accounts: PropTypes.object, + balances: PropTypes.object, contract: PropTypes.object, gasLimit: PropTypes.object.isRequired, onClose: PropTypes.func.isRequired, @@ -362,10 +365,15 @@ class ExecuteContract extends Component { } } -function mapStateToProps (state) { - const { gasLimit } = state.nodeStatus; +function mapStateToProps (initState, initProps) { + const fromAddresses = Object.keys(initProps.accounts); - return { gasLimit }; + return (state) => { + const balances = pick(state.balances.balances, fromAddresses); + const { gasLimit } = state.nodeStatus; + + return { gasLimit, balances }; + }; } function mapDispatchToProps (dispatch) { From cd6ab072170bfe30ac04a79008c9024d784141e6 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 17:06:44 +0100 Subject: [PATCH 16/44] Use the new `onClose` autocomplete prop --- js/src/ui/Form/AutoComplete/autocomplete.js | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/js/src/ui/Form/AutoComplete/autocomplete.js b/js/src/ui/Form/AutoComplete/autocomplete.js index c7a5dd141..f5ad43201 100644 --- a/js/src/ui/Form/AutoComplete/autocomplete.js +++ b/js/src/ui/Form/AutoComplete/autocomplete.js @@ -44,7 +44,6 @@ export default class AutoComplete extends Component { lastChangedValue: undefined, entry: null, open: false, - fakeBlur: false, dataSource: [] } @@ -78,7 +77,7 @@ export default class AutoComplete extends Component { onUpdateInput={ onUpdateInput } searchText={ value } onFocus={ this.onFocus } - onBlur={ this.onBlur } + onClose={ this.onClose } animation={ PopoverAnimationVertical } filter={ filter } popoverProps={ { open } } @@ -121,7 +120,6 @@ export default class AutoComplete extends Component { case 'down': const { menu } = muiAutocomplete.refs; menu && menu.handleKeyDown(event); - this.setState({ fakeBlur: true }); break; case 'enter': @@ -155,22 +153,12 @@ export default class AutoComplete extends Component { this.setState({ entry, open: false }); } - onBlur = (event) => { + onClose = (event) => { const { onUpdateInput } = this.props; - // TODO: Handle blur gracefully where we use onUpdateInput (currently replaces - // input where text is allowed with the last selected value from the dropdown) if (!onUpdateInput) { - window.setTimeout(() => { - const { entry, fakeBlur } = this.state; - - if (fakeBlur) { - this.setState({ fakeBlur: false }); - return; - } - - this.handleOnChange(entry); - }, 200); + const { entry } = this.state; + this.handleOnChange(entry); } } From 2346f29731da1816eab4115c8e130c6ad4745aa3 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 18:35:54 +0100 Subject: [PATCH 17/44] Add dividers to AutoComplete --- js/src/ui/Form/AddressSelect/addressSelect.js | 32 +++++-- js/src/ui/Form/AutoComplete/autocomplete.js | 94 +++++++++++++++---- 2 files changed, 103 insertions(+), 23 deletions(-) diff --git a/js/src/ui/Form/AddressSelect/addressSelect.js b/js/src/ui/Form/AddressSelect/addressSelect.js index 2fbcc80bf..4bd93caa9 100644 --- a/js/src/ui/Form/AddressSelect/addressSelect.js +++ b/js/src/ui/Form/AddressSelect/addressSelect.js @@ -47,23 +47,41 @@ export default class AddressSelect extends Component { } state = { + autocompleteEntries: [], entries: {}, addresses: [], value: '' } entriesFromProps (props = this.props) { - const { accounts, contacts, contracts, wallets } = props; - const entries = Object.assign({}, accounts || {}, wallets || {}, contacts || {}, contracts || {}); - return entries; + const { accounts = {}, contacts = {}, contracts = {}, wallets = {} } = props; + + const autocompleteEntries = [].concat( + Object.values(wallets), + 'divider', + Object.values(accounts), + 'divider', + Object.values(contacts), + 'divider', + Object.values(contracts) + ); + + const entries = { + ...wallets, + ...accounts, + ...contacts, + ...contracts + }; + + return { autocompleteEntries, entries }; } componentWillMount () { const { value } = this.props; - const entries = this.entriesFromProps(); + const { entries, autocompleteEntries } = this.entriesFromProps(); const addresses = Object.keys(entries).sort(); - this.setState({ entries, addresses, value }); + this.setState({ autocompleteEntries, entries, addresses, value }); } componentWillReceiveProps (newProps) { @@ -74,7 +92,7 @@ export default class AddressSelect extends Component { render () { const { allowInput, disabled, error, hint, label } = this.props; - const { entries, value } = this.state; + const { autocompleteEntries, value } = this.state; const searchText = this.getSearchText(); const icon = this.renderIdentityIcon(value); @@ -92,7 +110,7 @@ export default class AddressSelect extends Component { onUpdateInput={ allowInput && this.onUpdateInput } value={ searchText } filter={ this.handleFilter } - entries={ entries } + entries={ autocompleteEntries } entry={ this.getEntry() || {} } renderItem={ this.renderItem } /> diff --git a/js/src/ui/Form/AutoComplete/autocomplete.js b/js/src/ui/Form/AutoComplete/autocomplete.js index f5ad43201..0e2acc98b 100644 --- a/js/src/ui/Form/AutoComplete/autocomplete.js +++ b/js/src/ui/Form/AutoComplete/autocomplete.js @@ -17,9 +17,10 @@ import React, { Component, PropTypes } from 'react'; import keycode from 'keycode'; import { MenuItem, AutoComplete as MUIAutoComplete } from 'material-ui'; +import Divider from 'material-ui/Divider'; import { PopoverAnimationVertical } from 'material-ui/Popover'; -import { isEqual } from 'lodash'; +import { isEqual, range } from 'lodash'; export default class AutoComplete extends Component { static propTypes = { @@ -38,14 +39,17 @@ export default class AutoComplete extends Component { PropTypes.array, PropTypes.object ]) - } + }; state = { lastChangedValue: undefined, entry: null, open: false, - dataSource: [] - } + dataSource: [], + dividerBreaks: [] + }; + + dividersVisibility = {}; componentWillMount () { const dataSource = this.getDataSource(); @@ -63,7 +67,7 @@ export default class AutoComplete extends Component { } render () { - const { disabled, error, hint, label, value, className, filter, onUpdateInput } = this.props; + const { disabled, error, hint, label, value, className, onUpdateInput } = this.props; const { open, dataSource } = this.state; return ( @@ -79,7 +83,7 @@ export default class AutoComplete extends Component { onFocus={ this.onFocus } onClose={ this.onClose } animation={ PopoverAnimationVertical } - filter={ filter } + filter={ this.handleFilter } popoverProps={ { open } } openOnFocus menuCloseDelay={ 0 } @@ -99,18 +103,76 @@ export default class AutoComplete extends Component { ? entries : Object.values(entries); - if (renderItem && typeof renderItem === 'function') { - return entriesArray.map(entry => renderItem(entry)); + let currentDivider = 0; + let firstSet = false; + + const dataSource = entriesArray.map((entry, index) => { + // Render divider + if (typeof entry === 'string' && entry.toLowerCase() === 'divider') { + // Don't add divider if nothing before + if (!firstSet) { + return undefined; + } + + const item = { + text: '', + divider: currentDivider, + isDivider: true, + value: ( + + ) + }; + + currentDivider++; + return item; + } + + let item; + + if (renderItem && typeof renderItem === 'function') { + item = renderItem(entry); + } else { + item = { + text: entry, + value: ( + + ) + }; + } + + if (!firstSet) { + item.first = true; + firstSet = true; + } + + item.divider = currentDivider; + + return item; + }).filter((item) => item !== undefined); + + return dataSource; + } + + handleFilter = (searchText, name, item) => { + if (item.isDivider) { + return this.dividersVisibility[item.divider]; } - return entriesArray.map(entry => ({ - text: entry, - value: ( - - ) - })); + if (item.first) { + this.dividersVisibility = {}; + } + + const { filter } = this.props; + const show = filter(searchText, name, item); + + // Show the related divider + if (show) { + this.dividersVisibility[item.divider] = true; + } + + return show; } onKeyDown = (event) => { From 69c0086ada5b55a0924662b301e0b1fefbb93f4b Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 19:14:29 +0100 Subject: [PATCH 18/44] Better Autocomplete Divider --- js/src/ui/Form/AutoComplete/autocomplete.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/js/src/ui/Form/AutoComplete/autocomplete.js b/js/src/ui/Form/AutoComplete/autocomplete.js index 0e2acc98b..f78ab41dc 100644 --- a/js/src/ui/Form/AutoComplete/autocomplete.js +++ b/js/src/ui/Form/AutoComplete/autocomplete.js @@ -16,11 +16,23 @@ import React, { Component, PropTypes } from 'react'; import keycode from 'keycode'; -import { MenuItem, AutoComplete as MUIAutoComplete } from 'material-ui'; -import Divider from 'material-ui/Divider'; +import { MenuItem, AutoComplete as MUIAutoComplete, Divider as MUIDivider } from 'material-ui'; import { PopoverAnimationVertical } from 'material-ui/Popover'; -import { isEqual, range } from 'lodash'; +import { isEqual } from 'lodash'; + +// Hack to prevent "Unknown prop `disableFocusRipple` on
tag" error +class Divider extends Component { + static muiName = MUIDivider.muiName; + + render () { + return ( +
+ +
+ ); + } +} export default class AutoComplete extends Component { static propTypes = { From 0f6681d3e819aa1498d28be4c24313c369754f02 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 19:15:45 +0100 Subject: [PATCH 19/44] Linting issue --- js/src/ui/Form/AutoComplete/autocomplete.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/ui/Form/AutoComplete/autocomplete.js b/js/src/ui/Form/AutoComplete/autocomplete.js index f78ab41dc..d11ae7cc5 100644 --- a/js/src/ui/Form/AutoComplete/autocomplete.js +++ b/js/src/ui/Form/AutoComplete/autocomplete.js @@ -28,7 +28,7 @@ class Divider extends Component { render () { return (
- +
); } From 08a47ea2d48c2de543658a50d74dab39caa10b3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sat, 10 Dec 2016 20:18:42 +0100 Subject: [PATCH 20/44] Allow modifications of gas when confirming in signer (#3798) --- rpc/src/v1/impls/signer.rs | 10 ++++++---- rpc/src/v1/tests/mocked/signer.rs | 4 ++-- rpc/src/v1/types/confirmations.rs | 12 +++++++++++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/rpc/src/v1/impls/signer.rs b/rpc/src/v1/impls/signer.rs index 66f46ba01..f13a3d037 100644 --- a/rpc/src/v1/impls/signer.rs +++ b/rpc/src/v1/impls/signer.rs @@ -89,11 +89,13 @@ impl Signer for SignerClient where C: MiningBlockC signer.peek(&id).map(|confirmation| { let mut payload = confirmation.payload.clone(); // Modify payload - match (&mut payload, modification.gas_price) { - (&mut ConfirmationPayload::SendTransaction(ref mut request), Some(gas_price)) => { + if let ConfirmationPayload::SendTransaction(ref mut request) = payload { + if let Some(gas_price) = modification.gas_price { request.gas_price = gas_price.into(); - }, - _ => {}, + } + if let Some(gas) = modification.gas { + request.gas = gas.into(); + } } // Execute let result = dispatch::execute(&*client, &*miner, &*accounts, payload, Some(pass)); diff --git a/rpc/src/v1/tests/mocked/signer.rs b/rpc/src/v1/tests/mocked/signer.rs index ea89e5876..c87abb7eb 100644 --- a/rpc/src/v1/tests/mocked/signer.rs +++ b/rpc/src/v1/tests/mocked/signer.rs @@ -183,7 +183,7 @@ fn should_confirm_transaction_and_dispatch() { let t = Transaction { nonce: U256::zero(), gas_price: U256::from(0x1000), - gas: U256::from(10_000_000), + gas: U256::from(0x50505), action: Action::Call(recipient), value: U256::from(0x1), data: vec![] @@ -198,7 +198,7 @@ fn should_confirm_transaction_and_dispatch() { let request = r#"{ "jsonrpc":"2.0", "method":"signer_confirmRequest", - "params":["0x1", {"gasPrice":"0x1000"}, "test"], + "params":["0x1", {"gasPrice":"0x1000","gas":"0x50505"}, "test"], "id":1 }"#; let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#; diff --git a/rpc/src/v1/types/confirmations.rs b/rpc/src/v1/types/confirmations.rs index d8cfa14d6..f69018422 100644 --- a/rpc/src/v1/types/confirmations.rs +++ b/rpc/src/v1/types/confirmations.rs @@ -142,6 +142,8 @@ pub struct TransactionModification { /// Modified gas price #[serde(rename="gasPrice")] pub gas_price: Option, + /// Modified gas + pub gas: Option, } /// Represents two possible return values. @@ -275,18 +277,26 @@ mod tests { let s1 = r#"{ "gasPrice":"0xba43b7400" }"#; - let s2 = r#"{}"#; + let s2 = r#"{"gas": "0x1233"}"#; + let s3 = r#"{}"#; // when let res1: TransactionModification = serde_json::from_str(s1).unwrap(); let res2: TransactionModification = serde_json::from_str(s2).unwrap(); + let res3: TransactionModification = serde_json::from_str(s3).unwrap(); // then assert_eq!(res1, TransactionModification { gas_price: Some(U256::from_str("0ba43b7400").unwrap()), + gas: None, }); assert_eq!(res2, TransactionModification { gas_price: None, + gas: Some(U256::from_str("1233").unwrap()), + }); + assert_eq!(res3, TransactionModification { + gas_price: None, + gas: None, }); } } From 70eab0da0331c7867c619aa12a6f6a86831aea3f Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 20:29:22 +0100 Subject: [PATCH 21/44] PR grumbles --- .../DeployContract/DetailsStep/detailsStep.js | 22 +++++++++---------- .../DetailsStep/detailsStep.js | 6 ++--- js/src/ui/Form/AddressSelect/addressSelect.js | 3 +-- js/src/views/Addresses/addresses.js | 2 +- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/js/src/modals/DeployContract/DetailsStep/detailsStep.js b/js/src/modals/DeployContract/DetailsStep/detailsStep.js index 9db223793..3de7a8a44 100644 --- a/js/src/modals/DeployContract/DetailsStep/detailsStep.js +++ b/js/src/modals/DeployContract/DetailsStep/detailsStep.js @@ -28,27 +28,25 @@ export default class DetailsStep extends Component { static propTypes = { accounts: PropTypes.object.isRequired, - - onFromAddressChange: PropTypes.func.isRequired, - onNameChange: PropTypes.func.isRequired, - onDescriptionChange: PropTypes.func.isRequired, onAbiChange: PropTypes.func.isRequired, onCodeChange: PropTypes.func.isRequired, - onParamsChange: PropTypes.func.isRequired, + onDescriptionChange: PropTypes.func.isRequired, + onFromAddressChange: PropTypes.func.isRequired, onInputsChange: PropTypes.func.isRequired, + onNameChange: PropTypes.func.isRequired, + onParamsChange: PropTypes.func.isRequired, + abi: PropTypes.string, + abiError: PropTypes.string, balances: PropTypes.object, + code: PropTypes.string, + codeError: PropTypes.string, + description: PropTypes.string, + descriptionError: PropTypes.string, fromAddress: PropTypes.string, fromAddressError: PropTypes.string, name: PropTypes.string, nameError: PropTypes.string, - description: PropTypes.string, - descriptionError: PropTypes.string, - abi: PropTypes.string, - abiError: PropTypes.string, - code: PropTypes.string, - codeError: PropTypes.string, - readOnly: PropTypes.bool }; diff --git a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js index 7bbe7be84..fde7fa1b2 100644 --- a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js +++ b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js @@ -32,20 +32,20 @@ export default class DetailsStep extends Component { static propTypes = { accounts: PropTypes.object.isRequired, contract: PropTypes.object.isRequired, - amount: PropTypes.string, - amountError: PropTypes.string, onAmountChange: PropTypes.func.isRequired, onFromAddressChange: PropTypes.func.isRequired, onValueChange: PropTypes.func.isRequired, values: PropTypes.array.isRequired, valuesError: PropTypes.array.isRequired, + amount: PropTypes.string, + amountError: PropTypes.string, balances: PropTypes.object, fromAddress: PropTypes.string, fromAddressError: PropTypes.string, - gasEdit: PropTypes.bool, func: PropTypes.object, funcError: PropTypes.string, + gasEdit: PropTypes.bool, onFuncChange: PropTypes.func, onGasEditClick: PropTypes.func, warning: PropTypes.string diff --git a/js/src/ui/Form/AddressSelect/addressSelect.js b/js/src/ui/Form/AddressSelect/addressSelect.js index 4bd93caa9..0cd92c5c8 100644 --- a/js/src/ui/Form/AddressSelect/addressSelect.js +++ b/js/src/ui/Form/AddressSelect/addressSelect.js @@ -158,7 +158,7 @@ export default class AddressSelect extends Component { return null; } - const ethToken = balance.tokens.find((t) => t.token.tag.toLowerCase() === 'eth'); + const ethToken = balance.tokens.find((tok) => tok.token && tok.token.tag && tok.token.tag.toLowerCase() === 'eth'); if (!ethToken) { return null; @@ -187,7 +187,6 @@ export default class AddressSelect extends Component { - { balance }
); diff --git a/js/src/views/Addresses/addresses.js b/js/src/views/Addresses/addresses.js index fd26d94e5..609f029c7 100644 --- a/js/src/views/Addresses/addresses.js +++ b/js/src/views/Addresses/addresses.js @@ -89,7 +89,7 @@ class Addresses extends Component { if (hasContacts && Object.keys(balances).length === 0) { return ( - + ); } From 7401358543f8e4b18f55cebc4cc1d73ae5337ab6 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 22:15:56 +0100 Subject: [PATCH 22/44] PR grumbles --- .../DeployContract/DetailsStep/detailsStep.js | 23 +++++++++++-------- js/src/views/Contracts/contracts.js | 2 +- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/js/src/modals/DeployContract/DetailsStep/detailsStep.js b/js/src/modals/DeployContract/DetailsStep/detailsStep.js index 3de7a8a44..51c5d3cfb 100644 --- a/js/src/modals/DeployContract/DetailsStep/detailsStep.js +++ b/js/src/modals/DeployContract/DetailsStep/detailsStep.js @@ -94,25 +94,28 @@ export default class DetailsStep extends Component { + error={ fromAddressError } + onChange={ this.onFromAddressChange } + value={ fromAddress } + /> + /> + /> { this.renderContractSelect() } @@ -120,17 +123,19 @@ export default class DetailsStep extends Component { label='abi / solc combined-output' hint='the abi of the contract to deploy or solc combined-output' error={ abiError } - value={ solcOutput } onChange={ this.onSolcChange } onSubmit={ this.onSolcSubmit } - readOnly={ readOnly } /> + readOnly={ readOnly } + value={ solcOutput } + /> + readOnly={ readOnly || solc } + value={ code } + /> ); diff --git a/js/src/views/Contracts/contracts.js b/js/src/views/Contracts/contracts.js index 524954f80..7b74654da 100644 --- a/js/src/views/Contracts/contracts.js +++ b/js/src/views/Contracts/contracts.js @@ -147,7 +147,7 @@ class Contracts extends Component { >