From 5a6e2f89ddc46b0f09560e2b8322207404a16f69 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Mon, 14 Nov 2016 17:02:45 +0100 Subject: [PATCH 01/41] Hide external apps by default --- js/src/views/Dapps/AddDapps/AddDapps.js | 5 +- js/src/views/Dapps/Summary/summary.js | 3 +- js/src/views/Dapps/builtin.json | 13 ++- js/src/views/Dapps/dapps.js | 2 +- js/src/views/Dapps/dappsStore.js | 114 ++++++++++++------------ 5 files changed, 70 insertions(+), 67 deletions(-) diff --git a/js/src/views/Dapps/AddDapps/AddDapps.js b/js/src/views/Dapps/AddDapps/AddDapps.js index 38bb64792..49d3c5e8d 100644 --- a/js/src/views/Dapps/AddDapps/AddDapps.js +++ b/js/src/views/Dapps/AddDapps/AddDapps.js @@ -52,7 +52,7 @@ export default class AddDapps extends Component { visible scroll> - { store.apps.map(this.renderApp) } + { store.sortedApps.map(this.renderApp) } ); @@ -60,7 +60,8 @@ export default class AddDapps extends Component { renderApp = (app) => { const { store } = this.props; - const isHidden = store.hidden.includes(app.id); + const isHidden = !store.displayApps[app.id].visible; + const onCheck = () => { if (isHidden) { store.showApp(app.id); diff --git a/js/src/views/Dapps/Summary/summary.js b/js/src/views/Dapps/Summary/summary.js index 912f1495e..b0963a560 100644 --- a/js/src/views/Dapps/Summary/summary.js +++ b/js/src/views/Dapps/Summary/summary.js @@ -17,7 +17,7 @@ import React, { Component, PropTypes } from 'react'; import { Link } from 'react-router'; -import { Container, ContainerTitle } from '../../../ui'; +import { Container, ContainerTitle, Tags } from '../../../ui'; import styles from './summary.css'; @@ -49,6 +49,7 @@ export default class Summary extends Component { return ( { image } +
", - "version": "1.0.0" + "version": "1.0.0", + "visible": true }, { "id": "0xd1adaede68d344519025e2ff574650cd99d3830fe6d274c7a7843cdc00e17938", @@ -13,7 +14,8 @@ "name": "Registry", "description": "A global registry of addresses on the network", "author": "Parity Team ", - "version": "1.0.0" + "version": "1.0.0", + "visible": true }, { "id": "0x0a8048117e51e964628d0f2d26342b3cd915248b59bcce2721e1d05f5cfa2208", @@ -21,7 +23,8 @@ "name": "Token Registry", "description": "A registry of transactable tokens on the network", "author": "Parity Team ", - "version": "1.0.0" + "version": "1.0.0", + "visible": true }, { "id": "0xf49089046f53f5d2e5f3513c1c32f5ff57d986e46309a42d2b249070e4e72c46", @@ -29,7 +32,8 @@ "name": "Method Registry", "description": "A registry of method signatures for lookups on transactions", "author": "Parity Team ", - "version": "1.0.0" + "version": "1.0.0", + "visible": true }, { "id": "0x058740ee9a5a3fb9f1cfa10752baec87e09cc45cd7027fd54708271aca300c75", @@ -38,6 +42,7 @@ "description": "A mapping of GitHub URLs to hashes for use in contracts as references", "author": "Parity Team ", "version": "1.0.0", + "visible": false, "secure": true } ] diff --git a/js/src/views/Dapps/dapps.js b/js/src/views/Dapps/dapps.js index 708c52cb5..5d9877d73 100644 --- a/js/src/views/Dapps/dapps.js +++ b/js/src/views/Dapps/dapps.js @@ -54,7 +54,7 @@ export default class Dapps extends Component { />
- { this.store.visible.map(this.renderApp) } + { this.store.visibleApps.map(this.renderApp) }
diff --git a/js/src/views/Dapps/dappsStore.js b/js/src/views/Dapps/dappsStore.js index 599dc18e9..78be6d9fb 100644 --- a/js/src/views/Dapps/dappsStore.js +++ b/js/src/views/Dapps/dappsStore.js @@ -14,39 +14,42 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import BigNumber from 'bignumber.js'; import { action, computed, observable, transaction } from 'mobx'; +import store from 'store'; import Contracts from '../../contracts'; import { hashToImageUrl } from '../../redux/util'; import builtinApps from './builtin.json'; -const LS_KEY_HIDDEN = 'hiddenApps'; -const LS_KEY_EXTERNAL = 'externalApps'; +const LS_KEY_DISPLAY = 'displayApps'; export default class DappsStore { @observable apps = []; - @observable externalApps = []; - @observable hiddenApps = []; + @observable displayApps = {}; @observable modalOpen = false; constructor (api) { this._api = api; - this._readHiddenApps(); - this._readExternalApps(); + this._readDisplayApps(); - this._fetchBuiltinApps(); - this._fetchLocalApps(); - this._fetchRegistryApps(); + Promise + .all([ + this._fetchBuiltinApps(), + this._fetchLocalApps(), + this._fetchRegistryApps() + ]) + .then(this._writeDisplayApps); } - @computed get visible () { - return this.apps - .filter((app) => { - return this.externalApps.includes(app.id) || !this.hiddenApps.includes(app.id); - }) - .sort((a, b) => a.name.localeCompare(b.name)); + @computed get sortedApps () { + return this.apps.sort((a, b) => a.name.localeCompare(b.name)); + } + + @computed get visibleApps () { + return this.sortedApps.filter((app) => this.displayApps[app.id] && this.displayApps[app.id].visible); } @action openModal = () => { @@ -58,13 +61,33 @@ export default class DappsStore { } @action hideApp = (id) => { - this.hiddenApps = this.hiddenApps.concat(id); - this._writeHiddenApps(); + const app = this.displayApps[id] || {}; + app.visible = false; + + this.displayApps[id] = app; + this._writeDisplayApps(); } @action showApp = (id) => { - this.hiddenApps = this.hiddenApps.filter((_id) => _id !== id); - this._writeHiddenApps(); + const app = this.displayApps[id] || {}; + app.visible = true; + + this.displayApps[id] = app; + this._writeDisplayApps(); + } + + @action setDisplayApps = (apps) => { + this.displayApps = apps; + } + + @action addApp = (app, visible) => { + transaction(() => { + this.apps.push(app); + + if (!this.displayApps[app.id]) { + this.displayApps[app.id] = { visible }; + } + }); } _getHost (api) { @@ -83,9 +106,12 @@ export default class DappsStore { builtinApps.forEach((app, index) => { app.type = 'builtin'; app.image = hashToImageUrl(imageIds[index]); - this.apps.push(app); + this.addApp(app, app.visible); }); }); + }) + .catch((error) => { + console.warn('DappsStore:fetchBuiltinApps', error); }); } @@ -106,7 +132,7 @@ export default class DappsStore { }) .then((apps) => { transaction(() => { - (apps || []).forEach((app) => this.apps.push(app)); + (apps || []).forEach((app) => this.addApp(app, true)); }); }) .catch((error) => { @@ -132,7 +158,9 @@ export default class DappsStore { .then((appsInfo) => { const appIds = appsInfo .map(([appId, owner]) => this._api.util.bytesToHex(appId)) - .filter((appId) => !builtinApps.find((app) => app.id === appId)); + .filter((appId) => { + return (new BigNumber(appId)).gt(0) && !builtinApps.find((app) => app.id === appId); + }); return Promise .all([ @@ -181,7 +209,7 @@ export default class DappsStore { }) .then((apps) => { transaction(() => { - (apps || []).forEach((app) => this.apps.push(app)); + (apps || []).forEach((app) => this.addApp(app, false)); }); }) .catch((error) => { @@ -202,43 +230,11 @@ export default class DappsStore { }); } - _readHiddenApps () { - const stored = localStorage.getItem(LS_KEY_HIDDEN); - - if (stored) { - try { - this.hiddenApps = JSON.parse(stored); - } catch (error) { - console.warn('DappsStore:readHiddenApps', error); - } - } + _readDisplayApps = () => { + this.setDisplayApps(store.get(LS_KEY_DISPLAY) || {}); } - _readExternalApps () { - const stored = localStorage.getItem(LS_KEY_EXTERNAL); - - if (stored) { - try { - this.externalApps = JSON.parse(stored); - } catch (error) { - console.warn('DappsStore:readExternalApps', error); - } - } - } - - _writeExternalApps () { - try { - localStorage.setItem(LS_KEY_EXTERNAL, JSON.stringify(this.externalApps)); - } catch (error) { - console.error('DappsStore:writeExternalApps', error); - } - } - - _writeHiddenApps () { - try { - localStorage.setItem(LS_KEY_HIDDEN, JSON.stringify(this.hiddenApps)); - } catch (error) { - console.error('DappsStore:writeHiddenApps', error); - } + _writeDisplayApps = () => { + store.set(LS_KEY_DISPLAY, this.displayApps); } } From 1e155ae5484df4a360b337bf50024712ca3c93be Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Mon, 14 Nov 2016 21:57:28 +0100 Subject: [PATCH 02/41] Render selection inside areas --- js/src/views/Dapps/AddDapps/AddDapps.css | 19 +++++ js/src/views/Dapps/AddDapps/AddDapps.js | 26 ++++++- js/src/views/Dapps/dappsStore.js | 91 ++++++++++++------------ 3 files changed, 87 insertions(+), 49 deletions(-) diff --git a/js/src/views/Dapps/AddDapps/AddDapps.css b/js/src/views/Dapps/AddDapps/AddDapps.css index bff292322..be4438196 100644 --- a/js/src/views/Dapps/AddDapps/AddDapps.css +++ b/js/src/views/Dapps/AddDapps/AddDapps.css @@ -18,3 +18,22 @@ .description { margin-top: .5em !important; } + +.list { + .background { + background: rgba(255, 255, 255, 0.2); + margin: 0 -1.5em; + padding: 0.5em 1.5em; + } + + .header { + text-transform: uppercase; + } + + .byline { + font-size: 0.75em; + padding-top: 0.5em; + line-height: 1.5em; + opacity: 0.75; + } +} diff --git a/js/src/views/Dapps/AddDapps/AddDapps.js b/js/src/views/Dapps/AddDapps/AddDapps.js index 49d3c5e8d..4ff4cf42c 100644 --- a/js/src/views/Dapps/AddDapps/AddDapps.js +++ b/js/src/views/Dapps/AddDapps/AddDapps.js @@ -51,13 +51,33 @@ export default class AddDapps extends Component { ] } visible scroll> - - { store.sortedApps.map(this.renderApp) } - +
+
+ { this.renderList(store.sortedLocal, 'Applications locally available', 'All applications installed locally on the machine by the user for access by the Parity client.') } + { this.renderList(store.sortedBuiltin, 'Applications bundled with Parity', 'Experimental applications developed by the Parity team to show off dapp capabilities, integration, experimental features and to control certain network-wide client behaviour.') } + { this.renderList(store.sortedNetwork, 'Applications on the global network', 'These applications are not affiliated with Parity nor are they published by Partity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each application before interacting.') } ); } + renderList (items, header, byline) { + if (!items || !items.length) { + return null; + } + + return ( +
+
+
{ header }
+
{ byline }
+
+ + { items.map(this.renderApp) } + +
+ ); + } + renderApp = (app) => { const { store } = this.props; const isHidden = !store.displayApps[app.id].visible; diff --git a/js/src/views/Dapps/dappsStore.js b/js/src/views/Dapps/dappsStore.js index 78be6d9fb..a6f029b21 100644 --- a/js/src/views/Dapps/dappsStore.js +++ b/js/src/views/Dapps/dappsStore.js @@ -33,7 +33,7 @@ export default class DappsStore { constructor (api) { this._api = api; - this._readDisplayApps(); + this.readDisplayApps(); Promise .all([ @@ -41,15 +41,23 @@ export default class DappsStore { this._fetchLocalApps(), this._fetchRegistryApps() ]) - .then(this._writeDisplayApps); + .then(this.writeDisplayApps); } - @computed get sortedApps () { - return this.apps.sort((a, b) => a.name.localeCompare(b.name)); + @computed get sortedBuiltin () { + return this.apps.filter((app) => app.type === 'builtin'); + } + + @computed get sortedLocal () { + return this.apps.filter((app) => app.type === 'local'); + } + + @computed get sortedNetwork () { + return this.apps.filter((app) => app.type === 'network'); } @computed get visibleApps () { - return this.sortedApps.filter((app) => this.displayApps[app.id] && this.displayApps[app.id].visible); + return this.apps.filter((app) => this.displayApps[app.id] && this.displayApps[app.id].visible); } @action openModal = () => { @@ -61,32 +69,37 @@ export default class DappsStore { } @action hideApp = (id) => { - const app = this.displayApps[id] || {}; - app.visible = false; - - this.displayApps[id] = app; - this._writeDisplayApps(); + this.displayApps = Object.assign({}, this.displayApps, { [id]: { visible: false } }); + this.writeDisplayApps(); } @action showApp = (id) => { - const app = this.displayApps[id] || {}; - app.visible = true; - - this.displayApps[id] = app; - this._writeDisplayApps(); + this.displayApps = Object.assign({}, this.displayApps, { [id]: { visible: true } }); + this.writeDisplayApps(); } - @action setDisplayApps = (apps) => { - this.displayApps = apps; + @action readDisplayApps = () => { + this.displayApps = store.get(LS_KEY_DISPLAY) || {}; } - @action addApp = (app, visible) => { + @action writeDisplayApps = () => { + store.set(LS_KEY_DISPLAY, this.displayApps); + } + + @action addApps = (apps) => { transaction(() => { - this.apps.push(app); + this.apps = this.apps + .concat(apps || []) + .sort((a, b) => a.name.localeCompare(b.name)); - if (!this.displayApps[app.id]) { - this.displayApps[app.id] = { visible }; - } + const visibility = {}; + apps.forEach((app) => { + if (!this.displayApps[app.id]) { + visibility[app.id] = { visible: app.visible }; + } + }); + + this.displayApps = Object.assign({}, this.displayApps, visibility); }); } @@ -102,13 +115,13 @@ export default class DappsStore { return Promise .all(builtinApps.map((app) => dappReg.getImage(app.id))) .then((imageIds) => { - transaction(() => { - builtinApps.forEach((app, index) => { + this.addApps( + builtinApps.map((app, index) => { app.type = 'builtin'; app.image = hashToImageUrl(imageIds[index]); - this.addApp(app, app.visible); - }); - }); + return app; + }) + ); }) .catch((error) => { console.warn('DappsStore:fetchBuiltinApps', error); @@ -126,15 +139,12 @@ export default class DappsStore { return apps .map((app) => { app.type = 'local'; + app.visible = true; return app; }) .filter((app) => app.id && !['ui'].includes(app.id)); }) - .then((apps) => { - transaction(() => { - (apps || []).forEach((app) => this.addApp(app, true)); - }); - }) + .then(this.addApps) .catch((error) => { console.warn('DappsStore:fetchLocal', error); }); @@ -175,7 +185,8 @@ export default class DappsStore { image: hashToImageUrl(imageIds[index]), contentHash: this._api.util.bytesToHex(contentIds[index]).substr(2), manifestHash: this._api.util.bytesToHex(manifestIds[index]).substr(2), - type: 'network' + type: 'network', + visible: false }; return app; @@ -207,11 +218,7 @@ export default class DappsStore { }); }); }) - .then((apps) => { - transaction(() => { - (apps || []).forEach((app) => this.addApp(app, false)); - }); - }) + .then(this.addApps) .catch((error) => { console.warn('DappsStore:fetchRegistry', error); }); @@ -229,12 +236,4 @@ export default class DappsStore { return null; }); } - - _readDisplayApps = () => { - this.setDisplayApps(store.get(LS_KEY_DISPLAY) || {}); - } - - _writeDisplayApps = () => { - store.set(LS_KEY_DISPLAY, this.displayApps); - } } From 8dc7fcbe07cf20de00738770d73927f7a0b0a7c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 15 Nov 2016 11:20:54 +0100 Subject: [PATCH 03/41] Don't clear propagated transactions --- sync/src/chain.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 2f810e754..65fca4069 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -1850,7 +1850,7 @@ impl ChainSync { /// propagates new transactions to all peers pub fn propagate_new_transactions(&mut self, io: &mut SyncIo) -> usize { - // Early out of nobody to send to. + // Early out if nobody to send to. if self.peers.is_empty() { return 0; } @@ -1948,9 +1948,6 @@ impl ChainSync { trace!(target: "sync", "Bad blocks in the queue, restarting"); self.restart(io); } - for peer_info in self.peers.values_mut() { - peer_info.last_sent_transactions.clear(); - } } } From 9bd6378e42fc23411724ecef10d0b08d0bd8d54d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 16 Nov 2016 11:37:25 +0800 Subject: [PATCH 04/41] Typo --- js/src/views/Dapps/AddDapps/AddDapps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/views/Dapps/AddDapps/AddDapps.js b/js/src/views/Dapps/AddDapps/AddDapps.js index 4ff4cf42c..72b24bd2b 100644 --- a/js/src/views/Dapps/AddDapps/AddDapps.js +++ b/js/src/views/Dapps/AddDapps/AddDapps.js @@ -55,7 +55,7 @@ export default class AddDapps extends Component { { this.renderList(store.sortedLocal, 'Applications locally available', 'All applications installed locally on the machine by the user for access by the Parity client.') } { this.renderList(store.sortedBuiltin, 'Applications bundled with Parity', 'Experimental applications developed by the Parity team to show off dapp capabilities, integration, experimental features and to control certain network-wide client behaviour.') } - { this.renderList(store.sortedNetwork, 'Applications on the global network', 'These applications are not affiliated with Parity nor are they published by Partity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each application before interacting.') } + { this.renderList(store.sortedNetwork, 'Applications on the global network', '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.') } ); } From 4febd0eb934f09267d5d89f040a39d160d997804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 16 Nov 2016 10:45:55 +0100 Subject: [PATCH 05/41] Maintaining the statistics for propagation of pending transactions --- ethcore/src/client/client.rs | 4 +- ethcore/src/miner/mod.rs | 12 ++-- sync/src/chain.rs | 86 ++++++++++++++++---------- sync/src/lib.rs | 1 + sync/src/transactions_stats.rs | 109 +++++++++++++++++++++++++++++++++ 5 files changed, 172 insertions(+), 40 deletions(-) create mode 100644 sync/src/transactions_stats.rs diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index ec59e01cf..151d7e10e 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -262,7 +262,7 @@ impl Client { } } - /// Register an action to be done if a mode change happens. + /// Register an action to be done if a mode change happens. pub fn on_mode_change(&self, f: F) where F: 'static + FnMut(&Mode) + Send { *self.on_mode_change.lock() = Some(Box::new(f)); } @@ -890,7 +890,7 @@ impl BlockChainClient for Client { trace!(target: "mode", "Making callback..."); f(&*mode) }, - _ => {} + _ => {} } } match new_mode { diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index da93dc0b7..2eb09cdf1 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -41,16 +41,16 @@ //! } //! ``` -mod miner; -mod external; -mod transaction_queue; mod banning_queue; -mod work_notify; +mod external; +mod miner; mod price_info; +mod transaction_queue; +mod work_notify; -pub use self::transaction_queue::{TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin}; -pub use self::miner::{Miner, MinerOptions, Banning, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit}; pub use self::external::{ExternalMiner, ExternalMinerService}; +pub use self::miner::{Miner, MinerOptions, Banning, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit}; +pub use self::transaction_queue::{TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin}; pub use client::TransactionImportResult; use std::collections::BTreeMap; diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 65fca4069..6f2605b27 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -102,6 +102,7 @@ use block_sync::{BlockDownloader, BlockRequest, BlockDownloaderImportError as Do use snapshot::{Snapshot, ChunkType}; use rand::{thread_rng, Rng}; use api::{PeerInfo as PeerInfoDigest, WARP_SYNC_PROTOCOL_ID}; +use transactions_stats::TransactionsStats; known_heap_size!(0, PeerInfo); @@ -257,7 +258,7 @@ enum ForkConfirmation { Unconfirmed, /// Peers chain is too short to confirm the fork. TooShort, - /// Fork is confurmed. + /// Fork is confirmed. Confirmed, } @@ -338,6 +339,8 @@ pub struct ChainSync { handshaking_peers: HashMap, /// Sync start timestamp. Measured when first peer is connected sync_start_time: Option, + /// Transactions propagation statistics + transactions_stats: TransactionsStats, } type RlpResponseResult = Result, PacketDecodeError>; @@ -360,6 +363,7 @@ impl ChainSync { fork_block: config.fork_block, snapshot: Snapshot::new(), sync_start_time: None, + transactions_stats: TransactionsStats::default(), }; sync.update_targets(chain); sync @@ -1867,38 +1871,52 @@ impl ChainSync { packet.out() }; + // Clear old transactions from stats + self.transactions_stats.retain(&all_transactions_hashes); + // sqrt(x)/x scaled to max u32 let fraction = (self.peers.len() as f64).powf(-0.5).mul(u32::max_value() as f64).round() as u32; let small = self.peers.len() < MIN_PEERS_PROPAGATION; - let lucky_peers = self.peers.iter_mut() - .filter(|_| small || ::rand::random::() < fraction) - .take(MAX_PEERS_PROPAGATION) - .filter_map(|(peer_id, mut peer_info)| { - // Send all transactions - if peer_info.last_sent_transactions.is_empty() { - peer_info.last_sent_transactions = all_transactions_hashes.clone(); - return Some((*peer_id, all_transactions_rlp.clone())); - } - - // Get hashes of all transactions to send to this peer - let to_send = all_transactions_hashes.difference(&peer_info.last_sent_transactions).cloned().collect::>(); - if to_send.is_empty() { - return None; - } - - // Construct RLP - let mut packet = RlpStream::new_list(to_send.len()); - for tx in &transactions { - if to_send.contains(&tx.hash()) { - packet.append(tx); + let lucky_peers = { + let stats = &mut self.transactions_stats; + self.peers.iter_mut() + .filter(|_| small || ::rand::random::() < fraction) + .take(MAX_PEERS_PROPAGATION) + .filter_map(|(peer_id, mut peer_info)| { + // Send all transactions + if peer_info.last_sent_transactions.is_empty() { + // update stats + for hash in &all_transactions_hashes { + let id = io.peer_session_info(*peer_id).and_then(|info| info.id); + stats.propagated(*hash, id); + } + peer_info.last_sent_transactions = all_transactions_hashes.clone(); + return Some((*peer_id, all_transactions_rlp.clone())); } - } - peer_info.last_sent_transactions = all_transactions_hashes.clone(); - Some((*peer_id, packet.out())) - }) - .collect::>(); + // Get hashes of all transactions to send to this peer + let to_send = all_transactions_hashes.difference(&peer_info.last_sent_transactions).cloned().collect::>(); + if to_send.is_empty() { + return None; + } + + // Construct RLP + let mut packet = RlpStream::new_list(to_send.len()); + for tx in &transactions { + if to_send.contains(&tx.hash()) { + packet.append(tx); + // update stats + let peer_id = io.peer_session_info(*peer_id).and_then(|info| info.id); + stats.propagated(tx.hash(), peer_id); + } + } + + peer_info.last_sent_transactions = all_transactions_hashes.clone(); + Some((*peer_id, packet.out())) + }) + .collect::>() + }; // Send RLPs let sent = lucky_peers.len(); @@ -2273,18 +2291,23 @@ mod tests { let peer_count = sync.propagate_new_transactions(&mut io); // Try to propagate same transactions for the second time let peer_count2 = sync.propagate_new_transactions(&mut io); + // Even after new block transactions should not be propagated twice + sync.chain_new_blocks(&mut io, &[], &[], &[], &[], &[]); + // Try to propagate same transactions for the third time + let peer_count3 = sync.propagate_new_transactions(&mut io); // 1 message should be send assert_eq!(1, io.queue.len()); // 1 peer should be updated but only once assert_eq!(1, peer_count); assert_eq!(0, peer_count2); + assert_eq!(0, peer_count3); // TRANSACTIONS_PACKET assert_eq!(0x02, io.queue[0].packet_id); } #[test] - fn propagates_transactions_again_after_new_block() { + fn propagates_new_transactions_after_new_block() { let mut client = TestBlockChainClient::new(); client.add_blocks(100, EachBlockWith::Uncle); client.insert_transaction_to_queue(); @@ -2293,15 +2316,14 @@ mod tests { let ss = TestSnapshotService::new(); let mut io = TestIo::new(&mut client, &ss, &mut queue, None); let peer_count = sync.propagate_new_transactions(&mut io); + io.chain.insert_transaction_to_queue(); + // New block import should trigger propagation. sync.chain_new_blocks(&mut io, &[], &[], &[], &[], &[]); - // Try to propagate same transactions for the second time - let peer_count2 = sync.propagate_new_transactions(&mut io); // 2 message should be send assert_eq!(2, io.queue.len()); - // 1 peer should be updated twice + // 1 peer should receive the message assert_eq!(1, peer_count); - assert_eq!(1, peer_count2); // TRANSACTIONS_PACKET assert_eq!(0x02, io.queue[0].packet_id); assert_eq!(0x02, io.queue[1].packet_id); diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 532c05711..25350d410 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -51,6 +51,7 @@ mod blocks; mod block_sync; mod sync_io; mod snapshot; +mod transactions_stats; #[cfg(test)] mod tests; diff --git a/sync/src/transactions_stats.rs b/sync/src/transactions_stats.rs new file mode 100644 index 000000000..ed0a2aedd --- /dev/null +++ b/sync/src/transactions_stats.rs @@ -0,0 +1,109 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! Transaction Stats + +use std::collections::{HashSet, HashMap}; +use util::{H256, H512}; +use util::hash::H256FastMap; + +type NodeId = H512; +type BlockNumber = u64; + +#[derive(Debug, Default, PartialEq)] +pub struct Stats { + first_seen: BlockNumber, + propagated_to: HashMap, +} + +#[derive(Debug, Default)] +pub struct TransactionsStats { + pending_transactions: H256FastMap, +} + +impl TransactionsStats { + /// Increases number of propagations to given `enodeid`. + pub fn propagated(&mut self, hash: H256, enode_id: Option) { + let enode_id = enode_id.unwrap_or_default(); + let mut stats = self.pending_transactions.entry(hash).or_insert_with(|| Stats::default()); + let mut count = stats.propagated_to.entry(enode_id).or_insert(0); + *count = count.saturating_add(1); + } + + /// Returns propagation stats for given hash or `None` if hash is not known. + pub fn stats(&self, hash: &H256) -> Option<&Stats> { + self.pending_transactions.get(hash) + } + + /// Retains only transactions present in given `HashSet`. + pub fn retain(&mut self, hashes: &HashSet) { + let to_remove = self.pending_transactions.keys() + .filter(|hash| !hashes.contains(hash)) + .cloned() + .collect::>(); + + for hash in to_remove { + self.pending_transactions.remove(&hash); + } + } +} + +#[cfg(test)] +mod tests { + + use std::collections::{HashMap, HashSet}; + use super::{Stats, TransactionsStats}; + + #[test] + fn should_keep_track_of_propagations() { + // given + let mut stats = TransactionsStats::default(); + let hash = 5.into(); + let enodeid1 = 2.into(); + let enodeid2 = 5.into(); + + // when + stats.propagated(hash, Some(enodeid1)); + stats.propagated(hash, Some(enodeid1)); + stats.propagated(hash, Some(enodeid2)); + + // then + let stats = stats.stats(&hash); + assert_eq!(stats, Some(&Stats { + first_seen: 0, + propagated_to: hash_map![ + enodeid1 => 2, + enodeid2 => 1 + ] + })); + } + + #[test] + fn should_remove_hash_from_tracking() { + // given + let mut stats = TransactionsStats::default(); + let hash = 5.into(); + let enodeid1 = 5.into(); + stats.propagated(hash, Some(enodeid1)); + + // when + stats.retain(&HashSet::new()); + + // then + let stats = stats.stats(&hash); + assert_eq!(stats, None); + } +} From 501c7369b0d53cd979150653b323f913d424961d Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 16 Nov 2016 12:27:16 +0100 Subject: [PATCH 06/41] External app overlay --- js/src/views/Dapps/dapps.css | 12 ++++++++++++ js/src/views/Dapps/dapps.js | 25 ++++++++++++++++++++++--- js/src/views/Dapps/dappsStore.js | 2 +- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/js/src/views/Dapps/dapps.css b/js/src/views/Dapps/dapps.css index 1a38af3cf..23b9d565c 100644 --- a/js/src/views/Dapps/dapps.css +++ b/js/src/views/Dapps/dapps.css @@ -18,6 +18,7 @@ display: flex; flex-wrap: wrap; margin: -0.125em; + position: relative; } .list+.list { @@ -29,3 +30,14 @@ flex: 0 1 50%; box-sizing: border-box; } + +.overlay { + background: rgba(255, 255, 255, 0.75); + bottom: 0; + left: -0.25em; + position: absolute; + right: -0.25em; + top: 0; + z-index: 100; + padding: 1em; +} diff --git a/js/src/views/Dapps/dapps.js b/js/src/views/Dapps/dapps.js index 5d9877d73..b34aee7ff 100644 --- a/js/src/views/Dapps/dapps.js +++ b/js/src/views/Dapps/dapps.js @@ -37,6 +37,12 @@ export default class Dapps extends Component { store = new DappsStore(this.context.api); render () { + const externalOverlay = ( +
+ dismiss me +
+ ); + return (
@@ -53,14 +59,27 @@ export default class Dapps extends Component { ] } /> -
- { this.store.visibleApps.map(this.renderApp) } -
+ { this.renderList(this.store.sortedLocal) } + { this.renderList(this.store.sortedBuiltin) } + { this.renderList(this.store.sortedNetwork, externalOverlay) }
); } + renderList (items, overlay) { + if (!items || !items.length) { + return null; + } + + return ( +
+ { overlay } + { items.map(this.renderApp) } +
+ ); + } + renderApp = (app) => { return (
Date: Wed, 16 Nov 2016 12:36:15 +0100 Subject: [PATCH 07/41] Update styling --- js/src/ui/Page/page.css | 2 +- js/src/views/Dapps/dapps.css | 6 +++--- js/src/views/Dapps/dapps.js | 6 +++--- js/src/views/Dapps/dappsStore.js | 12 ++++++++++++ 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/js/src/ui/Page/page.css b/js/src/ui/Page/page.css index 81f4e0e68..cdb9fc868 100644 --- a/js/src/ui/Page/page.css +++ b/js/src/ui/Page/page.css @@ -19,5 +19,5 @@ } .layout>div { - padding-bottom: 0.25em; + padding-bottom: 0.75em; } diff --git a/js/src/views/Dapps/dapps.css b/js/src/views/Dapps/dapps.css index 23b9d565c..64c6b2cb7 100644 --- a/js/src/views/Dapps/dapps.css +++ b/js/src/views/Dapps/dapps.css @@ -32,12 +32,12 @@ } .overlay { - background: rgba(255, 255, 255, 0.75); - bottom: 0; + background: rgba(50, 50, 50, 0.85); + bottom: 0.5em; left: -0.25em; position: absolute; right: -0.25em; - top: 0; + top: -0.25em; z-index: 100; padding: 1em; } diff --git a/js/src/views/Dapps/dapps.js b/js/src/views/Dapps/dapps.js index b34aee7ff..c8d67ae9d 100644 --- a/js/src/views/Dapps/dapps.js +++ b/js/src/views/Dapps/dapps.js @@ -59,9 +59,9 @@ export default class Dapps extends Component { ] } /> - { this.renderList(this.store.sortedLocal) } - { this.renderList(this.store.sortedBuiltin) } - { this.renderList(this.store.sortedNetwork, externalOverlay) } + { this.renderList(this.store.visibleLocal) } + { this.renderList(this.store.visibleBuiltin) } + { this.renderList(this.store.visibleNetwork, externalOverlay) }
); diff --git a/js/src/views/Dapps/dappsStore.js b/js/src/views/Dapps/dappsStore.js index fa76eca7c..18368fcd4 100644 --- a/js/src/views/Dapps/dappsStore.js +++ b/js/src/views/Dapps/dappsStore.js @@ -60,6 +60,18 @@ export default class DappsStore { return this.apps.filter((app) => this.displayApps[app.id] && this.displayApps[app.id].visible); } + @computed get visibleBuiltin () { + return this.visibleApps.filter((app) => app.type === 'builtin'); + } + + @computed get visibleLocal () { + return this.visibleApps.filter((app) => app.type === 'local'); + } + + @computed get visibleNetwork () { + return this.visibleApps.filter((app) => app.type === 'network'); + } + @action openModal = () => { this.modalOpen = true; } From bbf6d1768d3bfe3648713b7c14b9843f1726f9d3 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 16 Nov 2016 13:08:08 +0100 Subject: [PATCH 08/41] Overal + acceptance in-place --- js/src/views/Dapps/dapps.css | 13 ++++++++++++- js/src/views/Dapps/dapps.js | 27 ++++++++++++++++++++++----- js/src/views/Dapps/dappsStore.js | 12 ++++++++++++ 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/js/src/views/Dapps/dapps.css b/js/src/views/Dapps/dapps.css index 64c6b2cb7..ea460f0b5 100644 --- a/js/src/views/Dapps/dapps.css +++ b/js/src/views/Dapps/dapps.css @@ -32,7 +32,7 @@ } .overlay { - background: rgba(50, 50, 50, 0.85); + background: rgba(0, 0, 0, 0.85); bottom: 0.5em; left: -0.25em; position: absolute; @@ -40,4 +40,15 @@ top: -0.25em; z-index: 100; padding: 1em; + + .body { + line-height: 1.5em; + margin: 0 auto; + text-align: left; + max-width: 980px; + + &>div:first-child { + padding-bottom: 1em; + } + } } diff --git a/js/src/views/Dapps/dapps.js b/js/src/views/Dapps/dapps.js index c8d67ae9d..5d87b808d 100644 --- a/js/src/views/Dapps/dapps.js +++ b/js/src/views/Dapps/dapps.js @@ -15,6 +15,7 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { Checkbox } from 'material-ui'; import { observer } from 'mobx-react'; import { Actionbar, Page } from '../../ui'; @@ -37,11 +38,23 @@ export default class Dapps extends Component { store = new DappsStore(this.context.api); render () { - const externalOverlay = ( -
- dismiss me -
- ); + let externalOverlay = null; + if (this.store.externalOverlayVisible) { + externalOverlay = ( +
+
+
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.
+
+ +
+
+
+ ); + } return (
@@ -89,4 +102,8 @@ export default class Dapps extends Component {
); } + + onClickAcceptExternal = () => { + this.store.closeExternalOverlay(); + } } diff --git a/js/src/views/Dapps/dappsStore.js b/js/src/views/Dapps/dappsStore.js index 18368fcd4..f9fc4863c 100644 --- a/js/src/views/Dapps/dappsStore.js +++ b/js/src/views/Dapps/dappsStore.js @@ -24,15 +24,18 @@ import { hashToImageUrl } from '../../redux/util'; import builtinApps from './builtin.json'; const LS_KEY_DISPLAY = 'displayApps'; +const LS_KEY_EXTERNAL_ACCEPT = 'acceptExternal'; export default class DappsStore { @observable apps = []; @observable displayApps = {}; @observable modalOpen = false; + @observable externalOverlayVisible = true; constructor (api) { this._api = api; + this.loadExternalOverlay(); this.readDisplayApps(); Promise @@ -80,6 +83,15 @@ export default class DappsStore { this.modalOpen = false; } + @action closeExternalOverlay = () => { + this.externalOverlayVisible = false; + store.set(LS_KEY_EXTERNAL_ACCEPT, true); + } + + @action loadExternalOverlay () { + this.externalOverlayVisible = !(store.get(LS_KEY_EXTERNAL_ACCEPT) || false); + } + @action hideApp = (id) => { this.displayApps = Object.assign({}, this.displayApps, { [id]: { visible: false } }); this.writeDisplayApps(); From 78b5c743f65bdb8c8b57bcde7fd781bdfebe84ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 16 Nov 2016 13:37:21 +0100 Subject: [PATCH 09/41] Stats RPC --- ipc/rpc/src/binary.rs | 3 +- rpc/src/v1/impls/parity.rs | 12 +++++- rpc/src/v1/tests/helpers/sync_provider.rs | 26 ++++++++++-- rpc/src/v1/tests/mocked/parity.rs | 12 ++++++ rpc/src/v1/traits/parity.rs | 6 ++- rpc/src/v1/types/mod.rs.in | 2 +- rpc/src/v1/types/sync.rs | 46 ++++++++++++++++++-- sync/build.rs | 1 + sync/src/api.rs | 22 +++++++++- sync/src/chain.rs | 29 +++++++++++-- sync/src/lib.rs | 2 +- sync/src/transactions_stats.rs | 51 +++++++++++++++++------ 12 files changed, 180 insertions(+), 32 deletions(-) diff --git a/ipc/rpc/src/binary.rs b/ipc/rpc/src/binary.rs index 3908992d1..e974626d0 100644 --- a/ipc/rpc/src/binary.rs +++ b/ipc/rpc/src/binary.rs @@ -16,7 +16,7 @@ //! Binary representation of types -use util::{U256, U512, H256, H2048, Address}; +use util::{U256, U512, H256, H512, H2048, Address}; use std::mem; use std::collections::{VecDeque, BTreeMap}; use std::ops::Range; @@ -800,6 +800,7 @@ binary_fixed_size!(bool); binary_fixed_size!(U256); binary_fixed_size!(U512); binary_fixed_size!(H256); +binary_fixed_size!(H512); binary_fixed_size!(H2048); binary_fixed_size!(Address); binary_fixed_size!(BinHandshake); diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index 1b8ee9695..a952d54ec 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -34,7 +34,7 @@ use ethcore::account_provider::AccountProvider; use jsonrpc_core::Error; use v1::traits::Parity; -use v1::types::{Bytes, U256, H160, H256, H512, Peers, Transaction, RpcSettings, Histogram}; +use v1::types::{Bytes, U256, H160, H256, H512, Peers, Transaction, RpcSettings, Histogram, TransactionStats}; use v1::helpers::{errors, SigningQueue, SignerService, NetworkSettings}; use v1::helpers::dispatch::DEFAULT_MAC; @@ -259,6 +259,16 @@ impl Parity for ParityClient where Ok(take_weak!(self.miner).all_transactions().into_iter().map(Into::into).collect::>()) } + fn pending_transactions_stats(&self) -> Result, Error> { + try!(self.active()); + + let stats = take_weak!(self.sync).transactions_stats(); + Ok(stats.into_iter() + .map(|(hash, stats)| (hash.into(), stats.into())) + .collect() + ) + } + fn signer_port(&self) -> Result { try!(self.active()); diff --git a/rpc/src/v1/tests/helpers/sync_provider.rs b/rpc/src/v1/tests/helpers/sync_provider.rs index e0f811fc0..24be33417 100644 --- a/rpc/src/v1/tests/helpers/sync_provider.rs +++ b/rpc/src/v1/tests/helpers/sync_provider.rs @@ -16,8 +16,9 @@ //! Test implementation of SyncProvider. -use util::{RwLock}; -use ethsync::{SyncProvider, SyncStatus, SyncState, PeerInfo}; +use std::collections::BTreeMap; +use util::{H256, RwLock}; +use ethsync::{SyncProvider, SyncStatus, SyncState, PeerInfo, TransactionStats}; /// TestSyncProvider config. pub struct Config { @@ -74,7 +75,7 @@ impl SyncProvider for TestSyncProvider { PeerInfo { id: Some("node1".to_owned()), client_version: "Parity/1".to_owned(), - capabilities: vec!["eth/62".to_owned(), "eth/63".to_owned()], + capabilities: vec!["eth/62".to_owned(), "eth/63".to_owned()], remote_address: "127.0.0.1:7777".to_owned(), local_address: "127.0.0.1:8888".to_owned(), eth_version: 62, @@ -84,7 +85,7 @@ impl SyncProvider for TestSyncProvider { PeerInfo { id: None, client_version: "Parity/2".to_owned(), - capabilities: vec!["eth/63".to_owned(), "eth/64".to_owned()], + capabilities: vec!["eth/63".to_owned(), "eth/64".to_owned()], remote_address: "Handshake".to_owned(), local_address: "127.0.0.1:3333".to_owned(), eth_version: 64, @@ -97,5 +98,22 @@ impl SyncProvider for TestSyncProvider { fn enode(&self) -> Option { None } + + fn transactions_stats(&self) -> BTreeMap { + map![ + 1.into() => TransactionStats { + first_seen: 10, + propagated_to: map![ + 128.into() => 16 + ] + }, + 5.into() => TransactionStats { + first_seen: 16, + propagated_to: map![ + 16.into() => 1 + ] + } + ] + } } diff --git a/rpc/src/v1/tests/mocked/parity.rs b/rpc/src/v1/tests/mocked/parity.rs index b5c8187c7..8c06d4c7d 100644 --- a/rpc/src/v1/tests/mocked/parity.rs +++ b/rpc/src/v1/tests/mocked/parity.rs @@ -355,3 +355,15 @@ fn rpc_parity_next_nonce() { assert_eq!(io1.handle_request_sync(&request), Some(response1.to_owned())); assert_eq!(io2.handle_request_sync(&request), Some(response2.to_owned())); } + +#[test] +fn rpc_parity_transactions_stats() { + let deps = Dependencies::new(); + let io = deps.default_client(); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_pendingTransactionsStats", "params":[], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"0x0000000000000000000000000000000000000000000000000000000000000001":{"firstSeen":10,"propagatedTo":{"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080":16}},"0x0000000000000000000000000000000000000000000000000000000000000005":{"firstSeen":16,"propagatedTo":{"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010":1}}},"id":1}"#; + + assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); +} + diff --git a/rpc/src/v1/traits/parity.rs b/rpc/src/v1/traits/parity.rs index f8c219a89..946da8149 100644 --- a/rpc/src/v1/traits/parity.rs +++ b/rpc/src/v1/traits/parity.rs @@ -19,7 +19,7 @@ use jsonrpc_core::Error; use std::collections::BTreeMap; use v1::helpers::auto_args::Wrap; -use v1::types::{H160, H256, H512, U256, Bytes, Peers, Transaction, RpcSettings, Histogram}; +use v1::types::{H160, H256, H512, U256, Bytes, Peers, Transaction, RpcSettings, Histogram, TransactionStats}; build_rpc_trait! { /// Parity-specific rpc interface. @@ -115,6 +115,10 @@ build_rpc_trait! { #[rpc(name = "parity_pendingTransactions")] fn pending_transactions(&self) -> Result, Error>; + /// Returns propagation statistics on transactions pending in the queue. + #[rpc(name = "parity_pendingTransactionsStats")] + fn pending_transactions_stats(&self) -> Result, Error>; + /// Returns current Trusted Signer port or an error if signer is disabled. #[rpc(name = "parity_signerPort")] fn signer_port(&self) -> Result; diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index b1e4ae2c9..29e50aae5 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -43,7 +43,7 @@ pub use self::filter::{Filter, FilterChanges}; pub use self::hash::{H64, H160, H256, H512, H520, H2048}; pub use self::index::Index; pub use self::log::Log; -pub use self::sync::{SyncStatus, SyncInfo, Peers, PeerInfo, PeerNetworkInfo, PeerProtocolsInfo, PeerEthereumProtocolInfo}; +pub use self::sync::{SyncStatus, SyncInfo, Peers, PeerInfo, PeerNetworkInfo, PeerProtocolsInfo, PeerEthereumProtocolInfo, TransactionStats}; pub use self::transaction::Transaction; pub use self::transaction_request::TransactionRequest; pub use self::receipt::Receipt; diff --git a/rpc/src/v1/types/sync.rs b/rpc/src/v1/types/sync.rs index a0f61e799..6f8938be9 100644 --- a/rpc/src/v1/types/sync.rs +++ b/rpc/src/v1/types/sync.rs @@ -14,9 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use ethsync::PeerInfo as SyncPeerInfo; +use std::collections::BTreeMap; +use ethsync::{PeerInfo as SyncPeerInfo, TransactionStats as SyncTransactionStats}; use serde::{Serialize, Serializer}; -use v1::types::U256; +use v1::types::{U256, H512}; /// Sync info #[derive(Default, Debug, Serialize, PartialEq)] @@ -117,8 +118,19 @@ impl Serialize for SyncStatus { } } +/// Propagation statistics for pending transaction. +#[derive(Default, Debug, Serialize)] +pub struct TransactionStats { + /// Block no this transaction was first seen. + #[serde(rename="firstSeen")] + pub first_seen: u64, + /// Peers this transaction was propagated to with count. + #[serde(rename="propagatedTo")] + pub propagated_to: BTreeMap, +} + impl From for PeerInfo { - fn from(p: SyncPeerInfo) -> PeerInfo { + fn from(p: SyncPeerInfo) -> Self { PeerInfo { id: p.id, name: p.client_version, @@ -138,10 +150,23 @@ impl From for PeerInfo { } } +impl From for TransactionStats { + fn from(s: SyncTransactionStats) -> Self { + TransactionStats { + first_seen: s.first_seen, + propagated_to: s.propagated_to + .into_iter() + .map(|(id, count)| (id.into(), count)) + .collect() + } + } +} + #[cfg(test)] mod tests { use serde_json; - use super::{SyncInfo, SyncStatus, Peers}; + use std::collections::BTreeMap; + use super::{SyncInfo, SyncStatus, Peers, TransactionStats}; #[test] fn test_serialize_sync_info() { @@ -176,4 +201,17 @@ mod tests { let serialized = serde_json::to_string(&t).unwrap(); assert_eq!(serialized, r#"{"startingBlock":"0x0","currentBlock":"0x0","highestBlock":"0x0","warpChunksAmount":null,"warpChunksProcessed":null,"blockGap":["0x1","0x5"]}"#) } + + #[test] + fn test_serialize_transaction_stats() { + let stats = TransactionStats { + first_seen: 100, + propagated_to: map![ + 10.into() => 50 + ] + }; + + let serialized = serde_json::to_string(&stats).unwrap(); + assert_eq!(serialized, r#"{"firstSeen":100,"propagatedTo":{"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a":50}}"#) + } } diff --git a/sync/build.rs b/sync/build.rs index c465d5e34..db881e328 100644 --- a/sync/build.rs +++ b/sync/build.rs @@ -18,4 +18,5 @@ extern crate ethcore_ipc_codegen; fn main() { ethcore_ipc_codegen::derive_ipc_cond("src/api.rs", cfg!(feature="ipc")).unwrap(); + ethcore_ipc_codegen::derive_ipc_cond("src/transactions_stats.rs", cfg!(feature="ipc")).unwrap(); } diff --git a/sync/src/api.rs b/sync/src/api.rs index d9dbbd263..3191483e4 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -15,13 +15,13 @@ // along with Parity. If not, see . use std::sync::Arc; -use std::collections::HashMap; +use std::collections::{HashMap, BTreeMap}; use std::io; use util::Bytes; use network::{NetworkProtocolHandler, NetworkService, NetworkContext, PeerId, ProtocolId, NetworkConfiguration as BasicNetworkConfiguration, NonReservedPeerMode, NetworkError, AllowIP as NetworkAllowIP}; -use util::{U256, H256}; +use util::{U256, H256, H512}; use io::{TimerToken}; use ethcore::client::{BlockChainClient, ChainNotify}; use ethcore::snapshot::SnapshotService; @@ -76,6 +76,16 @@ pub trait SyncProvider: Send + Sync { /// Get the enode if available. fn enode(&self) -> Option; + + /// Returns propagation count for pending transactions. + fn transactions_stats(&self) -> BTreeMap; +} + +/// Transaction stats +#[derive(Debug, Binary)] +pub struct TransactionStats { + pub first_seen: u64, + pub propagated_to: BTreeMap, } /// Peer connection information @@ -150,6 +160,14 @@ impl SyncProvider for EthSync { fn enode(&self) -> Option { self.network.external_url() } + + fn transactions_stats(&self) -> BTreeMap { + let sync = self.handler.sync.read(); + sync.transactions_stats() + .iter() + .map(|(hash, stats)| (*hash, stats.into())) + .collect() + } } struct SyncProtocolHandler { diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 6f2605b27..d255949b9 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -102,7 +102,7 @@ use block_sync::{BlockDownloader, BlockRequest, BlockDownloaderImportError as Do use snapshot::{Snapshot, ChunkType}; use rand::{thread_rng, Rng}; use api::{PeerInfo as PeerInfoDigest, WARP_SYNC_PROTOCOL_ID}; -use transactions_stats::TransactionsStats; +use transactions_stats::{TransactionsStats, Stats as TransactionStats}; known_heap_size!(0, PeerInfo); @@ -412,6 +412,11 @@ impl ChainSync { .collect() } + /// Returns transactions propagation statistics + pub fn transactions_stats(&self) -> &H256FastMap { + self.transactions_stats.stats() + } + /// Abort all sync activity pub fn abort(&mut self, io: &mut SyncIo) { self.reset_and_continue(io); @@ -1877,6 +1882,7 @@ impl ChainSync { // sqrt(x)/x scaled to max u32 let fraction = (self.peers.len() as f64).powf(-0.5).mul(u32::max_value() as f64).round() as u32; let small = self.peers.len() < MIN_PEERS_PROPAGATION; + let block_number = io.chain().chain_info().best_block_number; let lucky_peers = { let stats = &mut self.transactions_stats; @@ -1889,7 +1895,7 @@ impl ChainSync { // update stats for hash in &all_transactions_hashes { let id = io.peer_session_info(*peer_id).and_then(|info| info.id); - stats.propagated(*hash, id); + stats.propagated(*hash, id, block_number); } peer_info.last_sent_transactions = all_transactions_hashes.clone(); return Some((*peer_id, all_transactions_rlp.clone())); @@ -1907,8 +1913,8 @@ impl ChainSync { if to_send.contains(&tx.hash()) { packet.append(tx); // update stats - let peer_id = io.peer_session_info(*peer_id).and_then(|info| info.id); - stats.propagated(tx.hash(), peer_id); + let id = io.peer_session_info(*peer_id).and_then(|info| info.id); + stats.propagated(tx.hash(), id, block_number); } } @@ -2362,6 +2368,21 @@ mod tests { assert_eq!(0x02, io.queue[1].packet_id); } + #[test] + fn should_maintain_transations_propagation_stats() { + let mut client = TestBlockChainClient::new(); + client.add_blocks(100, EachBlockWith::Uncle); + client.insert_transaction_to_queue(); + let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(1), &client); + let mut queue = VecDeque::new(); + let ss = TestSnapshotService::new(); + let mut io = TestIo::new(&mut client, &ss, &mut queue, None); + sync.propagate_new_transactions(&mut io); + + let stats = sync.transactions_stats(); + assert_eq!(stats.len(), 1, "Should maintain stats for single transaction.") + } + #[test] fn handles_peer_new_block_malformed() { let mut client = TestBlockChainClient::new(); diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 25350d410..2061e4e3a 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -62,7 +62,7 @@ mod api { } pub use api::{EthSync, SyncProvider, SyncClient, NetworkManagerClient, ManageNetwork, SyncConfig, - ServiceConfiguration, NetworkConfiguration, PeerInfo, AllowIP}; + ServiceConfiguration, NetworkConfiguration, PeerInfo, AllowIP, TransactionStats}; pub use chain::{SyncStatus, SyncState}; pub use network::{is_valid_node_url, NonReservedPeerMode, NetworkError}; diff --git a/sync/src/transactions_stats.rs b/sync/src/transactions_stats.rs index ed0a2aedd..8c5eb6dda 100644 --- a/sync/src/transactions_stats.rs +++ b/sync/src/transactions_stats.rs @@ -14,8 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//! Transaction Stats - +use api::TransactionStats; use std::collections::{HashSet, HashMap}; use util::{H256, H512}; use util::hash::H256FastMap; @@ -23,12 +22,33 @@ use util::hash::H256FastMap; type NodeId = H512; type BlockNumber = u64; -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct Stats { first_seen: BlockNumber, propagated_to: HashMap, } +impl Stats { + pub fn new(number: BlockNumber) -> Self { + Stats { + first_seen: number, + propagated_to: Default::default(), + } + } +} + +impl<'a> From<&'a Stats> for TransactionStats { + fn from(other: &'a Stats) -> Self { + TransactionStats { + first_seen: other.first_seen, + propagated_to: other.propagated_to + .iter() + .map(|(hash, size)| (*hash, *size)) + .collect(), + } + } +} + #[derive(Debug, Default)] pub struct TransactionsStats { pending_transactions: H256FastMap, @@ -36,18 +56,23 @@ pub struct TransactionsStats { impl TransactionsStats { /// Increases number of propagations to given `enodeid`. - pub fn propagated(&mut self, hash: H256, enode_id: Option) { + pub fn propagated(&mut self, hash: H256, enode_id: Option, current_block_num: BlockNumber) { let enode_id = enode_id.unwrap_or_default(); - let mut stats = self.pending_transactions.entry(hash).or_insert_with(|| Stats::default()); + let mut stats = self.pending_transactions.entry(hash).or_insert_with(|| Stats::new(current_block_num)); let mut count = stats.propagated_to.entry(enode_id).or_insert(0); *count = count.saturating_add(1); } /// Returns propagation stats for given hash or `None` if hash is not known. - pub fn stats(&self, hash: &H256) -> Option<&Stats> { + #[cfg(test)] + pub fn get(&self, hash: &H256) -> Option<&Stats> { self.pending_transactions.get(hash) } + pub fn stats(&self) -> &H256FastMap { + &self.pending_transactions + } + /// Retains only transactions present in given `HashSet`. pub fn retain(&mut self, hashes: &HashSet) { let to_remove = self.pending_transactions.keys() @@ -76,14 +101,14 @@ mod tests { let enodeid2 = 5.into(); // when - stats.propagated(hash, Some(enodeid1)); - stats.propagated(hash, Some(enodeid1)); - stats.propagated(hash, Some(enodeid2)); + stats.propagated(hash, Some(enodeid1), 5); + stats.propagated(hash, Some(enodeid1), 10); + stats.propagated(hash, Some(enodeid2), 15); // then - let stats = stats.stats(&hash); + let stats = stats.get(&hash); assert_eq!(stats, Some(&Stats { - first_seen: 0, + first_seen: 5, propagated_to: hash_map![ enodeid1 => 2, enodeid2 => 1 @@ -97,13 +122,13 @@ mod tests { let mut stats = TransactionsStats::default(); let hash = 5.into(); let enodeid1 = 5.into(); - stats.propagated(hash, Some(enodeid1)); + stats.propagated(hash, Some(enodeid1), 10); // when stats.retain(&HashSet::new()); // then - let stats = stats.stats(&hash); + let stats = stats.get(&hash); assert_eq!(stats, None); } } From 66e327dfcbad838a725b819a09c32d25c041567d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 16 Nov 2016 15:58:14 +0100 Subject: [PATCH 10/41] Keep track of local transactions --- Cargo.lock | 1 + ethcore/Cargo.toml | 1 + ethcore/src/lib.rs | 1 + ethcore/src/miner/local_transactions.rs | 193 ++++++++++++++++++++++++ ethcore/src/miner/mod.rs | 1 + ethcore/src/miner/transaction_queue.rs | 132 +++++++++++++--- 6 files changed, 309 insertions(+), 20 deletions(-) create mode 100644 ethcore/src/miner/local_transactions.rs diff --git a/Cargo.lock b/Cargo.lock index a93d2d551..94e754997 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -297,6 +297,7 @@ dependencies = [ "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.9.4 (git+https://github.com/ethcore/hyper)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "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)", "lru-cache 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index 667b40ace..48fce064a 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -27,6 +27,7 @@ time = "0.1" rand = "0.3" byteorder = "0.5" transient-hashmap = "0.1" +linked-hash-map = "0.3.0" evmjit = { path = "../evmjit", optional = true } clippy = { version = "0.0.96", optional = true} ethash = { path = "../ethash" } diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index bf3e59171..ca343b1a7 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -102,6 +102,7 @@ extern crate rlp; extern crate ethcore_bloom_journal as bloom_journal; extern crate byteorder; extern crate transient_hashmap; +extern crate linked_hash_map; #[macro_use] extern crate log; diff --git a/ethcore/src/miner/local_transactions.rs b/ethcore/src/miner/local_transactions.rs new file mode 100644 index 000000000..c9c869c33 --- /dev/null +++ b/ethcore/src/miner/local_transactions.rs @@ -0,0 +1,193 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! Local Transactions List. + +use linked_hash_map::LinkedHashMap; +use transaction::SignedTransaction; +use error::TransactionError; +use util::{U256, H256}; + +#[derive(Debug, PartialEq, Clone)] +pub enum Status { + /// The transaction is currently in the transaction queue. + Pending, + /// The transaction is in future part of the queue. + Future, + /// Transaction is already mined. + Mined(SignedTransaction), + /// Transaction is dropped because of limit + Dropped(SignedTransaction), + /// Replaced because of higher gas price of another transaction. + Replaced(SignedTransaction, U256, H256), + /// Transaction was never accepted to the queue. + Rejected(SignedTransaction, TransactionError), + /// Transaction is invalid. + Invalid(SignedTransaction), +} + +impl Status { + fn is_current(&self) -> bool { + *self == Status::Pending || *self == Status::Future + } +} + +/// Keeps track of local transactions that are in the queue or were mined/dropped recently. +#[derive(Debug)] +pub struct LocalTransactionsList { + max_old: usize, + transactions: LinkedHashMap, +} + +impl Default for LocalTransactionsList { + fn default() -> Self { + Self::new(10) + } +} + +impl LocalTransactionsList { + pub fn new(max_old: usize) -> Self { + LocalTransactionsList { + max_old: max_old, + transactions: Default::default(), + } + } + + pub fn mark_pending(&mut self, hash: H256) { + self.clear_old(); + self.transactions.insert(hash, Status::Pending); + } + + pub fn mark_future(&mut self, hash: H256) { + self.transactions.insert(hash, Status::Future); + self.clear_old(); + } + + pub fn mark_rejected(&mut self, tx: SignedTransaction, err: TransactionError) { + self.transactions.insert(tx.hash(), Status::Rejected(tx, err)); + self.clear_old(); + } + + pub fn mark_replaced(&mut self, tx: SignedTransaction, gas_price: U256, hash: H256) { + self.transactions.insert(tx.hash(), Status::Replaced(tx, gas_price, hash)); + self.clear_old(); + } + + pub fn mark_invalid(&mut self, tx: SignedTransaction) { + self.transactions.insert(tx.hash(), Status::Invalid(tx)); + self.clear_old(); + } + + pub fn mark_dropped(&mut self, tx: SignedTransaction) { + self.transactions.insert(tx.hash(), Status::Dropped(tx)); + self.clear_old(); + } + + pub fn mark_mined(&mut self, tx: SignedTransaction) { + self.transactions.insert(tx.hash(), Status::Mined(tx)); + self.clear_old(); + } + + pub fn contains(&self, hash: &H256) -> bool { + self.transactions.contains_key(hash) + } + + pub fn all_transactions(&self) -> &LinkedHashMap { + &self.transactions + } + + fn clear_old(&mut self) { + let number_of_old = self.transactions + .values() + .filter(|status| !status.is_current()) + .count(); + + if self.max_old >= number_of_old { + return; + } + + let to_remove = self.transactions + .iter() + .filter(|&(_, status)| !status.is_current()) + .map(|(hash, _)| *hash) + .take(number_of_old - self.max_old) + .collect::>(); + + for hash in to_remove { + self.transactions.remove(&hash); + } + } +} + +#[cfg(test)] +mod tests { + use util::U256; + use ethkey::{Random, Generator}; + use transaction::{Action, Transaction, SignedTransaction}; + use super::{LocalTransactionsList, Status}; + + #[test] + fn should_add_transaction_as_pending() { + // given + let mut list = LocalTransactionsList::default(); + + // when + list.mark_pending(10.into()); + list.mark_future(20.into()); + + // then + assert!(list.contains(&10.into()), "Should contain the transaction."); + assert!(list.contains(&20.into()), "Should contain the transaction."); + let statuses = list.all_transactions().values().cloned().collect::>(); + assert_eq!(statuses, vec![Status::Pending, Status::Future]); + } + + #[test] + fn should_clear_old_transactions() { + // given + let mut list = LocalTransactionsList::new(1); + let tx1 = new_tx(10.into()); + let tx1_hash = tx1.hash(); + let tx2 = new_tx(50.into()); + let tx2_hash = tx2.hash(); + + list.mark_pending(10.into()); + list.mark_invalid(tx1); + list.mark_dropped(tx2); + assert!(list.contains(&tx2_hash)); + assert!(!list.contains(&tx1_hash)); + assert!(list.contains(&10.into())); + + // when + list.mark_future(15.into()); + + // then + assert!(list.contains(&10.into())); + assert!(list.contains(&15.into())); + } + + fn new_tx(nonce: U256) -> SignedTransaction { + let keypair = Random.generate().unwrap(); + Transaction { + action: Action::Create, + value: U256::from(100), + data: Default::default(), + gas: U256::from(10), + gas_price: U256::from(1245), + nonce: nonce + }.sign(keypair.secret(), None) + } +} diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 2eb09cdf1..29d731e9e 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -43,6 +43,7 @@ mod banning_queue; mod external; +mod local_transactions; mod miner; mod price_info; mod transaction_queue; diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index cc10bbe98..9f688adf5 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -91,6 +91,7 @@ use util::table::Table; use transaction::*; use error::{Error, TransactionError}; use client::TransactionImportResult; +use miner::local_transactions::LocalTransactionsList; /// Transaction origin #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -125,6 +126,12 @@ impl Ord for TransactionOrigin { } } +impl TransactionOrigin { + fn is_local(&self) -> bool { + *self == TransactionOrigin::Local + } +} + #[derive(Clone, Debug)] /// Light structure used to identify transaction and its order struct TransactionOrder { @@ -352,7 +359,7 @@ impl TransactionSet { /// /// It drops transactions from this set but also removes associated `VerifiedTransaction`. /// Returns addresses and lowest nonces of transactions removed because of limit. - fn enforce_limit(&mut self, by_hash: &mut HashMap) -> Option> { + fn enforce_limit(&mut self, by_hash: &mut HashMap, local: &mut LocalTransactionsList) -> Option> { let mut count = 0; let mut gas: U256 = 0.into(); let to_drop : Vec<(Address, U256)> = { @@ -379,9 +386,13 @@ impl TransactionSet { .expect("Transaction has just been found in `by_priority`; so it is in `by_address` also."); trace!(target: "txqueue", "Dropped out of limit transaction: {:?}", order.hash); - by_hash.remove(&order.hash) + let order = by_hash.remove(&order.hash) .expect("hash is in `by_priorty`; all hashes in `by_priority` must be in `by_hash`; qed"); + if order.origin.is_local() { + local.mark_dropped(order.transaction); + } + let min = removed.get(&sender).map_or(nonce, |val| cmp::min(*val, nonce)); removed.insert(sender, min); removed @@ -488,6 +499,8 @@ pub struct TransactionQueue { by_hash: HashMap, /// Last nonce of transaction in current (to quickly check next expected transaction) last_nonces: HashMap, + /// List of local transactions and their statuses. + local_transactions: LocalTransactionsList, } impl Default for TransactionQueue { @@ -529,6 +542,7 @@ impl TransactionQueue { future: future, by_hash: HashMap::new(), last_nonces: HashMap::new(), + local_transactions: LocalTransactionsList::default(), } } @@ -537,8 +551,8 @@ impl TransactionQueue { self.current.set_limit(limit); self.future.set_limit(limit); // And ensure the limits - self.current.enforce_limit(&mut self.by_hash); - self.future.enforce_limit(&mut self.by_hash); + self.current.enforce_limit(&mut self.by_hash, &mut self.local_transactions); + self.future.enforce_limit(&mut self.by_hash, &mut self.local_transactions); } /// Returns current limit of transactions in the queue. @@ -578,7 +592,7 @@ impl TransactionQueue { pub fn set_total_gas_limit(&mut self, gas_limit: U256) { self.future.gas_limit = gas_limit; self.current.gas_limit = gas_limit; - self.future.enforce_limit(&mut self.by_hash); + self.future.enforce_limit(&mut self.by_hash, &mut self.local_transactions); } /// Set the new limit for the amount of gas any individual transaction may have. @@ -609,6 +623,42 @@ impl TransactionQueue { F: Fn(&Address) -> AccountDetails, G: Fn(&SignedTransaction) -> U256, { + if origin == TransactionOrigin::Local { + let hash = tx.hash(); + let cloned_tx = tx.clone(); + + let result = self.add_internal(tx, origin, fetch_account, gas_estimator); + match result { + Ok(TransactionImportResult::Current) => { + self.local_transactions.mark_pending(hash); + }, + Ok(TransactionImportResult::Future) => { + self.local_transactions.mark_future(hash); + }, + Err(Error::Transaction(ref err)) => { + self.local_transactions.mark_rejected(cloned_tx, err.clone()); + }, + Err(_) => { + self.local_transactions.mark_invalid(cloned_tx); + }, + } + result + } else { + self.add_internal(tx, origin, fetch_account, gas_estimator) + } + } + + /// Adds signed transaction to the queue. + fn add_internal( + &mut self, + tx: SignedTransaction, + origin: TransactionOrigin, + fetch_account: &F, + gas_estimator: &G, + ) -> Result where + F: Fn(&Address) -> AccountDetails, + G: Fn(&SignedTransaction) -> U256, + { if tx.gas_price < self.minimal_gas_price && origin != TransactionOrigin::Local { trace!(target: "txqueue", @@ -647,7 +697,6 @@ impl TransactionQueue { self.gas_limit, self.tx_gas_limit ); - return Err(Error::Transaction(TransactionError::GasLimitExceeded { limit: self.gas_limit, got: tx.gas, @@ -766,6 +815,11 @@ impl TransactionQueue { trace!(target: "txqueue", "Removing invalid transaction: {:?}", transaction.hash()); + // Mark in locals + if self.local_transactions.contains(transaction_hash) { + self.local_transactions.mark_invalid(transaction.transaction.clone()); + } + // Remove from future let order = self.future.drop(&sender, &nonce); if order.is_some() { @@ -821,15 +875,21 @@ impl TransactionQueue { qed"); if k >= current_nonce { let order = order.update_height(k, current_nonce); + if order.origin.is_local() { + self.local_transactions.mark_future(order.hash); + } if let Some(old) = self.future.insert(*sender, k, order.clone()) { - Self::replace_orders(*sender, k, old, order, &mut self.future, &mut self.by_hash); + Self::replace_orders(*sender, k, old, order, &mut self.future, &mut self.by_hash, &mut self.local_transactions); } } else { trace!(target: "txqueue", "Removing old transaction: {:?} (nonce: {} < {})", order.hash, k, current_nonce); - self.by_hash.remove(&order.hash).expect("All transactions in `future` are also in `by_hash`"); + let tx = self.by_hash.remove(&order.hash).expect("All transactions in `future` are also in `by_hash`"); + if tx.origin.is_local() { + self.local_transactions.mark_mined(tx.transaction); + } } } - self.future.enforce_limit(&mut self.by_hash); + self.future.enforce_limit(&mut self.by_hash, &mut self.local_transactions); } /// Returns top transactions from the queue ordered by priority. @@ -897,8 +957,11 @@ impl TransactionQueue { self.future.by_gas_price.remove(&order.gas_price, &order.hash); // Put to current let order = order.update_height(current_nonce, first_nonce); + if order.origin.is_local() { + self.local_transactions.mark_pending(order.hash); + } if let Some(old) = self.current.insert(address, current_nonce, order.clone()) { - Self::replace_orders(address, current_nonce, old, order, &mut self.current, &mut self.by_hash); + Self::replace_orders(address, current_nonce, old, order, &mut self.current, &mut self.by_hash, &mut self.local_transactions); } update_last_nonce_to = Some(current_nonce); current_nonce = current_nonce + U256::one(); @@ -957,9 +1020,11 @@ impl TransactionQueue { if nonce > next_nonce { // We have a gap - put to future. // Insert transaction (or replace old one with lower gas price) - try!(check_too_cheap(Self::replace_transaction(tx, state_nonce, min_gas_price, &mut self.future, &mut self.by_hash))); + try!(check_too_cheap( + Self::replace_transaction(tx, state_nonce, min_gas_price, &mut self.future, &mut self.by_hash, &mut self.local_transactions) + )); // Enforce limit in Future - let removed = self.future.enforce_limit(&mut self.by_hash); + let removed = self.future.enforce_limit(&mut self.by_hash, &mut self.local_transactions); // Return an error if this transaction was not imported because of limit. try!(check_if_removed(&address, &nonce, removed)); @@ -973,13 +1038,15 @@ impl TransactionQueue { self.move_matching_future_to_current(address, nonce + U256::one(), state_nonce); // Replace transaction if any - try!(check_too_cheap(Self::replace_transaction(tx, state_nonce, min_gas_price, &mut self.current, &mut self.by_hash))); + try!(check_too_cheap( + Self::replace_transaction(tx, state_nonce, min_gas_price, &mut self.current, &mut self.by_hash, &mut self.local_transactions) + )); // Keep track of highest nonce stored in current let new_max = self.last_nonces.get(&address).map_or(nonce, |n| cmp::max(nonce, *n)); self.last_nonces.insert(address, new_max); // Also enforce the limit - let removed = self.current.enforce_limit(&mut self.by_hash); + let removed = self.current.enforce_limit(&mut self.by_hash, &mut self.local_transactions); // If some transaction were removed because of limit we need to update last_nonces also. self.update_last_nonces(&removed); // Trigger error if the transaction we are importing was removed. @@ -1010,7 +1077,14 @@ impl TransactionQueue { /// /// Returns `true` if transaction actually got to the queue (`false` if there was already a transaction with higher /// gas_price) - fn replace_transaction(tx: VerifiedTransaction, base_nonce: U256, min_gas_price: (U256, PrioritizationStrategy), set: &mut TransactionSet, by_hash: &mut HashMap) -> bool { + fn replace_transaction( + tx: VerifiedTransaction, + base_nonce: U256, + min_gas_price: (U256, PrioritizationStrategy), + set: &mut TransactionSet, + by_hash: &mut HashMap, + local: &mut LocalTransactionsList, + ) -> bool { let order = TransactionOrder::for_transaction(&tx, base_nonce, min_gas_price.0, min_gas_price.1); let hash = tx.hash(); let address = tx.sender(); @@ -1021,14 +1095,24 @@ impl TransactionQueue { if let Some(old) = set.insert(address, nonce, order.clone()) { - Self::replace_orders(address, nonce, old, order, set, by_hash) + Self::replace_orders(address, nonce, old, order, set, by_hash, local) } else { true } } - fn replace_orders(address: Address, nonce: U256, old: TransactionOrder, order: TransactionOrder, set: &mut TransactionSet, by_hash: &mut HashMap) -> bool { + fn replace_orders( + address: Address, + nonce: U256, + old: TransactionOrder, + order: TransactionOrder, + set: &mut TransactionSet, + by_hash: &mut HashMap, + local: &mut LocalTransactionsList, + ) -> bool { // There was already transaction in queue. Let's check which one should stay + let old_hash = old.hash; + let new_hash = order.hash; let old_fee = old.gas_price; let new_fee = order.gas_price; if old_fee.cmp(&new_fee) == Ordering::Greater { @@ -1036,12 +1120,18 @@ impl TransactionQueue { // Put back old transaction since it has greater priority (higher gas_price) set.insert(address, nonce, old); // and remove new one - by_hash.remove(&order.hash).expect("The hash has been just inserted and no other line is altering `by_hash`."); + let order = by_hash.remove(&order.hash).expect("The hash has been just inserted and no other line is altering `by_hash`."); + if order.origin.is_local() { + local.mark_replaced(order.transaction, old_fee, old_hash); + } false } else { trace!(target: "txqueue", "Replaced transaction: {:?} with transaction with higher gas price: {:?}", old.hash, order.hash); // Make sure we remove old transaction entirely - by_hash.remove(&old.hash).expect("The hash is coming from `future` so it has to be in `by_hash`."); + let old = by_hash.remove(&old.hash).expect("The hash is coming from `future` so it has to be in `by_hash`."); + if old.origin.is_local() { + local.mark_replaced(old.transaction, new_fee, new_hash); + } true } } @@ -1078,6 +1168,7 @@ mod test { use error::{Error, TransactionError}; use super::*; use super::{TransactionSet, TransactionOrder, VerifiedTransaction}; + use miner::local_transactions::LocalTransactionsList; use client::TransactionImportResult; fn unwrap_tx_err(err: Result) -> TransactionError { @@ -1208,6 +1299,7 @@ mod test { #[test] fn should_create_transaction_set() { // given + let mut local = LocalTransactionsList::default(); let mut set = TransactionSet { by_priority: BTreeSet::new(), by_address: Table::new(), @@ -1235,7 +1327,7 @@ mod test { assert_eq!(set.by_address.len(), 2); // when - set.enforce_limit(&mut by_hash); + set.enforce_limit(&mut by_hash, &mut local); // then assert_eq!(by_hash.len(), 1); From 2cd2b103273fa6a360750299b2f2d1f39e27b91e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 16 Nov 2016 17:54:54 +0100 Subject: [PATCH 11/41] Local transactions RPC --- ethcore/src/miner/local_transactions.rs | 3 + ethcore/src/miner/miner.rs | 12 +- ethcore/src/miner/mod.rs | 4 + ethcore/src/miner/transaction_queue.rs | 8 +- rpc/src/v1/helpers/errors.rs | 64 ++++++----- rpc/src/v1/impls/parity.rs | 17 ++- rpc/src/v1/tests/helpers/miner_service.rs | 9 +- rpc/src/v1/tests/mocked/parity.rs | 16 ++- rpc/src/v1/traits/parity.rs | 10 +- rpc/src/v1/types/mod.rs.in | 2 +- rpc/src/v1/types/transaction.rs | 133 +++++++++++++++++++++- 11 files changed, 240 insertions(+), 38 deletions(-) diff --git a/ethcore/src/miner/local_transactions.rs b/ethcore/src/miner/local_transactions.rs index c9c869c33..c8afcc0d5 100644 --- a/ethcore/src/miner/local_transactions.rs +++ b/ethcore/src/miner/local_transactions.rs @@ -21,6 +21,9 @@ use transaction::SignedTransaction; use error::TransactionError; use util::{U256, H256}; +/// Status of local transaction. +/// Can indicate that the transaction is currently part of the queue (`Pending/Future`) +/// or gives a reason why the transaction was removed. #[derive(Debug, PartialEq, Clone)] pub enum Status { /// The transaction is currently in the transaction queue. diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index af4677cf3..f86ecedc4 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -23,6 +23,7 @@ use account_provider::AccountProvider; use views::{BlockView, HeaderView}; use state::{State, CleanupMode}; use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics}; +use client::TransactionImportResult; use executive::contract_address; use block::{ClosedBlock, SealedBlock, IsBlock, Block}; use error::*; @@ -33,10 +34,11 @@ use engines::Engine; use miner::{MinerService, MinerStatus, TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin}; use miner::banning_queue::{BanningTransactionQueue, Threshold}; use miner::work_notify::WorkPoster; -use client::TransactionImportResult; use miner::price_info::PriceInfo; +use miner::local_transactions::{Status as LocalTransactionStatus}; use header::BlockNumber; + /// Different possible definitions for pending transaction set. #[derive(Debug, PartialEq)] pub enum PendingSet { @@ -845,6 +847,14 @@ impl MinerService for Miner { queue.top_transactions() } + fn local_transactions(&self) -> BTreeMap { + let queue = self.transaction_queue.lock(); + queue.local_transactions() + .iter() + .map(|(hash, status)| (*hash, status.clone())) + .collect() + } + fn pending_transactions(&self, best_block: BlockNumber) -> Vec { let queue = self.transaction_queue.lock(); match self.options.pending_set { diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 29d731e9e..1fb2244fd 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -52,6 +52,7 @@ mod work_notify; pub use self::external::{ExternalMiner, ExternalMinerService}; pub use self::miner::{Miner, MinerOptions, Banning, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit}; pub use self::transaction_queue::{TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin}; +pub use self::local_transactions::{Status as LocalTransactionStatus}; pub use client::TransactionImportResult; use std::collections::BTreeMap; @@ -146,6 +147,9 @@ pub trait MinerService : Send + Sync { /// Get a list of all pending transactions. fn pending_transactions(&self, best_block: BlockNumber) -> Vec; + /// Get a list of local transactions with statuses. + fn local_transactions(&self) -> BTreeMap; + /// Get a list of all pending receipts. fn pending_receipts(&self, best_block: BlockNumber) -> BTreeMap; diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 9f688adf5..bb7dea1eb 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -86,12 +86,13 @@ use std::ops::Deref; use std::cmp::Ordering; use std::cmp; use std::collections::{HashSet, HashMap, BTreeSet, BTreeMap}; +use linked_hash_map::LinkedHashMap; use util::{Address, H256, Uint, U256}; use util::table::Table; use transaction::*; use error::{Error, TransactionError}; use client::TransactionImportResult; -use miner::local_transactions::LocalTransactionsList; +use miner::local_transactions::{LocalTransactionsList, Status as LocalTransactionStatus}; /// Transaction origin #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -901,6 +902,11 @@ impl TransactionQueue { .collect() } + /// Returns local transactions (some of them might not be part of the queue anymore). + pub fn local_transactions(&self) -> &LinkedHashMap { + self.local_transactions.all_transactions() + } + #[cfg(test)] fn future_transactions(&self) -> Vec { self.future.by_priority diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index d36feca4b..673987084 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -22,7 +22,7 @@ macro_rules! rpc_unimplemented { use std::fmt; use rlp::DecoderError; -use ethcore::error::{Error as EthcoreError, CallError}; +use ethcore::error::{Error as EthcoreError, CallError, TransactionError}; use ethcore::account_provider::{Error as AccountError}; use fetch::FetchError; use jsonrpc_core::{Error, ErrorCode, Value}; @@ -227,40 +227,44 @@ pub fn from_password_error(error: AccountError) -> Error { } } -pub fn from_transaction_error(error: EthcoreError) -> Error { +pub fn transaction_message(error: TransactionError) -> String { use ethcore::error::TransactionError::*; + match error { + AlreadyImported => "Transaction with the same hash was already imported.".into(), + Old => "Transaction nonce is too low. Try incrementing the nonce.".into(), + TooCheapToReplace => { + "Transaction gas price is too low. There is another transaction with same nonce in the queue. Try increasing the gas price or incrementing the nonce.".into() + }, + LimitReached => { + "There are too many transactions in the queue. Your transaction was dropped due to limit. Try increasing the fee.".into() + }, + InsufficientGas { minimal, got } => { + format!("Transaction gas is too low. There is not enough gas to cover minimal cost of the transaction (minimal: {}, got: {}). Try increasing supplied gas.", minimal, got) + }, + InsufficientGasPrice { minimal, got } => { + format!("Transaction gas price is too low. It does not satisfy your node's minimal gas price (minimal: {}, got: {}). Try increasing the gas price.", minimal, got) + }, + InsufficientBalance { balance, cost } => { + format!("Insufficient funds. Account you try to send transaction from does not have enough funds. Required {} and got: {}.", cost, balance) + }, + GasLimitExceeded { limit, got } => { + format!("Transaction cost exceeds current gas limit. Limit: {}, got: {}. Try decreasing supplied gas.", limit, got) + }, + InvalidNetworkId => "Invalid network id.".into(), + InvalidGasLimit(_) => "Supplied gas is beyond limit.".into(), + SenderBanned => "Sender is banned in local queue.".into(), + RecipientBanned => "Recipient is banned in local queue.".into(), + CodeBanned => "Code is banned in local queue.".into(), + } +} + +pub fn from_transaction_error(error: EthcoreError) -> Error { + if let EthcoreError::Transaction(e) = error { - let msg = match e { - AlreadyImported => "Transaction with the same hash was already imported.".into(), - Old => "Transaction nonce is too low. Try incrementing the nonce.".into(), - TooCheapToReplace => { - "Transaction gas price is too low. There is another transaction with same nonce in the queue. Try increasing the gas price or incrementing the nonce.".into() - }, - LimitReached => { - "There are too many transactions in the queue. Your transaction was dropped due to limit. Try increasing the fee.".into() - }, - InsufficientGas { minimal, got } => { - format!("Transaction gas is too low. There is not enough gas to cover minimal cost of the transaction (minimal: {}, got: {}). Try increasing supplied gas.", minimal, got) - }, - InsufficientGasPrice { minimal, got } => { - format!("Transaction gas price is too low. It does not satisfy your node's minimal gas price (minimal: {}, got: {}). Try increasing the gas price.", minimal, got) - }, - InsufficientBalance { balance, cost } => { - format!("Insufficient funds. Account you try to send transaction from does not have enough funds. Required {} and got: {}.", cost, balance) - }, - GasLimitExceeded { limit, got } => { - format!("Transaction cost exceeds current gas limit. Limit: {}, got: {}. Try decreasing supplied gas.", limit, got) - }, - InvalidGasLimit(_) => "Supplied gas is beyond limit.".into(), - SenderBanned => "Sender is banned in local queue.".into(), - RecipientBanned => "Recipient is banned in local queue.".into(), - CodeBanned => "Code is banned in local queue.".into(), - e => format!("{}", e).into(), - }; Error { code: ErrorCode::ServerError(codes::TRANSACTION_ERROR), - message: msg, + message: transaction_message(e), data: None, } } else { diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index a952d54ec..1fdcbdef8 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -34,7 +34,11 @@ use ethcore::account_provider::AccountProvider; use jsonrpc_core::Error; use v1::traits::Parity; -use v1::types::{Bytes, U256, H160, H256, H512, Peers, Transaction, RpcSettings, Histogram, TransactionStats}; +use v1::types::{ + Bytes, U256, H160, H256, H512, + Peers, Transaction, RpcSettings, Histogram, + TransactionStats, LocalTransactionStatus, +}; use v1::helpers::{errors, SigningQueue, SignerService, NetworkSettings}; use v1::helpers::dispatch::DEFAULT_MAC; @@ -269,6 +273,17 @@ impl Parity for ParityClient where ) } + fn local_transactions(&self) -> Result, Error> { + try!(self.active()); + + let transactions = take_weak!(self.miner).local_transactions(); + Ok(transactions + .into_iter() + .map(|(hash, status)| (hash.into(), status.into())) + .collect() + ) + } + fn signer_port(&self) -> Result { try!(self.active()); diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index af158d564..ad55faa7b 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -24,7 +24,7 @@ use ethcore::block::{ClosedBlock, IsBlock}; use ethcore::header::BlockNumber; use ethcore::transaction::SignedTransaction; use ethcore::receipt::{Receipt, RichReceipt}; -use ethcore::miner::{MinerService, MinerStatus, TransactionImportResult}; +use ethcore::miner::{MinerService, MinerStatus, TransactionImportResult, LocalTransactionStatus}; /// Test miner service. pub struct TestMinerService { @@ -34,6 +34,8 @@ pub struct TestMinerService { pub latest_closed_block: Mutex>, /// Pre-existed pending transactions pub pending_transactions: Mutex>, + /// Pre-existed local transactions + pub local_transactions: Mutex>, /// Pre-existed pending receipts pub pending_receipts: Mutex>, /// Last nonces. @@ -53,6 +55,7 @@ impl Default for TestMinerService { imported_transactions: Mutex::new(Vec::new()), latest_closed_block: Mutex::new(None), pending_transactions: Mutex::new(HashMap::new()), + local_transactions: Mutex::new(BTreeMap::new()), pending_receipts: Mutex::new(BTreeMap::new()), last_nonces: RwLock::new(HashMap::new()), min_gas_price: RwLock::new(U256::from(20_000_000)), @@ -195,6 +198,10 @@ impl MinerService for TestMinerService { self.pending_transactions.lock().values().cloned().collect() } + fn local_transactions(&self) -> BTreeMap { + self.local_transactions.lock().iter().map(|(hash, stats)| (*hash, stats.clone())).collect() + } + fn pending_transactions(&self, _best_block: BlockNumber) -> Vec { self.pending_transactions.lock().values().cloned().collect() } diff --git a/rpc/src/v1/tests/mocked/parity.rs b/rpc/src/v1/tests/mocked/parity.rs index 8c06d4c7d..5226e2f96 100644 --- a/rpc/src/v1/tests/mocked/parity.rs +++ b/rpc/src/v1/tests/mocked/parity.rs @@ -18,8 +18,9 @@ use std::sync::Arc; use util::log::RotatingLogger; use util::Address; use ethsync::ManageNetwork; -use ethcore::client::{TestBlockChainClient}; use ethcore::account_provider::AccountProvider; +use ethcore::client::{TestBlockChainClient}; +use ethcore::miner::LocalTransactionStatus; use ethstore::ethkey::{Generator, Random}; use jsonrpc_core::IoHandler; @@ -367,3 +368,16 @@ fn rpc_parity_transactions_stats() { assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } +#[test] +fn rpc_parity_local_transactions() { + let deps = Dependencies::new(); + let io = deps.default_client(); + deps.miner.local_transactions.lock().insert(10.into(), LocalTransactionStatus::Pending); + deps.miner.local_transactions.lock().insert(15.into(), LocalTransactionStatus::Future); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_localTransactions", "params":[], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"0x000000000000000000000000000000000000000000000000000000000000000a":{"status":"pending"},"0x000000000000000000000000000000000000000000000000000000000000000f":{"status":"future"}},"id":1}"#; + + assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); +} + diff --git a/rpc/src/v1/traits/parity.rs b/rpc/src/v1/traits/parity.rs index 946da8149..b4df594e8 100644 --- a/rpc/src/v1/traits/parity.rs +++ b/rpc/src/v1/traits/parity.rs @@ -19,7 +19,11 @@ use jsonrpc_core::Error; use std::collections::BTreeMap; use v1::helpers::auto_args::Wrap; -use v1::types::{H160, H256, H512, U256, Bytes, Peers, Transaction, RpcSettings, Histogram, TransactionStats}; +use v1::types::{ + H160, H256, H512, U256, Bytes, + Peers, Transaction, RpcSettings, Histogram, + TransactionStats, LocalTransactionStatus, +}; build_rpc_trait! { /// Parity-specific rpc interface. @@ -119,6 +123,10 @@ build_rpc_trait! { #[rpc(name = "parity_pendingTransactionsStats")] fn pending_transactions_stats(&self) -> Result, Error>; + /// Returns a list of current and past local transactions with status details. + #[rpc(name = "parity_localTransactions")] + fn local_transactions(&self) -> Result, Error>; + /// Returns current Trusted Signer port or an error if signer is disabled. #[rpc(name = "parity_signerPort")] fn signer_port(&self) -> Result; diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index 29e50aae5..4ee61387e 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -44,7 +44,7 @@ pub use self::hash::{H64, H160, H256, H512, H520, H2048}; pub use self::index::Index; pub use self::log::Log; pub use self::sync::{SyncStatus, SyncInfo, Peers, PeerInfo, PeerNetworkInfo, PeerProtocolsInfo, PeerEthereumProtocolInfo, TransactionStats}; -pub use self::transaction::Transaction; +pub use self::transaction::{Transaction, LocalTransactionStatus}; pub use self::transaction_request::TransactionRequest; pub use self::receipt::Receipt; pub use self::rpc_settings::RpcSettings; diff --git a/rpc/src/v1/types/transaction.rs b/rpc/src/v1/types/transaction.rs index 0982a5ef9..7379a3b19 100644 --- a/rpc/src/v1/types/transaction.rs +++ b/rpc/src/v1/types/transaction.rs @@ -14,8 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use serde::{Serialize, Serializer}; +use ethcore::miner; use ethcore::contract_address; use ethcore::transaction::{LocalizedTransaction, Action, SignedTransaction}; +use v1::helpers::errors; use v1::types::{Bytes, H160, H256, U256, H512}; /// Transaction @@ -62,6 +65,74 @@ pub struct Transaction { pub s: H256, } +/// Local Transaction Status +#[derive(Debug)] +pub enum LocalTransactionStatus { + /// Transaction is pending + Pending, + /// Transaction is in future part of the queue + Future, + /// Transaction is already mined. + Mined(Transaction), + /// Transaction was dropped because of limit. + Dropped(Transaction), + /// Transaction was replaced by transaction with higher gas price. + Replaced(Transaction, U256, H256), + /// Transaction never got into the queue. + Rejected(Transaction, String), + /// Transaction is invalid. + Invalid(Transaction), +} + +impl Serialize for LocalTransactionStatus { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> + where S: Serializer + { + use self::LocalTransactionStatus::*; + + let elems = match *self { + Pending | Future => 1, + Mined(..) | Dropped(..) | Invalid(..) => 2, + Rejected(..) => 3, + Replaced(..) => 4, + }; + + let status = "status"; + let transaction = "transaction"; + + let mut state = try!(serializer.serialize_struct("LocalTransactionStatus", elems)); + match *self { + Pending => try!(serializer.serialize_struct_elt(&mut state, status, "pending")), + Future => try!(serializer.serialize_struct_elt(&mut state, status, "future")), + Mined(ref tx) => { + try!(serializer.serialize_struct_elt(&mut state, status, "mined")); + try!(serializer.serialize_struct_elt(&mut state, transaction, tx)); + }, + Dropped(ref tx) => { + try!(serializer.serialize_struct_elt(&mut state, status, "dropped")); + try!(serializer.serialize_struct_elt(&mut state, transaction, tx)); + }, + Invalid(ref tx) => { + try!(serializer.serialize_struct_elt(&mut state, status, "invalid")); + try!(serializer.serialize_struct_elt(&mut state, transaction, tx)); + }, + Rejected(ref tx, ref reason) => { + try!(serializer.serialize_struct_elt(&mut state, status, "rejected")); + try!(serializer.serialize_struct_elt(&mut state, transaction, tx)); + try!(serializer.serialize_struct_elt(&mut state, "error", reason)); + }, + Replaced(ref tx, ref gas_price, ref hash) => { + try!(serializer.serialize_struct_elt(&mut state, status, "replaced")); + try!(serializer.serialize_struct_elt(&mut state, transaction, tx)); + try!(serializer.serialize_struct_elt(&mut state, "hash", hash)); + try!(serializer.serialize_struct_elt(&mut state, "gasPrice", gas_price)); + }, + } + serializer.serialize_struct_end(state) + } +} + + impl From for Transaction { fn from(t: LocalizedTransaction) -> Transaction { let signature = t.signature(); @@ -124,9 +195,24 @@ impl From for Transaction { } } +impl From for LocalTransactionStatus { + fn from(s: miner::LocalTransactionStatus) -> Self { + use ethcore::miner::LocalTransactionStatus::*; + match s { + Pending => LocalTransactionStatus::Pending, + Future => LocalTransactionStatus::Future, + Mined(tx) => LocalTransactionStatus::Mined(tx.into()), + Dropped(tx) => LocalTransactionStatus::Dropped(tx.into()), + Rejected(tx, err) => LocalTransactionStatus::Rejected(tx.into(), errors::transaction_message(err)), + Replaced(tx, gas_price, hash) => LocalTransactionStatus::Replaced(tx.into(), gas_price.into(), hash.into()), + Invalid(tx) => LocalTransactionStatus::Invalid(tx.into()), + } + } +} + #[cfg(test)] mod tests { - use super::Transaction; + use super::{Transaction, LocalTransactionStatus}; use serde_json; #[test] @@ -135,5 +221,50 @@ mod tests { let serialized = serde_json::to_string(&t).unwrap(); assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"v":0,"r":"0x0000000000000000000000000000000000000000000000000000000000000000","s":"0x0000000000000000000000000000000000000000000000000000000000000000"}"#); } + + #[test] + fn test_local_transaction_status_serialize() { + let tx_ser = serde_json::to_string(&Transaction::default()).unwrap(); + let status1 = LocalTransactionStatus::Pending; + let status2 = LocalTransactionStatus::Future; + let status3 = LocalTransactionStatus::Mined(Transaction::default()); + let status4 = LocalTransactionStatus::Dropped(Transaction::default()); + let status5 = LocalTransactionStatus::Invalid(Transaction::default()); + let status6 = LocalTransactionStatus::Rejected(Transaction::default(), "Just because".into()); + let status7 = LocalTransactionStatus::Replaced(Transaction::default(), 5.into(), 10.into()); + + assert_eq!( + serde_json::to_string(&status1).unwrap(), + r#"{"status":"pending"}"# + ); + assert_eq!( + serde_json::to_string(&status2).unwrap(), + r#"{"status":"future"}"# + ); + assert_eq!( + serde_json::to_string(&status3).unwrap(), + r#"{"status":"mined","transaction":"#.to_owned() + &format!("{}", tx_ser) + r#"}"# + ); + assert_eq!( + serde_json::to_string(&status4).unwrap(), + r#"{"status":"dropped","transaction":"#.to_owned() + &format!("{}", tx_ser) + r#"}"# + ); + assert_eq!( + serde_json::to_string(&status5).unwrap(), + r#"{"status":"invalid","transaction":"#.to_owned() + &format!("{}", tx_ser) + r#"}"# + ); + assert_eq!( + serde_json::to_string(&status6).unwrap(), + r#"{"status":"rejected","transaction":"#.to_owned() + + &format!("{}", tx_ser) + + r#","error":"Just because"}"# + ); + assert_eq!( + serde_json::to_string(&status7).unwrap(), + r#"{"status":"replaced","transaction":"#.to_owned() + + &format!("{}", tx_ser) + + r#","hash":"0x000000000000000000000000000000000000000000000000000000000000000a","gasPrice":"0x5"}"# + ); + } } From ff27bbcb4fea40a4e38014902f7914774655106e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 17 Nov 2016 10:15:11 +0100 Subject: [PATCH 12/41] Simple GUI for local transactions --- js/src/api/rpc/parity/parity.js | 30 +- js/src/dapps/localtx.html | 17 + js/src/dapps/localtx.js | 33 ++ .../dapps/localtx/Application/application.js | 197 ++++++++++ .../localtx/Application/application.spec.js | 32 ++ js/src/dapps/localtx/Application/index.js | 17 + js/src/dapps/localtx/Transaction/index.js | 17 + .../dapps/localtx/Transaction/transaction.css | 31 ++ .../dapps/localtx/Transaction/transaction.js | 359 ++++++++++++++++++ .../localtx/Transaction/transaction.spec.js | 37 ++ js/src/dapps/localtx/parity.js | 21 + js/src/jsonrpc/interfaces/parity.js | 45 ++- js/src/views/Dapps/builtin.json | 10 + js/webpack.config.js | 1 + 14 files changed, 834 insertions(+), 13 deletions(-) create mode 100644 js/src/dapps/localtx.html create mode 100644 js/src/dapps/localtx.js create mode 100644 js/src/dapps/localtx/Application/application.js create mode 100644 js/src/dapps/localtx/Application/application.spec.js create mode 100644 js/src/dapps/localtx/Application/index.js create mode 100644 js/src/dapps/localtx/Transaction/index.js create mode 100644 js/src/dapps/localtx/Transaction/transaction.css create mode 100644 js/src/dapps/localtx/Transaction/transaction.js create mode 100644 js/src/dapps/localtx/Transaction/transaction.spec.js create mode 100644 js/src/dapps/localtx/parity.js diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index a33828b80..2c90d091b 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, inData, inHex, inNumber16, inOptions } from '../../format/input'; -import { outAccountInfo, outAddress, outHistogram, outNumber, outPeers } from '../../format/output'; +import { outAccountInfo, outAddress, outHistogram, outNumber, outPeers, outTransaction } from '../../format/output'; export default class Parity { constructor (transport) { @@ -117,16 +117,27 @@ export default class Parity { .execute('parity_hashContent', url); } + importGethAccounts (accounts) { + return this._transport + .execute('parity_importGethAccounts', (accounts || []).map(inAddress)) + .then((accounts) => (accounts || []).map(outAddress)); + } + listGethAccounts () { return this._transport .execute('parity_listGethAccounts') .then((accounts) => (accounts || []).map(outAddress)); } - importGethAccounts (accounts) { + localTransactions () { return this._transport - .execute('parity_importGethAccounts', (accounts || []).map(inAddress)) - .then((accounts) => (accounts || []).map(outAddress)); + .execute('parity_localTransactions') + .then(transactions => { + Object.values(transactions) + .filter(tx => tx.transaction) + .map(tx => tx.transaction = outTransaction(tx.transaction)); + return transactions; + }); } minGasPrice () { @@ -192,6 +203,17 @@ export default class Parity { .execute('parity_nodeName'); } + pendingTransactions () { + return this._transport + .execute('parity_pendingTransactions') + .then(data => data.map(outTransaction)); + } + + pendingTransactionsStats () { + return this._transport + .execute('parity_pendingTransactionsStats'); + } + phraseToAddress (phrase) { return this._transport .execute('parity_phraseToAddress', phrase) diff --git a/js/src/dapps/localtx.html b/js/src/dapps/localtx.html new file mode 100644 index 000000000..d1e6fed05 --- /dev/null +++ b/js/src/dapps/localtx.html @@ -0,0 +1,17 @@ + + + + + + + + Local transactions Viewer + + +
+ + + + + + diff --git a/js/src/dapps/localtx.js b/js/src/dapps/localtx.js new file mode 100644 index 000000000..98561f33f --- /dev/null +++ b/js/src/dapps/localtx.js @@ -0,0 +1,33 @@ +// Copyright 2015, 2016 Ethcore (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 ReactDOM from 'react-dom'; +import React from 'react'; + +import injectTapEventPlugin from 'react-tap-event-plugin'; +injectTapEventPlugin(); + +import Application from './localtx/Application'; + +import '../../assets/fonts/Roboto/font.css'; +import '../../assets/fonts/RobotoMono/font.css'; +import './style.css'; +import './localtx.html'; + +ReactDOM.render( + , + document.querySelector('#container') +); diff --git a/js/src/dapps/localtx/Application/application.js b/js/src/dapps/localtx/Application/application.js new file mode 100644 index 000000000..cbe7eebaa --- /dev/null +++ b/js/src/dapps/localtx/Application/application.js @@ -0,0 +1,197 @@ +// Copyright 2015, 2016 Ethcore (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 BigNumber from 'bignumber.js'; +import React, { Component } from 'react'; + +import { api } from '../parity'; + +import { Transaction, LocalTransaction } from '../Transaction'; + +export default class Application extends Component { + state = { + loading: true, + transactions: [], + localTransactions: {} + } + + componentDidMount () { + const poll = () => this.fetchTransactionData().then(poll); + this._timeout = setTimeout(poll, 2000); + } + + componentWillUnmount () { + clearTimeout(this._timeout); + } + + fetchTransactionData () { + return Promise.all([ + api.parity.pendingTransactions(), + api.parity.pendingTransactionsStats(), + api.parity.localTransactions(), + ]).then(([pending, stats, local]) => { + // Combine results together + const transactions = pending.map(tx => { + return { + transaction: tx, + stats: stats[tx.hash], + isLocal: !!local[tx.hash] + }; + }); + + // Add transaction data to locals + transactions + .filter(tx => tx.isLocal) + .map(data => { + const tx = data.transaction; + local[tx.hash].transaction = tx; + local[tx.hash].stats = data.stats; + }); + + // Convert local transactions to array + const localTransactions = Object.keys(local).map(hash => { + const data = local[hash]; + data.txHash = hash; + return data; + }); + + // Sort local transactions by nonce (move future to the end) + localTransactions.sort((a, b) => { + a = a.transaction || {}; + b = b.transaction || {}; + + if (a.from && b.from && a.from !== b.from) { + return a.from < b.from; + } + + if (!a.nonce || !b.nonce) { + return !a.nonce ? 1 : -1; + } + + return new BigNumber(a.nonce).comparedTo(new BigNumber(b.nonce)); + }); + + this.setState({ + loading: false, + transactions, + localTransactions + }); + }); + } + + render () { + const { loading } = this.state; + + if (loading) { + return ( +
Loading...
+ ); + } + + return ( +
+

Your past local transactions

+ { this.renderLocals() } +

Transactions in the queue

+ { this.renderQueueSummary() } + { this.renderQueue() } +
+ ); + } + + renderQueueSummary () { + const { transactions } = this.state; + if (!transactions.length) { + return null; + } + + const count = transactions.length; + const locals = transactions.filter(tx => tx.isLocal).length; + const fee = transactions + .map(tx => tx.transaction) + .map(tx => tx.gasPrice.mul(tx.gas)) + .reduce((sum, fee) => sum.add(fee), new BigNumber(0)); + + return ( +

+ Count: { locals ? `${count} (${locals})` : count } +   + Total Fee: { api.util.fromWei(fee).toFixed(3) } ETH +

+ ); + } + + renderQueue () { + const { transactions } = this.state; + if (!transactions.length) { + return ( +

The queue seems is empty.

+ ); + } + + return ( + + + { Transaction.renderHeader() } + + + { + transactions.map((tx, idx) => ( + + )) + } + +
+ ); + } + + renderLocals () { + const { localTransactions } = this.state; + if (!localTransactions.length) { + return ( +

You haven't sent any transactions yet.

+ ); + } + + return ( + + + { LocalTransaction.renderHeader() } + + + { + localTransactions.map(tx => ( + + )) + } + +
+ ); + } +} diff --git a/js/src/dapps/localtx/Application/application.spec.js b/js/src/dapps/localtx/Application/application.spec.js new file mode 100644 index 000000000..2044b4e14 --- /dev/null +++ b/js/src/dapps/localtx/Application/application.spec.js @@ -0,0 +1,32 @@ +// Copyright 2015, 2016 Ethcore (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 '../../../environment/tests'; + +import Application from './application'; + +describe('localtx/Application', () => { + describe('rendering', () => { + it('renders without crashing', () => { + const rendered = shallow(); + + expect(rendered).to.be.defined; + }); + }); +}); diff --git a/js/src/dapps/localtx/Application/index.js b/js/src/dapps/localtx/Application/index.js new file mode 100644 index 000000000..236578226 --- /dev/null +++ b/js/src/dapps/localtx/Application/index.js @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Ethcore (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 './application'; diff --git a/js/src/dapps/localtx/Transaction/index.js b/js/src/dapps/localtx/Transaction/index.js new file mode 100644 index 000000000..56854f412 --- /dev/null +++ b/js/src/dapps/localtx/Transaction/index.js @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Ethcore (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 { Transaction, LocalTransaction } from './transaction'; diff --git a/js/src/dapps/localtx/Transaction/transaction.css b/js/src/dapps/localtx/Transaction/transaction.css new file mode 100644 index 000000000..c49d9479f --- /dev/null +++ b/js/src/dapps/localtx/Transaction/transaction.css @@ -0,0 +1,31 @@ +.from { + white-space: nowrap; + + img { + vertical-align: middle; + } +} + +.transaction { + td { + padding: 7px 15px; + } + + td:first-child { + padding: 7px 0; + } + + &.local { + background: #8bc34a; + } +} + +.nowrap { + white-space: nowrap; +} + +.edit { + label, input { + display: block; + } +} diff --git a/js/src/dapps/localtx/Transaction/transaction.js b/js/src/dapps/localtx/Transaction/transaction.js new file mode 100644 index 000000000..e3195a59d --- /dev/null +++ b/js/src/dapps/localtx/Transaction/transaction.js @@ -0,0 +1,359 @@ +// Copyright 2015, 2016 Ethcore (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 classnames from 'classnames'; + +import { api } from '../parity'; + +import styles from './transaction.css'; + +import IdentityIcon from '../../githubhint/IdentityIcon'; + +class BaseTransaction extends Component { + + shortHash (hash) { + return `${hash.substr(0, 6)}..${hash.substr(hash.length - 4)}`; + } + + renderHash (hash) { + return ( + + { this.shortHash(hash) } + + ); + } + + renderFrom (transaction) { + if (!transaction) { + return '-'; + } + + return ( +
+ + 0x{ transaction.nonce.toString(16) } +
+ ); + } + + renderGasPrice (transaction) { + if (!transaction) { + return '-'; + } + + return ( + + { api.util.fromWei(transaction.gasPrice, 'shannon').toFormat(2) } shannon + + ); + } + + renderGas (transaction) { + if (!transaction) { + return '-'; + } + + return ( + + { transaction.gas.div(10**6).toFormat(3) } MGas + + ); + } + + renderPropagation (stats) { + const noOfPeers = Object.keys(stats.propagatedTo).length; + const noOfPropagations = Object.values(stats.propagatedTo).reduce((sum, val) => sum + val, 0); + + return ( + + { noOfPropagations } ({ noOfPeers } peers) + + ); + } +} + +export class Transaction extends BaseTransaction { + + static propTypes = { + idx: PropTypes.number.isRequired, + transaction: PropTypes.object.isRequired, + isLocal: PropTypes.bool, + stats: PropTypes.object + }; + + static defaultProps = { + isLocal: false, + stats: { + firstSeen: 0, + propagatedTo: {} + } + }; + + static renderHeader () { + return ( + + + + Transaction + + + From + + + Gas Price + + + Gas + + + First seen + + + # Propagated + + + + + ); + } + + render () { + const { isLocal, stats, transaction, idx } = this.props; + + const clazz = classnames(styles.transaction, { + [styles.local]: isLocal + }); + const noOfPeers = Object.keys(stats.propagatedTo).length; + const noOfPropagations = Object.values(stats.propagatedTo).reduce((sum, val) => sum + val, 0); + + return ( + + + { idx }. + + + { this.renderHash(transaction.hash) } + + + { this.renderFrom(transaction) } + + + { this.renderGasPrice(transaction) } + + + { this.renderGas(transaction) } + + + { stats.firstSeen } + + + { this.renderPropagation(stats) } + + + ); + } +} + +export class LocalTransaction extends BaseTransaction { + + static propTypes = { + hash: PropTypes.string.isRequired, + status: PropTypes.string.isRequired, + transaction: PropTypes.object, + isLocal: PropTypes.bool, + stats: PropTypes.object, + details: PropTypes.object + }; + + static defaultProps = { + stats: { + propagatedTo: {} + } + }; + + static renderHeader () { + return ( + + + + Transaction + + + From + + + Gas Price / Gas + + + Propagated + + + Status + + + ); + } + + state = { + isSending: false, + isResubmitting: false, + gasPrice: null, + gas: null + }; + + toggleResubmit = () => { + const { transaction } = this.props; + const { isResubmitting, gasPrice } = this.state; + + this.setState({ + isResubmitting: !isResubmitting, + }); + + if (gasPrice === null) { + this.setState({ + gasPrice: `0x${ transaction.gasPrice.toString(16) }`, + gas: `0x${ transaction.gas.toString(16) }` + }); + } + }; + + sendTransaction = () => { + const { transaction } = this.props; + const { gasPrice, gas } = this.state; + + const newTransaction = { + from: transaction.from, + to: transaction.to, + nonce: transaction.nonce, + value: transaction.value, + data: transaction.data, + gasPrice, gas + }; + + this.setState({ + isResubmitting: false, + isSending: true + }); + + const closeSending = () => this.setState({ + isSending: false + }); + + api.eth.sendTransaction(newTransaction) + .then(closeSending) + .catch(closeSending); + }; + + render () { + if (this.state.isResubmitting) { + return this.renderResubmit(); + } + + const { stats, transaction, hash, status } = this.props; + const { isSending } = this.state; + + const resubmit = isSending ? ( + 'sending...' + ) : ( + + resubmit + + ); + + return ( + + + { !transaction ? null : resubmit } + + + { this.renderHash(hash) } + + + { this.renderFrom(transaction) } + + + { this.renderGasPrice(transaction) } +
+ { this.renderGas(transaction) } + + + { status === 'pending' ? this.renderPropagation(stats) : '-' } + + + { this.renderStatus() } + + + ); + } + + renderStatus () { + const { details } = this.props; + + let state = { + 'pending': () => `In queue: Pending`, + 'future': () => `In queue: Future`, + 'mined': () => `Mined`, + 'dropped': () => `Dropped because of queue limit`, + 'invalid': () => `Transaction is invalid`, + 'rejected': () => `Rejected: ${ details.error }`, + 'replaced': () => `Replaced by ${ this.shortHash(details.hash) }`, + }[this.props.status]; + + return state ? state() : 'unknown'; + } + + renderResubmit () { + const { transaction } = this.props; + const { gasPrice, gas } = this.state; + + return ( + + + + cancel + + + + { this.renderHash(transaction.hash) } + + + { this.renderFrom(transaction) } + + + this.setState({ gasPrice: el.target.value }) } + /> + this.setState({ gas: el.target.value }) } + /> + + + + Send + + + + ); + } + +} diff --git a/js/src/dapps/localtx/Transaction/transaction.spec.js b/js/src/dapps/localtx/Transaction/transaction.spec.js new file mode 100644 index 000000000..1fe78124c --- /dev/null +++ b/js/src/dapps/localtx/Transaction/transaction.spec.js @@ -0,0 +1,37 @@ +// Copyright 2015, 2016 Ethcore (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 '../../../environment/tests'; + +import Transaction from './transaction'; + +describe('localtx/Transaction', () => { + describe('rendering', () => { + it('renders without crashing', () => { + const rendered = shallow( + + ); + + expect(rendered).to.be.defined; + }); + }); +}); diff --git a/js/src/dapps/localtx/parity.js b/js/src/dapps/localtx/parity.js new file mode 100644 index 000000000..acee4dee0 --- /dev/null +++ b/js/src/dapps/localtx/parity.js @@ -0,0 +1,21 @@ +// Copyright 2015, 2016 Ethcore (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 api = window.parent.secureApi; + +export { + api +}; diff --git a/js/src/jsonrpc/interfaces/parity.js b/js/src/jsonrpc/interfaces/parity.js index 5dd313e00..a5717f502 100644 --- a/js/src/jsonrpc/interfaces/parity.js +++ b/js/src/jsonrpc/interfaces/parity.js @@ -224,15 +224,6 @@ export default { } }, - listGethAccounts: { - desc: 'Returns a list of the accounts available from Geth', - params: [], - returns: { - type: Array, - desc: '20 Bytes addresses owned by the client.' - } - }, - importGethAccounts: { desc: 'Imports a list of accounts from geth', params: [ @@ -247,6 +238,24 @@ export default { } }, + listGethAccounts: { + desc: 'Returns a list of the accounts available from Geth', + params: [], + returns: { + type: Array, + desc: '20 Bytes addresses owned by the client.' + } + }, + + localTransactions: { + desc: 'Returns an object of current and past local transactions.', + params: [], + returns: { + type: Object, + desc: 'Mapping of `tx hash` into status object.' + } + }, + minGasPrice: { desc: 'Returns currently set minimal gas price', params: [], @@ -379,6 +388,24 @@ export default { } }, + pendingTransactions: { + desc: 'Returns a list of transactions currently in the queue.', + params: [], + returns: { + type: Array, + desc: 'Transactions ordered by priority' + } + }, + + pendingTransactionsStats: { + desc: 'Returns propagation stats for transactions in the queue', + params: [], + returns: { + type: Object, + desc: 'mapping of `tx hash` into `stats`' + } + }, + phraseToAddress: { desc: 'Converts a secret phrase into the corresponting address', params: [ diff --git a/js/src/views/Dapps/builtin.json b/js/src/views/Dapps/builtin.json index 827c52db0..71f3cd6ed 100644 --- a/js/src/views/Dapps/builtin.json +++ b/js/src/views/Dapps/builtin.json @@ -39,5 +39,15 @@ "author": "Parity Team ", "version": "1.0.0", "secure": true + }, + { + "id": "0xae74ad174b95cdbd01c88ac5b73a296d33e9088fc2a200e76bcedf3a94a7815d", + "url": "localtx", + "name": "TxQueue Viewer", + "description": "Have a peak on internals of transaction queue of your node.", + "author": "Parity Team ", + "version": "1.0.0", + "secure": true } + ] diff --git a/js/webpack.config.js b/js/webpack.config.js index 4413299fa..7d445262e 100644 --- a/js/webpack.config.js +++ b/js/webpack.config.js @@ -40,6 +40,7 @@ module.exports = { 'githubhint': ['./dapps/githubhint.js'], 'registry': ['./dapps/registry.js'], 'signaturereg': ['./dapps/signaturereg.js'], + 'localtx': ['./dapps/localtx.js'], 'tokenreg': ['./dapps/tokenreg.js'], // app 'index': ['./index.js'] From 74bf2c75f0c11e7009ca095a71a28d706d5f38bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 17 Nov 2016 11:01:21 +0100 Subject: [PATCH 13/41] Transaction queue improvements --- ethcore/src/miner/miner.rs | 27 +++-- ethcore/src/miner/transaction_queue.rs | 102 +++++++++++++++++- .../dapps/localtx/Application/application.css | 19 ++++ .../dapps/localtx/Application/application.js | 22 ++-- .../dapps/localtx/Transaction/transaction.js | 33 ++++-- 5 files changed, 176 insertions(+), 27 deletions(-) create mode 100644 js/src/dapps/localtx/Application/application.css diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index b770aa40c..df55e7367 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -564,7 +564,7 @@ impl Miner { prepare_new } - fn add_transactions_to_queue(&self, chain: &MiningBlockChainClient, transactions: Vec, origin: TransactionOrigin, transaction_queue: &mut BanningTransactionQueue) -> + fn add_transactions_to_queue(&self, chain: &MiningBlockChainClient, transactions: Vec, default_origin: TransactionOrigin, transaction_queue: &mut BanningTransactionQueue) -> Vec> { let fetch_account = |a: &Address| AccountDetails { @@ -572,15 +572,28 @@ impl Miner { balance: chain.latest_balance(a), }; + let accounts = self.accounts.as_ref() + .and_then(|provider| provider.accounts().ok()) + .map(|accounts| accounts.into_iter().collect::>()); + let schedule = chain.latest_schedule(); let gas_required = |tx: &SignedTransaction| tx.gas_required(&schedule).into(); transactions.into_iter() - .map(|tx| match origin { - TransactionOrigin::Local | TransactionOrigin::RetractedBlock => { - transaction_queue.add(tx, origin, &fetch_account, &gas_required) - }, - TransactionOrigin::External => { - transaction_queue.add_with_banlist(tx, &fetch_account, &gas_required) + .map(|tx| { + let origin = accounts.as_ref().and_then(|accounts| { + tx.sender().ok().and_then(|sender| match accounts.contains(&sender) { + true => Some(TransactionOrigin::Local), + false => None, + }) + }).unwrap_or(default_origin); + + match origin { + TransactionOrigin::Local | TransactionOrigin::RetractedBlock => { + transaction_queue.add(tx, origin, &fetch_account, &gas_required) + }, + TransactionOrigin::External => { + transaction_queue.add_with_banlist(tx, &fetch_account, &gas_required) + } } }) .collect() diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index bb7dea1eb..31c27982a 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -250,6 +250,7 @@ impl Ord for TransactionOrder { } /// Verified transaction (with sender) +#[derive(Debug)] struct VerifiedTransaction { /// Transaction transaction: SignedTransaction, @@ -637,7 +638,11 @@ impl TransactionQueue { self.local_transactions.mark_future(hash); }, Err(Error::Transaction(ref err)) => { - self.local_transactions.mark_rejected(cloned_tx, err.clone()); + // Sometimes transactions are re-imported, so + // don't overwrite transactions if they are already on the list + if !self.local_transactions.contains(&cloned_tx.hash()) { + self.local_transactions.mark_rejected(cloned_tx, err.clone()); + } }, Err(_) => { self.local_transactions.mark_invalid(cloned_tx); @@ -772,6 +777,12 @@ impl TransactionQueue { None => return, Some(t) => t, }; + + // Never penalize local transactions + if transaction.origin.is_local() { + return; + } + let sender = transaction.sender(); // Penalize all transactions from this sender @@ -843,6 +854,33 @@ impl TransactionQueue { } } + /// Marks all transactions from particular sender as local transactions + fn mark_transactions_local(&mut self, sender: &Address) { + fn mark_local(sender: &Address, set: &mut TransactionSet, mut mark: F) { + // Mark all transactions from this sender as local + let nonces_from_sender = set.by_address.row(sender) + .map(|row_map| { + row_map.iter().filter_map(|(nonce, order)| if order.origin.is_local() { + None + } else { + Some(*nonce) + }).collect::>() + }) + .unwrap_or_else(Vec::new); + + for k in nonces_from_sender { + let mut order = set.drop(sender, &k).expect("transaction known to be in self.current/self.future; qed"); + order.origin = TransactionOrigin::Local; + mark(order.hash); + set.insert(*sender, k, order); + } + } + + let local = &mut self.local_transactions; + mark_local(sender, &mut self.current, |hash| local.mark_pending(hash)); + mark_local(sender, &mut self.future, |hash| local.mark_future(hash)); + } + /// Update height of all transactions in future transactions set. fn update_future(&mut self, sender: &Address, current_nonce: U256) { // We need to drain all transactions for current sender from future and reinsert them with updated height @@ -1022,6 +1060,10 @@ impl TransactionQueue { .cloned() .map_or(state_nonce, |n| n + U256::one()); + if tx.origin.is_local() { + self.mark_transactions_local(&address); + } + // Future transaction if nonce > next_nonce { // We have a gap - put to future. @@ -1099,6 +1141,7 @@ impl TransactionQueue { let old_hash = by_hash.insert(hash, tx); assert!(old_hash.is_none(), "Each hash has to be inserted exactly once."); + trace!(target: "txqueue", "Inserting: {:?}", order); if let Some(old) = set.insert(address, nonce, order.clone()) { Self::replace_orders(address, nonce, old, order, set, by_hash, local) @@ -1726,6 +1769,31 @@ mod test { assert_eq!(top.len(), 2); } + #[test] + fn when_importing_local_should_mark_others_from_the_same_sender_as_local() { + // given + let mut txq = TransactionQueue::default(); + let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); + // the second one has same nonce but higher `gas_price` + let (_, tx0) = new_similar_tx_pair(); + + txq.add(tx0.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + // the one with higher gas price is first + assert_eq!(txq.top_transactions()[0], tx0); + assert_eq!(txq.top_transactions()[1], tx1); + + // when + // insert second as local + txq.add(tx2.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + + // then + // the order should be updated + assert_eq!(txq.top_transactions()[0], tx1); + assert_eq!(txq.top_transactions()[1], tx0); + assert_eq!(txq.top_transactions()[2], tx2); + } + #[test] fn should_prioritize_reimported_transactions_within_same_nonce_height() { // given @@ -1793,6 +1861,38 @@ mod test { assert_eq!(top.len(), 4); } + #[test] + fn should_not_penalize_local_transactions() { + // given + let mut txq = TransactionQueue::default(); + // txa, txb - slightly bigger gas price to have consistent ordering + let (txa, txb) = new_tx_pair_default(1.into(), 0.into()); + let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); + + // insert everything + txq.add(txa.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(txb.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + + let top = txq.top_transactions(); + assert_eq!(top[0], tx1); + assert_eq!(top[1], txa); + assert_eq!(top[2], tx2); + assert_eq!(top[3], txb); + assert_eq!(top.len(), 4); + + // when + txq.penalize(&tx1.hash()); + + // then (order is the same) + let top = txq.top_transactions(); + assert_eq!(top[0], tx1); + assert_eq!(top[1], txa); + assert_eq!(top[2], tx2); + assert_eq!(top[3], txb); + assert_eq!(top.len(), 4); + } #[test] fn should_penalize_transactions_from_sender() { diff --git a/js/src/dapps/localtx/Application/application.css b/js/src/dapps/localtx/Application/application.css new file mode 100644 index 000000000..4b5f0bc31 --- /dev/null +++ b/js/src/dapps/localtx/Application/application.css @@ -0,0 +1,19 @@ +.container { + padding: 1rem 2rem; + text-align: center; + + h1 { + margin-top: 3rem; + margin-bottom: 1rem; + } + + table { + text-align: left; + margin: auto; + max-width: 90vw; + + th { + text-align: center; + } + } +} diff --git a/js/src/dapps/localtx/Application/application.js b/js/src/dapps/localtx/Application/application.js index cbe7eebaa..a6e6cd166 100644 --- a/js/src/dapps/localtx/Application/application.js +++ b/js/src/dapps/localtx/Application/application.js @@ -19,17 +19,20 @@ import React, { Component } from 'react'; import { api } from '../parity'; +import styles from './application.css'; + import { Transaction, LocalTransaction } from '../Transaction'; export default class Application extends Component { state = { loading: true, transactions: [], - localTransactions: {} + localTransactions: {}, + blockNumber: 0 } componentDidMount () { - const poll = () => this.fetchTransactionData().then(poll); + const poll = () => this.fetchTransactionData().then(poll).catch(poll); this._timeout = setTimeout(poll, 2000); } @@ -42,7 +45,8 @@ export default class Application extends Component { api.parity.pendingTransactions(), api.parity.pendingTransactionsStats(), api.parity.localTransactions(), - ]).then(([pending, stats, local]) => { + api.eth.blockNumber() + ]).then(([pending, stats, local, blockNumber]) => { // Combine results together const transactions = pending.map(tx => { return { @@ -87,7 +91,8 @@ export default class Application extends Component { this.setState({ loading: false, transactions, - localTransactions + localTransactions, + blockNumber }); }); } @@ -97,13 +102,13 @@ export default class Application extends Component { if (loading) { return ( -
Loading...
+
Loading...
); } return ( -
-

Your past local transactions

+
+

Your local transactions

{ this.renderLocals() }

Transactions in the queue

{ this.renderQueueSummary() } @@ -135,7 +140,7 @@ export default class Application extends Component { } renderQueue () { - const { transactions } = this.state; + const { blockNumber, transactions } = this.state; if (!transactions.length) { return (

The queue seems is empty.

@@ -156,6 +161,7 @@ export default class Application extends Component { isLocal={ tx.isLocal } transaction={ tx.transaction } stats={ tx.stats } + blockNumber={ blockNumber } /> )) } diff --git a/js/src/dapps/localtx/Transaction/transaction.js b/js/src/dapps/localtx/Transaction/transaction.js index e3195a59d..0263a8430 100644 --- a/js/src/dapps/localtx/Transaction/transaction.js +++ b/js/src/dapps/localtx/Transaction/transaction.js @@ -14,6 +14,7 @@ // 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 classnames from 'classnames'; @@ -26,7 +27,7 @@ import IdentityIcon from '../../githubhint/IdentityIcon'; class BaseTransaction extends Component { shortHash (hash) { - return `${hash.substr(0, 6)}..${hash.substr(hash.length - 4)}`; + return `${hash.substr(0, 5)}..${hash.substr(hash.length - 3)}`; } renderHash (hash) { @@ -93,6 +94,7 @@ export class Transaction extends BaseTransaction { static propTypes = { idx: PropTypes.number.isRequired, transaction: PropTypes.object.isRequired, + blockNumber: PropTypes.object.isRequired, isLocal: PropTypes.bool, stats: PropTypes.object }; @@ -122,7 +124,7 @@ export class Transaction extends BaseTransaction { Gas - First seen + First propagation # Propagated @@ -141,6 +143,7 @@ export class Transaction extends BaseTransaction { }); const noOfPeers = Object.keys(stats.propagatedTo).length; const noOfPropagations = Object.values(stats.propagatedTo).reduce((sum, val) => sum + val, 0); + const blockNo = new BigNumber(stats.firstSeen); return ( @@ -159,8 +162,8 @@ export class Transaction extends BaseTransaction { { this.renderGas(transaction) } - - { stats.firstSeen } + + { this.renderTime(stats.firstSeen) } { this.renderPropagation(stats) } @@ -168,6 +171,16 @@ export class Transaction extends BaseTransaction { ); } + + renderTime (firstSeen) { + const { blockNumber } = this.props; + if (!firstSeen) { + return 'never'; + } + + const timeInMinutes = blockNumber.sub(firstSeen).mul(14).div(60).toFormat(1); + return `${timeInMinutes} minutes ago`; + } } export class LocalTransaction extends BaseTransaction { @@ -200,9 +213,6 @@ export class LocalTransaction extends BaseTransaction { Gas Price / Gas - - Propagated - Status @@ -252,7 +262,9 @@ export class LocalTransaction extends BaseTransaction { }); const closeSending = () => this.setState({ - isSending: false + isSending: false, + gasPrice: null, + gas: null }); api.eth.sendTransaction(newTransaction) @@ -292,11 +304,10 @@ export class LocalTransaction extends BaseTransaction {
{ this.renderGas(transaction) } - - { status === 'pending' ? this.renderPropagation(stats) : '-' } - { this.renderStatus() } +
+ { status === 'pending' ? this.renderPropagation(stats) : null } ); From fc4b51fe683b5b37fb7610b0d825e787a22dca5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 17 Nov 2016 13:42:18 +0100 Subject: [PATCH 14/41] Prioritizing local transactions regardless of nonce --- ethcore/src/miner/transaction_queue.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 31c27982a..00fe47243 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -209,17 +209,16 @@ impl Ord for TransactionOrder { return self.penalties.cmp(&b.penalties); } - // First check nonce_height - if self.nonce_height != b.nonce_height { - return self.nonce_height.cmp(&b.nonce_height); - } - // Local transactions should always have priority - // NOTE nonce has to be checked first, cause otherwise the order might be wrong. if self.origin != b.origin { return self.origin.cmp(&b.origin); } + // Check nonce_height + if self.nonce_height != b.nonce_height { + return self.nonce_height.cmp(&b.nonce_height); + } + match self.strategy { PrioritizationStrategy::GasAndGasPrice => { if self.gas != b.gas { @@ -1790,8 +1789,8 @@ mod test { // then // the order should be updated assert_eq!(txq.top_transactions()[0], tx1); - assert_eq!(txq.top_transactions()[1], tx0); - assert_eq!(txq.top_transactions()[2], tx2); + assert_eq!(txq.top_transactions()[1], tx2); + assert_eq!(txq.top_transactions()[2], tx0); } #[test] @@ -2141,8 +2140,8 @@ mod test { let (tx5, tx6) = new_tx_pair_default(U256::from(1), U256::from(2)); txq.add(tx1.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); txq.add(tx2.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx5.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // Not accepted because of limit + txq.add(tx5.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap_err(); txq.add(tx6.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap_err(); txq.add(tx3.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); txq.add(tx4.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); From 5c62e38a7c0063c3393ad9282c49b5ccb53642d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 17 Nov 2016 13:48:25 +0100 Subject: [PATCH 15/41] Cleanup --- ethcore/src/client/client.rs | 9 +++------ ethcore/src/miner/miner.rs | 1 - ethcore/src/miner/transaction_queue.rs | 3 +-- js/src/views/Dapps/builtin.json | 1 - rpc/src/v1/types/transaction.rs | 1 - sync/build.rs | 1 - 6 files changed, 4 insertions(+), 12 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index bf318aeda..0e0b292f9 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -891,12 +891,9 @@ impl BlockChainClient for Client { let mut mode = self.mode.lock(); *mode = new_mode.clone().into(); trace!(target: "mode", "Mode now {:?}", &*mode); - match *self.on_mode_change.lock() { - Some(ref mut f) => { - trace!(target: "mode", "Making callback..."); - f(&*mode) - }, - _ => {} + if let Some(ref mut f) = *self.on_mode_change.lock() { + trace!(target: "mode", "Making callback..."); + f(&*mode) } } match new_mode { diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index df55e7367..5530ac462 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -38,7 +38,6 @@ use miner::price_info::PriceInfo; use miner::local_transactions::{Status as LocalTransactionStatus}; use header::BlockNumber; - /// Different possible definitions for pending transaction set. #[derive(Debug, PartialEq)] pub enum PendingSet { diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 00fe47243..bfbd3fade 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -2137,12 +2137,11 @@ mod test { let mut txq = TransactionQueue::with_limits(PrioritizationStrategy::GasPriceOnly, 100, default_gas_val() * U256::from(2), !U256::zero()); let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1)); let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2)); - let (tx5, tx6) = new_tx_pair_default(U256::from(1), U256::from(2)); + let (tx5, _) = new_tx_pair_default(U256::from(1), U256::from(2)); txq.add(tx1.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); txq.add(tx2.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); // Not accepted because of limit txq.add(tx5.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap_err(); - txq.add(tx6.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap_err(); txq.add(tx3.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); txq.add(tx4.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 4); diff --git a/js/src/views/Dapps/builtin.json b/js/src/views/Dapps/builtin.json index 71f3cd6ed..a2959bb18 100644 --- a/js/src/views/Dapps/builtin.json +++ b/js/src/views/Dapps/builtin.json @@ -49,5 +49,4 @@ "version": "1.0.0", "secure": true } - ] diff --git a/rpc/src/v1/types/transaction.rs b/rpc/src/v1/types/transaction.rs index 7379a3b19..45f1cdd5e 100644 --- a/rpc/src/v1/types/transaction.rs +++ b/rpc/src/v1/types/transaction.rs @@ -132,7 +132,6 @@ impl Serialize for LocalTransactionStatus { } } - impl From for Transaction { fn from(t: LocalizedTransaction) -> Transaction { let signature = t.signature(); diff --git a/sync/build.rs b/sync/build.rs index db881e328..c465d5e34 100644 --- a/sync/build.rs +++ b/sync/build.rs @@ -18,5 +18,4 @@ extern crate ethcore_ipc_codegen; fn main() { ethcore_ipc_codegen::derive_ipc_cond("src/api.rs", cfg!(feature="ipc")).unwrap(); - ethcore_ipc_codegen::derive_ipc_cond("src/transactions_stats.rs", cfg!(feature="ipc")).unwrap(); } From be6eb79296809b6907dc0ae25b731f58bd4c6055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 17 Nov 2016 14:00:53 +0100 Subject: [PATCH 16/41] Fixing JS lints --- js/src/api/rpc/parity/parity.js | 4 +- .../dapps/localtx/Application/application.js | 2 +- .../dapps/localtx/Transaction/transaction.js | 46 ++++++++++++------- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index 2c90d091b..d14cc6554 100644 --- a/js/src/api/rpc/parity/parity.js +++ b/js/src/api/rpc/parity/parity.js @@ -135,7 +135,9 @@ export default class Parity { .then(transactions => { Object.values(transactions) .filter(tx => tx.transaction) - .map(tx => tx.transaction = outTransaction(tx.transaction)); + .map(tx => { + tx.transaction = outTransaction(tx.transaction); + }); return transactions; }); } diff --git a/js/src/dapps/localtx/Application/application.js b/js/src/dapps/localtx/Application/application.js index a6e6cd166..89b0b73b4 100644 --- a/js/src/dapps/localtx/Application/application.js +++ b/js/src/dapps/localtx/Application/application.js @@ -157,7 +157,7 @@ export default class Application extends Component { transactions.map((tx, idx) => ( + { api.util.fromWei(transaction.gasPrice, 'shannon').toFormat(2) } shannon ); @@ -72,7 +72,7 @@ class BaseTransaction extends Component { return ( - { transaction.gas.div(10**6).toFormat(3) } MGas + { transaction.gas.div(10 ** 6).toFormat(3) } MGas ); } @@ -137,13 +137,11 @@ export class Transaction extends BaseTransaction { render () { const { isLocal, stats, transaction, idx } = this.props; + const blockNo = new BigNumber(stats.firstSeen); const clazz = classnames(styles.transaction, { [styles.local]: isLocal }); - const noOfPeers = Object.keys(stats.propagatedTo).length; - const noOfPropagations = Object.values(stats.propagatedTo).reduce((sum, val) => sum + val, 0); - const blockNo = new BigNumber(stats.firstSeen); return ( @@ -232,17 +230,29 @@ export class LocalTransaction extends BaseTransaction { const { isResubmitting, gasPrice } = this.state; this.setState({ - isResubmitting: !isResubmitting, + isResubmitting: !isResubmitting }); if (gasPrice === null) { this.setState({ - gasPrice: `0x${ transaction.gasPrice.toString(16) }`, - gas: `0x${ transaction.gas.toString(16) }` + gasPrice: `0x${transaction.gasPrice.toString(16)}`, + gas: `0x${transaction.gas.toString(16)}` }); } }; + setGasPrice = el => { + this.setState({ + gasPrice: el.target.value + }); + }; + + setGas = el => { + this.setState({ + gas: el.target.value + }); + }; + sendTransaction = () => { const { transaction } = this.props; const { gasPrice, gas } = this.state; @@ -317,18 +327,20 @@ export class LocalTransaction extends BaseTransaction { const { details } = this.props; let state = { - 'pending': () => `In queue: Pending`, - 'future': () => `In queue: Future`, - 'mined': () => `Mined`, - 'dropped': () => `Dropped because of queue limit`, - 'invalid': () => `Transaction is invalid`, - 'rejected': () => `Rejected: ${ details.error }`, - 'replaced': () => `Replaced by ${ this.shortHash(details.hash) }`, + 'pending': () => 'In queue: Pending', + 'future': () => 'In queue: Future', + 'mined': () => 'Mined', + 'dropped': () => 'Dropped because of queue limit', + 'invalid': () => 'Transaction is invalid', + 'rejected': () => `Rejected: ${details.error}`, + 'replaced': () => `Replaced by ${this.shortHash(details.hash)}` }[this.props.status]; return state ? state() : 'unknown'; } + // TODO [ToDr] Gas Price / Gas selection is not needed + // when signer supports gasPrice/gas tunning. renderResubmit () { const { transaction } = this.props; const { gasPrice, gas } = this.state; @@ -350,12 +362,12 @@ export class LocalTransaction extends BaseTransaction { this.setState({ gasPrice: el.target.value }) } + onChange={ this.setGasPrice } /> this.setState({ gas: el.target.value }) } + onChange={ this.setGas } /> From 45c7a285856609d5845f1e99311c48d2f269eb68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 17 Nov 2016 14:13:59 +0100 Subject: [PATCH 17/41] Fixing JS tests --- .../localtx/Transaction/transaction.spec.js | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/js/src/dapps/localtx/Transaction/transaction.spec.js b/js/src/dapps/localtx/Transaction/transaction.spec.js index 1fe78124c..5a9e1a8e9 100644 --- a/js/src/dapps/localtx/Transaction/transaction.spec.js +++ b/js/src/dapps/localtx/Transaction/transaction.spec.js @@ -19,15 +19,38 @@ import { shallow } from 'enzyme'; import '../../../environment/tests'; -import Transaction from './transaction'; +import BigNumber from 'bignumber.js'; +import { Transaction, LocalTransaction } from './transaction'; describe('localtx/Transaction', () => { describe('rendering', () => { it('renders without crashing', () => { + const transaction = { + hash: '0x1234567890', + nonce: 15, + gasPrice: new BigNumber(10), + gas: new BigNumber(10) + }; const rendered = shallow( + ); + + expect(rendered).to.be.defined; + }); + }); +}); + +describe('localtx/LocalTransaction', () => { + describe('rendering', () => { + it('renders without crashing', () => { + const rendered = shallow( + ); From afef5b5316dab3445aaefd1991e95a8042a3da15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 17 Nov 2016 14:30:53 +0100 Subject: [PATCH 18/41] Mocking API for tests --- js/src/dapps/localtx/Transaction/transaction.spec.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/js/src/dapps/localtx/Transaction/transaction.spec.js b/js/src/dapps/localtx/Transaction/transaction.spec.js index 5a9e1a8e9..5e9c39147 100644 --- a/js/src/dapps/localtx/Transaction/transaction.spec.js +++ b/js/src/dapps/localtx/Transaction/transaction.spec.js @@ -18,6 +18,13 @@ import React from 'react'; import { shallow } from 'enzyme'; import '../../../environment/tests'; +import EthApi from '../../../api'; + +// Mock API for tests +import * as Api from '../parity'; +Api.api = { + util: EthApi.prototype.util +}; import BigNumber from 'bignumber.js'; import { Transaction, LocalTransaction } from './transaction'; From 0c26eddd7fb739d92af4a2b9618fb2f081299b34 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 17 Nov 2016 19:37:08 +0100 Subject: [PATCH 19/41] Better GHH event display & tracking --- .../githubhint/Application/application.css | 24 ++- .../githubhint/Application/application.js | 171 ++++++++++++++---- js/src/dapps/githubhint/Events/events.css | 37 ++++ js/src/dapps/githubhint/Events/events.js | 50 +++++ js/src/dapps/githubhint/Events/index.js | 17 ++ 5 files changed, 256 insertions(+), 43 deletions(-) create mode 100644 js/src/dapps/githubhint/Events/events.css create mode 100644 js/src/dapps/githubhint/Events/events.js create mode 100644 js/src/dapps/githubhint/Events/index.js diff --git a/js/src/dapps/githubhint/Application/application.css b/js/src/dapps/githubhint/Application/application.css index be04ecf34..61929f552 100644 --- a/js/src/dapps/githubhint/Application/application.css +++ b/js/src/dapps/githubhint/Application/application.css @@ -15,12 +15,17 @@ /* along with Parity. If not, see . */ -.container { +.body { + text-align: center; background: #333; + color: #fff; +} + +.container { font-family: 'Roboto'; vertical-align: middle; padding: 4em 0; - text-align: center; + margin: 0 0 2em 0; } .form { @@ -98,7 +103,7 @@ color: #333; background: #eee; border: none; - border-radius: 5px; + border-radius: 0.5em; width: 100%; font-size: 1em; text-align: center; @@ -113,20 +118,29 @@ } .hashError, .hashWarning, .hashOk { - padding-top: 0.5em; + margin: 0.5em 0; text-align: center; + padding: 1em 0; + border: 0.25em solid #333; + border-radius: 0.5em; } .hashError { + border-color: #f66; color: #f66; + background: rgba(255, 102, 102, 0.25); } .hashWarning { + border-color: #f80; color: #f80; + background: rgba(255, 236, 0, 0.25); } .hashOk { - opacity: 0.5; + border-color: #6f6; + color: #6f6; + background: rgba(102, 255, 102, 0.25); } .typeButtons { diff --git a/js/src/dapps/githubhint/Application/application.js b/js/src/dapps/githubhint/Application/application.js index 5a7494928..1690bf1c4 100644 --- a/js/src/dapps/githubhint/Application/application.js +++ b/js/src/dapps/githubhint/Application/application.js @@ -19,6 +19,7 @@ import React, { Component } from 'react'; import { api } from '../parity'; import { attachInterface } from '../services'; import Button from '../Button'; +import Events from '../Events'; import IdentityIcon from '../IdentityIcon'; import Loading from '../Loading'; @@ -27,6 +28,8 @@ import styles from './application.css'; const INVALID_URL_HASH = '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'; const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; +let nextEventId = 0; + export default class Application extends Component { state = { fromAddress: null, @@ -43,7 +46,9 @@ export default class Application extends Component { registerState: '', registerType: 'file', repo: '', - repoError: null + repoError: null, + events: {}, + eventIds: [] } componentDidMount () { @@ -75,7 +80,7 @@ export default class Application extends Component { let hashClass = null; if (contentHashError) { hashClass = contentHashOwner !== fromAddress ? styles.hashError : styles.hashWarning; - } else { + } else if (contentHash) { hashClass = styles.hashOk; } @@ -116,29 +121,34 @@ export default class Application extends Component { } return ( -
-
-
- - -
-
-
- Provide a valid URL to register. The content information can be used in other contracts that allows for reverse lookups, e.g. image registries, dapp registries, etc. +
+
+
+
+ +
- { valueInputs } -
- { contentHashError || contentHash } +
+
+ Provide a valid URL to register. The content information can be used in other contracts that allows for reverse lookups, e.g. image registries, dapp registries, etc. +
+ { valueInputs } +
+ { contentHashError || contentHash } +
+ { registerBusy ? this.renderProgress() : this.renderButtons() }
- { registerBusy ? this.renderProgress() : this.renderButtons() }
+
); } @@ -285,15 +295,29 @@ export default class Application extends Component { } } - trackRequest (promise) { + trackRequest (eventId, promise) { return promise .then((signerRequestId) => { - this.setState({ signerRequestId, registerState: 'Transaction posted, Waiting for transaction authorization' }); + this.setState({ + events: Object.assign({}, this.state.events, { + [eventId]: Object.assign({}, this.state.events[eventId], { + signerRequestId, + registerState: 'Transaction posted, Waiting for transaction authorization' + }) + }) + }); return api.pollMethod('parity_checkRequest', signerRequestId); }) .then((txHash) => { - this.setState({ txHash, registerState: 'Transaction authorized, Waiting for network confirmations' }); + this.setState({ + events: Object.assign({}, this.state.events, { + [eventId]: Object.assign({}, this.state.events[eventId], { + txHash, + registerState: 'Transaction authorized, Waiting for network confirmations' + }) + }) + }); return api.pollMethod('eth_getTransactionReceipt', txHash, (receipt) => { if (!receipt || !receipt.blockNumber || receipt.blockNumber.eq(0)) { @@ -304,27 +328,72 @@ export default class Application extends Component { }); }) .then((txReceipt) => { - this.setState({ txReceipt, registerBusy: false, registerState: 'Network confirmed, Received transaction receipt', url: '', commit: '', repo: '', commitError: null, contentHash: '', contentHashOwner: null, contentHashError: null }); + this.setState({ + events: Object.assign({}, this.state.events, { + [eventId]: Object.assign({}, this.state.events[eventId], { + txReceipt, + registerBusy: false, + registerState: 'Network confirmed, Received transaction receipt' + }) + }) + }); }) .catch((error) => { console.error('onSend', error); - this.setState({ registerError: error.message }); + + this.setState({ + events: Object.assign({}, this.state.events, { + [eventId]: Object.assign({}, this.state.events[eventId], { + registerState: error.message, + registerError: true, + registerBusy: false + }) + }) + }); }); } - registerContent (repo, commit) { + registerContent (contentRepo, contentCommit) { const { contentHash, fromAddress, instance } = this.state; + contentCommit = contentCommit.substr(0, 2) === '0x' ? contentCommit : `0x${contentCommit}`; - this.setState({ registerBusy: true, registerState: 'Estimating gas for the transaction' }); - - const values = [contentHash, repo, commit.substr(0, 2) === '0x' ? commit : `0x${commit}`]; + const eventId = nextEventId++; + const values = [contentHash, contentRepo, contentCommit]; const options = { from: fromAddress }; + this.setState({ + eventIds: [eventId].concat(this.state.eventIds), + events: Object.assign({}, this.state.events, { + [eventId]: { + contentHash, + contentRepo, + contentCommit, + fromAddress, + registerBusy: true, + registerState: 'Estimating gas for the transaction', + timestamp: new Date() + } + }), + url: '', + commit: '', + repo: '', + commitError: null, + contentHash: '', + contentHashOwner: null, + contentHashError: null + }); + this.trackRequest( - instance + eventId, instance .hint.estimateGas(options, values) .then((gas) => { - this.setState({ registerState: 'Gas estimated, Posting transaction to the network' }); + this.setState({ + events: Object.assign({}, this.state.events, { + [eventId]: Object.assign({}, this.state.events[eventId], { + registerState: 'Gas estimated, Posting transaction to the network' + }) + }) + }); const gasPassed = gas.mul(1.2); options.gas = gasPassed.toFixed(0); @@ -335,19 +404,45 @@ export default class Application extends Component { ); } - registerUrl (url) { + registerUrl (contentUrl) { const { contentHash, fromAddress, instance } = this.state; - this.setState({ registerBusy: true, registerState: 'Estimating gas for the transaction' }); - - const values = [contentHash, url]; + const eventId = nextEventId++; + const values = [contentHash, contentUrl]; const options = { from: fromAddress }; + this.setState({ + eventIds: [eventId].concat(this.state.eventIds), + events: Object.assign({}, this.state.events, { + [eventId]: { + contentHash, + contentUrl, + fromAddress, + registerBusy: true, + registerState: 'Estimating gas for the transaction', + timestamp: new Date() + } + }), + url: '', + commit: '', + repo: '', + commitError: null, + contentHash: '', + contentHashOwner: null, + contentHashError: null + }); + this.trackRequest( - instance + eventId, instance .hintURL.estimateGas(options, values) .then((gas) => { - this.setState({ registerState: 'Gas estimated, Posting transaction to the network' }); + this.setState({ + events: Object.assign({}, this.state.events, { + [eventId]: Object.assign({}, this.state.events[eventId], { + registerState: 'Gas estimated, Posting transaction to the network' + }) + }) + }); const gasPassed = gas.mul(1.2); options.gas = gasPassed.toFixed(0); diff --git a/js/src/dapps/githubhint/Events/events.css b/js/src/dapps/githubhint/Events/events.css new file mode 100644 index 000000000..a33c09236 --- /dev/null +++ b/js/src/dapps/githubhint/Events/events.css @@ -0,0 +1,37 @@ +/* Copyright 2015, 2016 Ethcore (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 . +*/ + +.list { + border: none; + margin: 0 auto; + text-align: left; + vertical-align: top; + + tr { + &[data-busy="true"] { + opacity: 0.5; + } + + &[data-error="true"] { + color: #f66; + } + } + + td { + padding: 0.5em; + } +} diff --git a/js/src/dapps/githubhint/Events/events.js b/js/src/dapps/githubhint/Events/events.js new file mode 100644 index 000000000..f3b48292b --- /dev/null +++ b/js/src/dapps/githubhint/Events/events.js @@ -0,0 +1,50 @@ +// Copyright 2015, 2016 Ethcore (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 moment from 'moment'; + +import styles from './events.css'; + +export default class Events extends Component { + static propTypes = { + eventIds: PropTypes.array.isRequired, + events: PropTypes.array.isRequired + } + + render () { + return ( + + { this.props.eventIds.map((id) => this.renderEvent(id, this.props.events[id])) } +
+ ); + } + + renderEvent = (eventId, event) => { + return ( + + +
{ moment(event.timestamp).fromNow() }
+
{ event.registerState }
+ + +
{ event.contentUrl || `${event.contentRepo}/${event.contentCommit}` }
+
{ event.contentHash }
+ + + ); + } +} diff --git a/js/src/dapps/githubhint/Events/index.js b/js/src/dapps/githubhint/Events/index.js new file mode 100644 index 000000000..88ad6d407 --- /dev/null +++ b/js/src/dapps/githubhint/Events/index.js @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Ethcore (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 './events'; From 5897e01dbdc542a7a12af249c654ab47c0365d8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 18 Nov 2016 11:03:29 +0100 Subject: [PATCH 20/41] Correct format of eth_signTransaction --- rpc/src/v1/helpers/dispatch.rs | 4 ++-- rpc/src/v1/impls/eth.rs | 4 ++++ rpc/src/v1/impls/signing.rs | 5 +++-- rpc/src/v1/impls/signing_unsafe.rs | 5 +++-- rpc/src/v1/tests/mocked/eth.rs | 26 ++++++++++++++++++++++---- rpc/src/v1/tests/mocked/signing.rs | 20 ++++++++++++++++++-- rpc/src/v1/traits/eth.rs | 4 ++++ rpc/src/v1/traits/eth_signing.rs | 8 ++++---- rpc/src/v1/types/confirmations.rs | 6 +++--- rpc/src/v1/types/mod.rs.in | 2 +- rpc/src/v1/types/transaction.rs | 22 +++++++++++++++++++++- 11 files changed, 85 insertions(+), 21 deletions(-) diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index cbae49cc3..a66bc816d 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -28,6 +28,7 @@ use jsonrpc_core::Error; use v1::helpers::{errors, TransactionRequest, FilledTransactionRequest, ConfirmationPayload}; use v1::types::{ H256 as RpcH256, H520 as RpcH520, Bytes as RpcBytes, + RichRawTransaction as RpcRichRawTransaction, ConfirmationPayload as RpcConfirmationPayload, ConfirmationResponse, SignRequest as RpcSignRequest, @@ -47,8 +48,7 @@ pub fn execute(client: &C, miner: &M, accounts: &AccountProvider, payload: }, ConfirmationPayload::SignTransaction(request) => { sign_no_dispatch(client, miner, accounts, request, pass) - .map(|tx| rlp::encode(&tx).to_vec()) - .map(RpcBytes) + .map(RpcRichRawTransaction::from) .map(ConfirmationResponse::SignTransaction) }, ConfirmationPayload::Signature(address, hash) => { diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 8207426ba..5f1449e07 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -619,6 +619,10 @@ impl Eth for EthClient where } } + fn submit_transaction(&self, raw: Bytes) -> Result { + self.send_raw_transaction(raw) + } + fn call(&self, request: CallRequest, num: Trailing) -> Result { try!(self.active()); diff --git a/rpc/src/v1/impls/signing.rs b/rpc/src/v1/impls/signing.rs index cf20f1045..262e04dfb 100644 --- a/rpc/src/v1/impls/signing.rs +++ b/rpc/src/v1/impls/signing.rs @@ -34,6 +34,7 @@ use v1::traits::{EthSigning, ParitySigning}; use v1::types::{ H160 as RpcH160, H256 as RpcH256, U256 as RpcU256, Bytes as RpcBytes, H520 as RpcH520, Either as RpcEither, + RichRawTransaction as RpcRichRawTransaction, TransactionRequest as RpcTransactionRequest, ConfirmationPayload as RpcConfirmationPayload, ConfirmationResponse as RpcConfirmationResponse @@ -201,11 +202,11 @@ impl EthSigning for SigningQueueClient where }); } - fn sign_transaction(&self, ready: Ready, request: RpcTransactionRequest) { + fn sign_transaction(&self, ready: Ready, request: RpcTransactionRequest) { let res = self.active().and_then(|_| self.dispatch(RpcConfirmationPayload::SignTransaction(request))); self.handle_dispatch(res, |response| { match response { - Ok(RpcConfirmationResponse::SignTransaction(rlp)) => ready.ready(Ok(rlp)), + Ok(RpcConfirmationResponse::SignTransaction(tx)) => ready.ready(Ok(tx)), Err(e) => ready.ready(Err(e)), e => ready.ready(Err(errors::internal("Unexpected result.", e))), } diff --git a/rpc/src/v1/impls/signing_unsafe.rs b/rpc/src/v1/impls/signing_unsafe.rs index da9aeb901..46ffe6ded 100644 --- a/rpc/src/v1/impls/signing_unsafe.rs +++ b/rpc/src/v1/impls/signing_unsafe.rs @@ -31,6 +31,7 @@ use v1::types::{ U256 as RpcU256, H160 as RpcH160, H256 as RpcH256, H520 as RpcH520, Bytes as RpcBytes, Either as RpcEither, + RichRawTransaction as RpcRichRawTransaction, TransactionRequest as RpcTransactionRequest, ConfirmationPayload as RpcConfirmationPayload, ConfirmationResponse as RpcConfirmationResponse, @@ -100,9 +101,9 @@ impl EthSigning for SigningUnsafeClient where ready.ready(result); } - fn sign_transaction(&self, ready: Ready, request: RpcTransactionRequest) { + fn sign_transaction(&self, ready: Ready, request: RpcTransactionRequest) { let result = match self.handle(RpcConfirmationPayload::SignTransaction(request)) { - Ok(RpcConfirmationResponse::SignTransaction(rlp)) => Ok(rlp), + Ok(RpcConfirmationResponse::SignTransaction(tx)) => Ok(tx), Err(e) => Err(e), e => Err(errors::internal("Unexpected result", e)), }; diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index 67e77a6db..861bb5234 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -18,8 +18,10 @@ use std::str::FromStr; use std::collections::HashMap; use std::sync::Arc; use std::time::{Instant, Duration}; +use rustc_serialize::hex::ToHex; +use time::get_time; use rlp; -use jsonrpc_core::IoHandler; + use util::{Uint, U256, Address, H256, FixedHash, Mutex}; use ethcore::account_provider::AccountProvider; use ethcore::client::{TestBlockChainClient, EachBlockWith, Executed, TransactionID}; @@ -28,10 +30,10 @@ use ethcore::receipt::LocalizedReceipt; use ethcore::transaction::{Transaction, Action}; use ethcore::miner::{ExternalMiner, MinerService}; use ethsync::SyncState; + +use jsonrpc_core::IoHandler; use v1::{Eth, EthClient, EthClientOptions, EthFilter, EthFilterClient, EthSigning, SigningUnsafeClient}; use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService, TestSnapshotService}; -use rustc_serialize::hex::ToHex; -use time::get_time; fn blockchain_client() -> Arc { let client = TestBlockChainClient::new(); @@ -798,9 +800,25 @@ fn rpc_eth_sign_transaction() { }; let signature = tester.accounts_provider.sign(address, None, t.hash(None)).unwrap(); let t = t.with_signature(signature, None); + let signature = t.signature(); let rlp = rlp::encode(&t); - let response = r#"{"jsonrpc":"2.0","result":"0x"#.to_owned() + &rlp.to_hex() + r#"","id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"#.to_owned() + + r#""raw":"0x"# + &rlp.to_hex() + r#"","# + + r#""tx":{"# + + r#""blockHash":null,"blockNumber":null,"creates":null,"# + + &format!("\"from\":\"0x{:?}\",", &address) + + r#""gas":"0x76c0","gasPrice":"0x9184e72a000","# + + &format!("\"hash\":\"0x{:?}\",", t.hash()) + + r#""input":"0x","nonce":"0x1","# + + &format!("\"publicKey\":\"0x{:?}\",", t.public_key().unwrap()) + + &format!("\"r\":\"0x{}\",", signature.r().to_hex()) + + &format!("\"raw\":\"0x{}\",", rlp.to_hex()) + + &format!("\"s\":\"0x{}\",", signature.s().to_hex()) + + r#""to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","transactionIndex":null,"# + + &format!("\"v\":{},", signature.v()) + + r#""value":"0x9184e72a""# + + r#"}},"id":1}"#; tester.miner.last_nonces.write().insert(address.clone(), U256::zero()); diff --git a/rpc/src/v1/tests/mocked/signing.rs b/rpc/src/v1/tests/mocked/signing.rs index b43c1e180..7431bc45e 100644 --- a/rpc/src/v1/tests/mocked/signing.rs +++ b/rpc/src/v1/tests/mocked/signing.rs @@ -268,16 +268,32 @@ fn should_add_sign_transaction_to_the_queue() { }; let signature = tester.accounts.sign(address, Some("test".into()), t.hash(None)).unwrap(); let t = t.with_signature(signature, None); + let signature = t.signature(); let rlp = rlp::encode(&t); - let response = r#"{"jsonrpc":"2.0","result":"0x"#.to_owned() + &rlp.to_hex() + r#"","id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"#.to_owned() + + r#""raw":"0x"# + &rlp.to_hex() + r#"","# + + r#""tx":{"# + + r#""blockHash":null,"blockNumber":null,"creates":null,"# + + &format!("\"from\":\"0x{:?}\",", &address) + + r#""gas":"0x76c0","gasPrice":"0x9184e72a000","# + + &format!("\"hash\":\"0x{:?}\",", t.hash()) + + r#""input":"0x","nonce":"0x1","# + + &format!("\"publicKey\":\"0x{:?}\",", t.public_key().unwrap()) + + &format!("\"r\":\"0x{}\",", signature.r().to_hex()) + + &format!("\"raw\":\"0x{}\",", rlp.to_hex()) + + &format!("\"s\":\"0x{}\",", signature.s().to_hex()) + + r#""to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","transactionIndex":null,"# + + &format!("\"v\":{},", signature.v()) + + r#""value":"0x9184e72a""# + + r#"}},"id":1}"#; // then tester.miner.last_nonces.write().insert(address.clone(), U256::zero()); let async_result = tester.io.handle_request(&request).unwrap(); assert_eq!(tester.signer.requests().len(), 1); // respond - tester.signer.request_confirmed(1.into(), Ok(ConfirmationResponse::SignTransaction(rlp.to_vec().into()))); + tester.signer.request_confirmed(1.into(), Ok(ConfirmationResponse::SignTransaction(t.into()))); assert!(async_result.on_result(move |res| { assert_eq!(res, response.to_owned()); })); diff --git a/rpc/src/v1/traits/eth.rs b/rpc/src/v1/traits/eth.rs index b524c616f..6308be324 100644 --- a/rpc/src/v1/traits/eth.rs +++ b/rpc/src/v1/traits/eth.rs @@ -102,6 +102,10 @@ build_rpc_trait! { #[rpc(name = "eth_sendRawTransaction")] fn send_raw_transaction(&self, Bytes) -> Result; + /// Alias of `eth_sendRawTransaction`. + #[rpc(name = "eth_submitTransaction")] + fn submit_transaction(&self, Bytes) -> Result; + /// Call contract, returning the output data. #[rpc(name = "eth_call")] fn call(&self, CallRequest, Trailing) -> Result; diff --git a/rpc/src/v1/traits/eth_signing.rs b/rpc/src/v1/traits/eth_signing.rs index bd1d1ecdc..09f8c5e03 100644 --- a/rpc/src/v1/traits/eth_signing.rs +++ b/rpc/src/v1/traits/eth_signing.rs @@ -17,7 +17,7 @@ //! Eth rpc interface. use v1::helpers::auto_args::{WrapAsync, Ready}; -use v1::types::{H160, H256, H520, TransactionRequest, Bytes}; +use v1::types::{H160, H256, H520, TransactionRequest, RichRawTransaction}; build_rpc_trait! { /// Signing methods implementation relying on unlocked accounts. @@ -33,9 +33,9 @@ build_rpc_trait! { fn send_transaction(&self, Ready, TransactionRequest); /// Signs transactions without dispatching it to the network. - /// Returns signed transaction RLP representation. - /// It can be later submitted using `eth_sendRawTransaction`. + /// Returns signed transaction RLP representation and the transaction itself. + /// It can be later submitted using `eth_sendRawTransaction/eth_submitTransaction`. #[rpc(async, name = "eth_signTransaction")] - fn sign_transaction(&self, Ready, TransactionRequest); + fn sign_transaction(&self, Ready, TransactionRequest); } } diff --git a/rpc/src/v1/types/confirmations.rs b/rpc/src/v1/types/confirmations.rs index e83989326..2b7813df9 100644 --- a/rpc/src/v1/types/confirmations.rs +++ b/rpc/src/v1/types/confirmations.rs @@ -18,7 +18,7 @@ use std::fmt; use serde::{Serialize, Serializer}; -use v1::types::{U256, TransactionRequest, H160, H256, H520, Bytes}; +use v1::types::{U256, TransactionRequest, RichRawTransaction, H160, H256, H520, Bytes}; use v1::helpers; /// Confirmation waiting in a queue @@ -76,12 +76,12 @@ impl From<(H160, Bytes)> for DecryptRequest { } /// Confirmation response for particular payload -#[derive(Debug, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Clone, PartialEq)] pub enum ConfirmationResponse { /// Transaction Hash SendTransaction(H256), /// Transaction RLP - SignTransaction(Bytes), + SignTransaction(RichRawTransaction), /// Signature Signature(H520), /// Decrypted data diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index b1e4ae2c9..924f12884 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -44,7 +44,7 @@ pub use self::hash::{H64, H160, H256, H512, H520, H2048}; pub use self::index::Index; pub use self::log::Log; pub use self::sync::{SyncStatus, SyncInfo, Peers, PeerInfo, PeerNetworkInfo, PeerProtocolsInfo, PeerEthereumProtocolInfo}; -pub use self::transaction::Transaction; +pub use self::transaction::{Transaction, RichRawTransaction}; pub use self::transaction_request::TransactionRequest; pub use self::receipt::Receipt; pub use self::rpc_settings::RpcSettings; diff --git a/rpc/src/v1/types/transaction.rs b/rpc/src/v1/types/transaction.rs index 0982a5ef9..1f1ef1787 100644 --- a/rpc/src/v1/types/transaction.rs +++ b/rpc/src/v1/types/transaction.rs @@ -19,7 +19,7 @@ use ethcore::transaction::{LocalizedTransaction, Action, SignedTransaction}; use v1::types::{Bytes, H160, H256, U256, H512}; /// Transaction -#[derive(Debug, Default, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Serialize)] pub struct Transaction { /// Hash pub hash: H256, @@ -62,6 +62,26 @@ pub struct Transaction { pub s: H256, } +/// Geth-compatible output for eth_signTransaction method +#[derive(Debug, Default, Clone, PartialEq, Serialize)] +pub struct RichRawTransaction { + /// Raw transaction RLP + pub raw: Bytes, + /// Transaction details + #[serde(rename="tx")] + pub transaction: Transaction +} + +impl From for RichRawTransaction { + fn from(t: SignedTransaction) -> Self { + let tx: Transaction = t.into(); + RichRawTransaction { + raw: tx.raw.clone(), + transaction: tx, + } + } +} + impl From for Transaction { fn from(t: LocalizedTransaction) -> Transaction { let signature = t.signature(); From ffc94343fe7898de2828e398e908af8796474894 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Fri, 18 Nov 2016 17:44:40 +0100 Subject: [PATCH 21/41] Fix version for NPM --- js/package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/js/package.json b/js/package.json index eb6ad91b4..d3906ba4f 100644 --- a/js/package.json +++ b/js/package.json @@ -104,7 +104,7 @@ "raw-loader": "^0.5.1", "react-addons-test-utils": "^15.3.0", "react-copy-to-clipboard": "^4.2.3", - "react-hot-loader": "^1.3.0", + "react-hot-loader": "~1.3.0", "rucksack-css": "^0.8.6", "sinon": "^1.17.4", "sinon-as-promised": "^4.0.2", @@ -114,7 +114,7 @@ "webpack": "^1.13.2", "webpack-dev-server": "^1.15.2", "webpack-error-notification": "0.1.6", - "webpack-hot-middleware": "^2.7.1", + "webpack-hot-middleware": "~2.13.2", "websocket": "^1.0.23" }, "dependencies": { @@ -134,7 +134,7 @@ "js-sha3": "^0.5.2", "lodash": "^4.11.1", "marked": "^0.3.6", - "material-ui": "^0.16.1", + "material-ui": "0.16.1", "material-ui-chip-input": "^0.8.0", "mobx": "^2.6.1", "mobx-react": "^3.5.8", @@ -142,16 +142,16 @@ "moment": "^2.14.1", "phoneformat.js": "^1.0.3", "qs": "^6.3.0", - "react": "^15.2.1", + "react": "~15.3.2", "react-ace": "^4.0.0", - "react-addons-css-transition-group": "^15.2.1", + "react-addons-css-transition-group": "~15.3.2", "react-chartjs-2": "^1.5.0", - "react-dom": "^15.2.1", + "react-dom": "~15.3.2", "react-dropzone": "^3.7.3", "react-redux": "^4.4.5", "react-router": "^2.6.1", "react-router-redux": "^4.0.5", - "react-tap-event-plugin": "^1.0.0", + "react-tap-event-plugin": "~1.0.0", "react-tooltip": "^2.0.3", "recharts": "^0.15.2", "redux": "^3.5.2", From 15702a875c193359e6617c017dec3f1474a943d5 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Fri, 18 Nov 2016 17:45:00 +0100 Subject: [PATCH 22/41] Limit sync reorg (#3509) --- sync/src/block_sync.rs | 31 +++++++++++++++++++++---------- sync/src/chain.rs | 1 + 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/sync/src/block_sync.rs b/sync/src/block_sync.rs index 7c3cbf2d7..ff5411140 100644 --- a/sync/src/block_sync.rs +++ b/sync/src/block_sync.rs @@ -34,6 +34,7 @@ const MAX_RECEPITS_TO_REQUEST: usize = 128; const SUBCHAIN_SIZE: u64 = 256; const MAX_ROUND_PARENTS: usize = 32; const MAX_PARALLEL_SUBCHAIN_DOWNLOAD: usize = 5; +const MAX_REORG_BLOCKS: u64 = 20; #[derive(Copy, Clone, Eq, PartialEq, Debug)] /// Downloader state @@ -262,7 +263,8 @@ impl BlockDownloader { State::Blocks => { let count = headers.len(); // At least one of the heades must advance the subchain. Otherwise they are all useless. - if !any_known { + if count == 0 || !any_known { + trace!(target: "sync", "No useful headers"); return Err(BlockDownloaderImportError::Useless); } self.blocks.insert_headers(headers); @@ -340,14 +342,21 @@ impl BlockDownloader { self.last_imported_hash = p.clone(); trace!(target: "sync", "Searching common header from the last round {} ({})", self.last_imported_block, self.last_imported_hash); } else { - match io.chain().block_hash(BlockID::Number(self.last_imported_block - 1)) { - Some(h) => { - self.last_imported_block -= 1; - self.last_imported_hash = h; - trace!(target: "sync", "Searching common header in the blockchain {} ({})", self.last_imported_block, self.last_imported_hash); - } - None => { - debug!(target: "sync", "Could not revert to previous block, last: {} ({})", self.last_imported_block, self.last_imported_hash); + let best = io.chain().chain_info().best_block_number; + if best > self.last_imported_block && best - self.last_imported_block > MAX_REORG_BLOCKS { + debug!(target: "sync", "Could not revert to previous ancient block, last: {} ({})", self.last_imported_block, self.last_imported_hash); + self.reset(); + } else { + match io.chain().block_hash(BlockID::Number(self.last_imported_block - 1)) { + Some(h) => { + self.last_imported_block -= 1; + self.last_imported_hash = h; + trace!(target: "sync", "Searching common header in the blockchain {} ({})", self.last_imported_block, self.last_imported_hash); + } + None => { + debug!(target: "sync", "Could not revert to previous block, last: {} ({})", self.last_imported_block, self.last_imported_hash); + self.reset(); + } } } } @@ -362,7 +371,9 @@ impl BlockDownloader { match self.state { State::Idle => { self.start_sync_round(io); - return self.request_blocks(io, num_active_peers); + if self.state == State::ChainHead { + return self.request_blocks(io, num_active_peers); + } }, State::ChainHead => { if num_active_peers < MAX_PARALLEL_SUBCHAIN_DOWNLOAD { diff --git a/sync/src/chain.rs b/sync/src/chain.rs index d2939fbac..ffd89ecdd 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -1144,6 +1144,7 @@ impl ChainSync { let have_latest = io.chain().block_status(BlockID::Hash(peer_latest)) != BlockStatus::Unknown; if !have_latest && (higher_difficulty || force || self.state == SyncState::NewBlocks) { // check if got new blocks to download + trace!(target: "sync", "Syncing with {}, force={}, td={:?}, our td={}, state={:?}", peer_id, force, peer_difficulty, syncing_difficulty, self.state); if let Some(request) = self.new_blocks.request_blocks(io, num_active_peers) { self.request_blocks(io, peer_id, request, BlockSet::NewBlocks); if self.state == SyncState::Idle { From 4a3bdf13df3d27133f0ae324e6ead6d8bc9a8085 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Fri, 18 Nov 2016 17:45:19 +0100 Subject: [PATCH 23/41] Check transaction signature when adding to the queue (#3508) --- ethcore/src/miner/miner.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 84e29458d..8a87ea189 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -21,6 +21,7 @@ use util::*; use util::using_queue::{UsingQueue, GetAction}; use account_provider::AccountProvider; use views::{BlockView, HeaderView}; +use header::Header; use state::{State, CleanupMode}; use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics}; use executive::contract_address; @@ -572,7 +573,16 @@ impl Miner { let schedule = chain.latest_schedule(); let gas_required = |tx: &SignedTransaction| tx.gas_required(&schedule).into(); + let best_block_header: Header = ::rlp::decode(&chain.best_block_header()); transactions.into_iter() + .filter(|tx| match self.engine.verify_transaction_basic(tx, &best_block_header) { + Ok(()) => true, + Err(e) => { + debug!(target: "miner", "Rejected tx {:?} with invalid signature: {:?}", tx.hash(), e); + false + } + } + ) .map(|tx| match origin { TransactionOrigin::Local | TransactionOrigin::RetractedBlock => { transaction_queue.add(tx, origin, &fetch_account, &gas_required) From 51012d1faee0d967e001558d417057ad6c2fde45 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Fri, 18 Nov 2016 17:50:27 +0100 Subject: [PATCH 24/41] Revert "Limit sync reorganization to 20 blocks" (#3517) --- sync/src/block_sync.rs | 31 ++++++++++--------------------- sync/src/chain.rs | 1 - 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/sync/src/block_sync.rs b/sync/src/block_sync.rs index ff5411140..7c3cbf2d7 100644 --- a/sync/src/block_sync.rs +++ b/sync/src/block_sync.rs @@ -34,7 +34,6 @@ const MAX_RECEPITS_TO_REQUEST: usize = 128; const SUBCHAIN_SIZE: u64 = 256; const MAX_ROUND_PARENTS: usize = 32; const MAX_PARALLEL_SUBCHAIN_DOWNLOAD: usize = 5; -const MAX_REORG_BLOCKS: u64 = 20; #[derive(Copy, Clone, Eq, PartialEq, Debug)] /// Downloader state @@ -263,8 +262,7 @@ impl BlockDownloader { State::Blocks => { let count = headers.len(); // At least one of the heades must advance the subchain. Otherwise they are all useless. - if count == 0 || !any_known { - trace!(target: "sync", "No useful headers"); + if !any_known { return Err(BlockDownloaderImportError::Useless); } self.blocks.insert_headers(headers); @@ -342,21 +340,14 @@ impl BlockDownloader { self.last_imported_hash = p.clone(); trace!(target: "sync", "Searching common header from the last round {} ({})", self.last_imported_block, self.last_imported_hash); } else { - let best = io.chain().chain_info().best_block_number; - if best > self.last_imported_block && best - self.last_imported_block > MAX_REORG_BLOCKS { - debug!(target: "sync", "Could not revert to previous ancient block, last: {} ({})", self.last_imported_block, self.last_imported_hash); - self.reset(); - } else { - match io.chain().block_hash(BlockID::Number(self.last_imported_block - 1)) { - Some(h) => { - self.last_imported_block -= 1; - self.last_imported_hash = h; - trace!(target: "sync", "Searching common header in the blockchain {} ({})", self.last_imported_block, self.last_imported_hash); - } - None => { - debug!(target: "sync", "Could not revert to previous block, last: {} ({})", self.last_imported_block, self.last_imported_hash); - self.reset(); - } + match io.chain().block_hash(BlockID::Number(self.last_imported_block - 1)) { + Some(h) => { + self.last_imported_block -= 1; + self.last_imported_hash = h; + trace!(target: "sync", "Searching common header in the blockchain {} ({})", self.last_imported_block, self.last_imported_hash); + } + None => { + debug!(target: "sync", "Could not revert to previous block, last: {} ({})", self.last_imported_block, self.last_imported_hash); } } } @@ -371,9 +362,7 @@ impl BlockDownloader { match self.state { State::Idle => { self.start_sync_round(io); - if self.state == State::ChainHead { - return self.request_blocks(io, num_active_peers); - } + return self.request_blocks(io, num_active_peers); }, State::ChainHead => { if num_active_peers < MAX_PARALLEL_SUBCHAIN_DOWNLOAD { diff --git a/sync/src/chain.rs b/sync/src/chain.rs index ffd89ecdd..d2939fbac 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -1144,7 +1144,6 @@ impl ChainSync { let have_latest = io.chain().block_status(BlockID::Hash(peer_latest)) != BlockStatus::Unknown; if !have_latest && (higher_difficulty || force || self.state == SyncState::NewBlocks) { // check if got new blocks to download - trace!(target: "sync", "Syncing with {}, force={}, td={:?}, our td={}, state={:?}", peer_id, force, peer_difficulty, syncing_difficulty, self.state); if let Some(request) = self.new_blocks.request_blocks(io, num_active_peers) { self.request_blocks(io, peer_id, request, BlockSet::NewBlocks); if self.state == SyncState::Idle { From 8fc1506c57b86d496dbaa68f71a6694e6854a9ee Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Fri, 18 Nov 2016 17:54:25 +0100 Subject: [PATCH 25/41] ABI can be empty and auto-fill contract name #3511 --- js/src/modals/DeployContract/DetailsStep/detailsStep.js | 6 +++++- js/src/util/validation.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/js/src/modals/DeployContract/DetailsStep/detailsStep.js b/js/src/modals/DeployContract/DetailsStep/detailsStep.js index 06b5a85bb..8d0a2457c 100644 --- a/js/src/modals/DeployContract/DetailsStep/detailsStep.js +++ b/js/src/modals/DeployContract/DetailsStep/detailsStep.js @@ -103,7 +103,7 @@ export default class DetailsStep extends Component { label='contract name' hint='a name for the deployed contract' error={ nameError } - value={ name } + value={ name || '' } onChange={ this.onNameChange } /> Date: Fri, 18 Nov 2016 18:12:38 +0100 Subject: [PATCH 26/41] PR grumble --- js/src/dapps/githubhint/Events/events.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/src/dapps/githubhint/Events/events.js b/js/src/dapps/githubhint/Events/events.js index f3b48292b..ba74ceaea 100644 --- a/js/src/dapps/githubhint/Events/events.js +++ b/js/src/dapps/githubhint/Events/events.js @@ -28,7 +28,9 @@ export default class Events extends Component { render () { return ( - { this.props.eventIds.map((id) => this.renderEvent(id, this.props.events[id])) } + + { this.props.eventIds.map((id) => this.renderEvent(id, this.props.events[id])) } +
); } From 128cd2a8e4eba6b143b9f8b3375805b273b7d0ab Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Fri, 18 Nov 2016 18:14:34 +0100 Subject: [PATCH 27/41] Fixing JS tests --- js/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/package.json b/js/package.json index d3906ba4f..93009883f 100644 --- a/js/package.json +++ b/js/package.json @@ -102,8 +102,9 @@ "postcss-nested": "^1.0.0", "postcss-simple-vars": "^3.0.0", "raw-loader": "^0.5.1", - "react-addons-test-utils": "^15.3.0", + "react-addons-test-utils": "~15.3.2", "react-copy-to-clipboard": "^4.2.3", + "react-dom": "~15.3.2", "react-hot-loader": "~1.3.0", "rucksack-css": "^0.8.6", "sinon": "^1.17.4", From be1e5ae52fe616d6c6fa929905249c0820961db1 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Fri, 18 Nov 2016 18:20:14 +0100 Subject: [PATCH 28/41] Updated overlay size as per comments --- js/src/views/Dapps/dapps.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/src/views/Dapps/dapps.css b/js/src/views/Dapps/dapps.css index ea460f0b5..0fb1a07b5 100644 --- a/js/src/views/Dapps/dapps.css +++ b/js/src/views/Dapps/dapps.css @@ -34,9 +34,9 @@ .overlay { background: rgba(0, 0, 0, 0.85); bottom: 0.5em; - left: -0.25em; + left: -0.125em; position: absolute; - right: -0.25em; + right: -0.125em; top: -0.25em; z-index: 100; padding: 1em; From 9c62dd3916f8175721d23bf2cf71c70944879abd Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Fri, 18 Nov 2016 19:17:35 +0100 Subject: [PATCH 29/41] Limit sync reorg to 20 blocks (#3519) * Limit sync reorg * Fixed tests --- sync/src/block_sync.rs | 31 +++++++++++++++++++++---------- sync/src/chain.rs | 1 + sync/src/tests/chain.rs | 26 +++++++++++++------------- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/sync/src/block_sync.rs b/sync/src/block_sync.rs index 7c3cbf2d7..ff5411140 100644 --- a/sync/src/block_sync.rs +++ b/sync/src/block_sync.rs @@ -34,6 +34,7 @@ const MAX_RECEPITS_TO_REQUEST: usize = 128; const SUBCHAIN_SIZE: u64 = 256; const MAX_ROUND_PARENTS: usize = 32; const MAX_PARALLEL_SUBCHAIN_DOWNLOAD: usize = 5; +const MAX_REORG_BLOCKS: u64 = 20; #[derive(Copy, Clone, Eq, PartialEq, Debug)] /// Downloader state @@ -262,7 +263,8 @@ impl BlockDownloader { State::Blocks => { let count = headers.len(); // At least one of the heades must advance the subchain. Otherwise they are all useless. - if !any_known { + if count == 0 || !any_known { + trace!(target: "sync", "No useful headers"); return Err(BlockDownloaderImportError::Useless); } self.blocks.insert_headers(headers); @@ -340,14 +342,21 @@ impl BlockDownloader { self.last_imported_hash = p.clone(); trace!(target: "sync", "Searching common header from the last round {} ({})", self.last_imported_block, self.last_imported_hash); } else { - match io.chain().block_hash(BlockID::Number(self.last_imported_block - 1)) { - Some(h) => { - self.last_imported_block -= 1; - self.last_imported_hash = h; - trace!(target: "sync", "Searching common header in the blockchain {} ({})", self.last_imported_block, self.last_imported_hash); - } - None => { - debug!(target: "sync", "Could not revert to previous block, last: {} ({})", self.last_imported_block, self.last_imported_hash); + let best = io.chain().chain_info().best_block_number; + if best > self.last_imported_block && best - self.last_imported_block > MAX_REORG_BLOCKS { + debug!(target: "sync", "Could not revert to previous ancient block, last: {} ({})", self.last_imported_block, self.last_imported_hash); + self.reset(); + } else { + match io.chain().block_hash(BlockID::Number(self.last_imported_block - 1)) { + Some(h) => { + self.last_imported_block -= 1; + self.last_imported_hash = h; + trace!(target: "sync", "Searching common header in the blockchain {} ({})", self.last_imported_block, self.last_imported_hash); + } + None => { + debug!(target: "sync", "Could not revert to previous block, last: {} ({})", self.last_imported_block, self.last_imported_hash); + self.reset(); + } } } } @@ -362,7 +371,9 @@ impl BlockDownloader { match self.state { State::Idle => { self.start_sync_round(io); - return self.request_blocks(io, num_active_peers); + if self.state == State::ChainHead { + return self.request_blocks(io, num_active_peers); + } }, State::ChainHead => { if num_active_peers < MAX_PARALLEL_SUBCHAIN_DOWNLOAD { diff --git a/sync/src/chain.rs b/sync/src/chain.rs index d2939fbac..ffd89ecdd 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -1144,6 +1144,7 @@ impl ChainSync { let have_latest = io.chain().block_status(BlockID::Hash(peer_latest)) != BlockStatus::Unknown; if !have_latest && (higher_difficulty || force || self.state == SyncState::NewBlocks) { // check if got new blocks to download + trace!(target: "sync", "Syncing with {}, force={}, td={:?}, our td={}, state={:?}", peer_id, force, peer_difficulty, syncing_difficulty, self.state); if let Some(request) = self.new_blocks.request_blocks(io, num_active_peers) { self.request_blocks(io, peer_id, request, BlockSet::NewBlocks); if self.state == SyncState::Idle { diff --git a/sync/src/tests/chain.rs b/sync/src/tests/chain.rs index 17c051162..5fe34428e 100644 --- a/sync/src/tests/chain.rs +++ b/sync/src/tests/chain.rs @@ -79,14 +79,14 @@ fn empty_blocks() { fn forked() { ::env_logger::init().ok(); let mut net = TestNet::new(3); - net.peer_mut(0).chain.add_blocks(300, EachBlockWith::Uncle); - net.peer_mut(1).chain.add_blocks(300, EachBlockWith::Uncle); - net.peer_mut(2).chain.add_blocks(300, EachBlockWith::Uncle); - net.peer_mut(0).chain.add_blocks(100, EachBlockWith::Nothing); //fork - net.peer_mut(1).chain.add_blocks(200, EachBlockWith::Uncle); - net.peer_mut(2).chain.add_blocks(200, EachBlockWith::Uncle); - net.peer_mut(1).chain.add_blocks(100, EachBlockWith::Uncle); //fork between 1 and 2 - net.peer_mut(2).chain.add_blocks(10, EachBlockWith::Nothing); + net.peer_mut(0).chain.add_blocks(30, EachBlockWith::Uncle); + net.peer_mut(1).chain.add_blocks(30, EachBlockWith::Uncle); + net.peer_mut(2).chain.add_blocks(30, EachBlockWith::Uncle); + net.peer_mut(0).chain.add_blocks(10, EachBlockWith::Nothing); //fork + net.peer_mut(1).chain.add_blocks(20, EachBlockWith::Uncle); + net.peer_mut(2).chain.add_blocks(20, EachBlockWith::Uncle); + net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Uncle); //fork between 1 and 2 + net.peer_mut(2).chain.add_blocks(1, EachBlockWith::Nothing); // peer 1 has the best chain of 601 blocks let peer1_chain = net.peer(1).chain.numbers.read().clone(); net.sync(); @@ -102,12 +102,12 @@ fn forked_with_misbehaving_peer() { let mut net = TestNet::new(3); // peer 0 is on a totally different chain with higher total difficulty net.peer_mut(0).chain = TestBlockChainClient::new_with_extra_data(b"fork".to_vec()); - net.peer_mut(0).chain.add_blocks(500, EachBlockWith::Nothing); - net.peer_mut(1).chain.add_blocks(100, EachBlockWith::Nothing); - net.peer_mut(2).chain.add_blocks(100, EachBlockWith::Nothing); + net.peer_mut(0).chain.add_blocks(50, EachBlockWith::Nothing); + net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Nothing); + net.peer_mut(2).chain.add_blocks(10, EachBlockWith::Nothing); - net.peer_mut(1).chain.add_blocks(100, EachBlockWith::Nothing); - net.peer_mut(2).chain.add_blocks(200, EachBlockWith::Uncle); + net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Nothing); + net.peer_mut(2).chain.add_blocks(20, EachBlockWith::Uncle); // peer 1 should sync to peer 2, others should not change let peer0_chain = net.peer(0).chain.numbers.read().clone(); let peer2_chain = net.peer(2).chain.numbers.read().clone(); From d870b71636fd0bf95863a3ce6a3d4f6d4011f4d5 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Fri, 18 Nov 2016 18:41:39 +0000 Subject: [PATCH 30/41] updated the european warp bootnode addresses (#3528) --- ethcore/res/ethereum/frontier.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethcore/res/ethereum/frontier.json b/ethcore/res/ethereum/frontier.json index 9bb9c50ff..2c520c46a 100644 --- a/ethcore/res/ethereum/frontier.json +++ b/ethcore/res/ethereum/frontier.json @@ -182,8 +182,8 @@ "enode://89d5dc2a81e574c19d0465f497c1af96732d1b61a41de89c2a37f35707689ac416529fae1038809852b235c2d30fd325abdc57c122feeefbeaaf802cc7e9580d@45.55.33.62:30303", "enode://605e04a43b1156966b3a3b66b980c87b7f18522f7f712035f84576016be909a2798a438b2b17b1a8c58db314d88539a77419ca4be36148c086900fba487c9d39@188.166.255.12:30303", "enode://016b20125f447a3b203a3cae953b2ede8ffe51290c071e7599294be84317635730c397b8ff74404d6be412d539ee5bb5c3c700618723d3b53958c92bd33eaa82@159.203.210.80:30303", - "enode://01f76fa0561eca2b9a7e224378dd854278735f1449793c46ad0c4e79e8775d080c21dcc455be391e90a98153c3b05dcc8935c8440de7b56fe6d67251e33f4e3c@10.6.6.117:30303", - "enode://fe11ef89fc5ac9da358fc160857855f25bbf9e332c79b9ca7089330c02b728b2349988c6062f10982041702110745e203d26975a6b34bcc97144f9fe439034e8@10.1.72.117:30303" + "enode://01f76fa0561eca2b9a7e224378dd854278735f1449793c46ad0c4e79e8775d080c21dcc455be391e90a98153c3b05dcc8935c8440de7b56fe6d67251e33f4e3c@51.15.42.252:30303", + "enode://8d91c8137890d29110b9463882f17ae4e279cd2c90cf56573187ed1c8546fca5f590a9f05e9f108eb1bd91767ed01ede4daad9e001b61727885eaa246ddb39c2@163.172.171.38:30303" ], "accounts": { "0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, From 53fcfa658f8a380a65d9845ad19898533e9f19ef Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Fri, 18 Nov 2016 19:49:30 +0100 Subject: [PATCH 31/41] Fix parity.js badly built (#3526) --- js/webpack.libraries.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/js/webpack.libraries.js b/js/webpack.libraries.js index 07e40957e..889a45103 100644 --- a/js/webpack.libraries.js +++ b/js/webpack.libraries.js @@ -26,7 +26,6 @@ const DEST = process.env.BUILD_DEST || '.build'; module.exports = { context: path.join(__dirname, './src'), - target: 'node', entry: { // library 'inject': ['./web3.js'], @@ -39,14 +38,7 @@ module.exports = { library: '[name].js', libraryTarget: 'umd' }, - externals: { - 'node-fetch': 'node-fetch', - 'vertx': 'vertx' - }, module: { - noParse: [ - /babel-polyfill/ - ], loaders: [ { test: /\.js$/, From 60922b61c9313c0872b96fb54f73e1124918465c Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Fri, 18 Nov 2016 19:27:32 +0000 Subject: [PATCH 32/41] [ci skip] js-precompiled 20161118-192308 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cab5b0aee..fb441ef96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#9f65351f2e81cbc9daf458e56dc031796695450a" +source = "git+https://github.com/ethcore/js-precompiled.git#427319583ccde288ba26728c14384392ddbba93d" 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 93009883f..9a45fbf18 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.53", + "version": "0.2.54", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 306ee068f54acf8c375fb9d11f7f61f63336571a Mon Sep 17 00:00:00 2001 From: sandakersmann Date: Sat, 19 Nov 2016 19:22:26 +0100 Subject: [PATCH 33/41] Add simple one-line installer to README.md Add simple one-line installer for Mac and Ubuntu to README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index fc5cd9762..65c374413 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,14 @@ $ cargo build --release This will produce an executable in the `./target/release` subdirectory. +---- + +## Simple one-line installer for Mac and Ubuntu + +```bash +bash <(curl https://get.parity.io -Lk) +``` + ## Start Parity ### Manually To start Parity manually, just run From ab4941b5ee5a8345c9966ee954fda58bbf6f3d09 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Sun, 20 Nov 2016 13:28:04 +0000 Subject: [PATCH 34/41] [ci skip] js-precompiled 20161120-132620 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dafac3d7a..78078fdce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1250,7 +1250,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#427319583ccde288ba26728c14384392ddbba93d" +source = "git+https://github.com/ethcore/js-precompiled.git#27560245f25577aa64bb2496a8d77b47c3326a10" 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 9a45fbf18..f73599b28 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.54", + "version": "0.2.55", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 9832be395f73e89338a087f9b974be0d132cddbc Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Sun, 20 Nov 2016 13:35:00 +0000 Subject: [PATCH 35/41] [ci skip] js-precompiled 20161120-133314 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 78078fdce..4a209e009 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1250,7 +1250,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#27560245f25577aa64bb2496a8d77b47c3326a10" +source = "git+https://github.com/ethcore/js-precompiled.git#20e50a56709fbe3e39d0fea6cf31f48bd1da4bff" 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 f73599b28..d4ee6fe8e 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.55", + "version": "0.2.56", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From efd1d9bd0e9ad10184e713b2fb191256c61d1a92 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Sun, 20 Nov 2016 16:38:45 +0100 Subject: [PATCH 36/41] Ropsten network (#3539) * Ropsten network * Sorted premine * Comas * Removed trailing coma --- ethcore/res/ethereum/ropsten.json | 304 ++++++++++++++++++++++++++++++ ethcore/src/ethereum/mod.rs | 3 + parity/params.rs | 4 + 3 files changed, 311 insertions(+) create mode 100644 ethcore/res/ethereum/ropsten.json diff --git a/ethcore/res/ethereum/ropsten.json b/ethcore/res/ethereum/ropsten.json new file mode 100644 index 000000000..c92676161 --- /dev/null +++ b/ethcore/res/ethereum/ropsten.json @@ -0,0 +1,304 @@ +{ + "name": "Ropsten", + "engine": { + "Ethash": { + "params": { + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar": "0x52dff57a8a1532e6afb3dc07e2af58bb9eb05b3d", + "homesteadTransition": 0, + "eip150Transition": 0, + "eip155Transition": 10, + "eip160Transition": 10, + "eip161abcTransition": 10, + "eip161dTransition": 10 + } + } + }, + "params": { + "accountStartNonce": "0x0", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID" : "0x3" + }, + "genesis": { + "seal": { + "ethereum": { + "nonce": "0x0000000000000042", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "difficulty": "0x100000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x3535353535353535353535353535353535353535353535353535353535353535", + "gasLimit": "0x1000000" + }, + "nodes": [ + "enode://a22f0977ce02653bf95e38730106356342df48b5222e2c2a1a6f9ef34769bf593bae9ca0a888cf60839edd52efc1b6e393c63a57d76f4c4fe14e641f1f9e637e@128.199.55.137:30303", + "enode://012239fccf3ff1d92b036983a430cb6705c6528c96c0354413f8854802138e5135c084ab36e7c54efb621c46728df8c3a6f4c1db9bb48a1330efe3f82f2dd7a6@52.169.94.142:30303" + ], + "accounts": { + "0000000000000000000000000000000000000001": { "balance": "1", "nonce": "0", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, + "0000000000000000000000000000000000000002": { "balance": "1", "nonce": "0", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, + "0000000000000000000000000000000000000003": { "balance": "1", "nonce": "0", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, + "0000000000000000000000000000000000000004": { "balance": "1", "nonce": "0", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, + "0000000000000000000000000000000000000000": { "balance": "1" }, + "0000000000000000000000000000000000000005": { "balance": "1" }, + "0000000000000000000000000000000000000006": { "balance": "1" }, + "0000000000000000000000000000000000000007": { "balance": "1" }, + "0000000000000000000000000000000000000008": { "balance": "1" }, + "0000000000000000000000000000000000000009": { "balance": "1" }, + "000000000000000000000000000000000000000a": { "balance": "0" }, + "000000000000000000000000000000000000000b": { "balance": "0" }, + "000000000000000000000000000000000000000c": { "balance": "0" }, + "000000000000000000000000000000000000000d": { "balance": "0" }, + "000000000000000000000000000000000000000e": { "balance": "0" }, + "000000000000000000000000000000000000000f": { "balance": "0" }, + "0000000000000000000000000000000000000010": { "balance": "0" }, + "0000000000000000000000000000000000000011": { "balance": "0" }, + "0000000000000000000000000000000000000012": { "balance": "0" }, + "0000000000000000000000000000000000000013": { "balance": "0" }, + "0000000000000000000000000000000000000014": { "balance": "0" }, + "0000000000000000000000000000000000000015": { "balance": "0" }, + "0000000000000000000000000000000000000016": { "balance": "0" }, + "0000000000000000000000000000000000000017": { "balance": "0" }, + "0000000000000000000000000000000000000018": { "balance": "0" }, + "0000000000000000000000000000000000000019": { "balance": "0" }, + "000000000000000000000000000000000000001a": { "balance": "0" }, + "000000000000000000000000000000000000001b": { "balance": "0" }, + "000000000000000000000000000000000000001c": { "balance": "0" }, + "000000000000000000000000000000000000001d": { "balance": "0" }, + "000000000000000000000000000000000000001e": { "balance": "0" }, + "000000000000000000000000000000000000001f": { "balance": "0" }, + "0000000000000000000000000000000000000020": { "balance": "0" }, + "0000000000000000000000000000000000000021": { "balance": "0" }, + "0000000000000000000000000000000000000022": { "balance": "0" }, + "0000000000000000000000000000000000000023": { "balance": "0" }, + "0000000000000000000000000000000000000024": { "balance": "0" }, + "0000000000000000000000000000000000000025": { "balance": "0" }, + "0000000000000000000000000000000000000026": { "balance": "0" }, + "0000000000000000000000000000000000000027": { "balance": "0" }, + "0000000000000000000000000000000000000028": { "balance": "0" }, + "0000000000000000000000000000000000000029": { "balance": "0" }, + "000000000000000000000000000000000000002a": { "balance": "0" }, + "000000000000000000000000000000000000002b": { "balance": "0" }, + "000000000000000000000000000000000000002c": { "balance": "0" }, + "000000000000000000000000000000000000002d": { "balance": "0" }, + "000000000000000000000000000000000000002e": { "balance": "0" }, + "000000000000000000000000000000000000002f": { "balance": "0" }, + "0000000000000000000000000000000000000030": { "balance": "0" }, + "0000000000000000000000000000000000000031": { "balance": "0" }, + "0000000000000000000000000000000000000032": { "balance": "0" }, + "0000000000000000000000000000000000000033": { "balance": "0" }, + "0000000000000000000000000000000000000034": { "balance": "0" }, + "0000000000000000000000000000000000000035": { "balance": "0" }, + "0000000000000000000000000000000000000036": { "balance": "0" }, + "0000000000000000000000000000000000000037": { "balance": "0" }, + "0000000000000000000000000000000000000038": { "balance": "0" }, + "0000000000000000000000000000000000000039": { "balance": "0" }, + "000000000000000000000000000000000000003a": { "balance": "0" }, + "000000000000000000000000000000000000003b": { "balance": "0" }, + "000000000000000000000000000000000000003c": { "balance": "0" }, + "000000000000000000000000000000000000003d": { "balance": "0" }, + "000000000000000000000000000000000000003e": { "balance": "0" }, + "000000000000000000000000000000000000003f": { "balance": "0" }, + "0000000000000000000000000000000000000040": { "balance": "0" }, + "0000000000000000000000000000000000000041": { "balance": "0" }, + "0000000000000000000000000000000000000042": { "balance": "0" }, + "0000000000000000000000000000000000000043": { "balance": "0" }, + "0000000000000000000000000000000000000044": { "balance": "0" }, + "0000000000000000000000000000000000000045": { "balance": "0" }, + "0000000000000000000000000000000000000046": { "balance": "0" }, + "0000000000000000000000000000000000000047": { "balance": "0" }, + "0000000000000000000000000000000000000048": { "balance": "0" }, + "0000000000000000000000000000000000000049": { "balance": "0" }, + "000000000000000000000000000000000000004a": { "balance": "0" }, + "000000000000000000000000000000000000004b": { "balance": "0" }, + "000000000000000000000000000000000000004c": { "balance": "0" }, + "000000000000000000000000000000000000004d": { "balance": "0" }, + "000000000000000000000000000000000000004e": { "balance": "0" }, + "000000000000000000000000000000000000004f": { "balance": "0" }, + "0000000000000000000000000000000000000050": { "balance": "0" }, + "0000000000000000000000000000000000000051": { "balance": "0" }, + "0000000000000000000000000000000000000052": { "balance": "0" }, + "0000000000000000000000000000000000000053": { "balance": "0" }, + "0000000000000000000000000000000000000054": { "balance": "0" }, + "0000000000000000000000000000000000000055": { "balance": "0" }, + "0000000000000000000000000000000000000056": { "balance": "0" }, + "0000000000000000000000000000000000000057": { "balance": "0" }, + "0000000000000000000000000000000000000058": { "balance": "0" }, + "0000000000000000000000000000000000000059": { "balance": "0" }, + "000000000000000000000000000000000000005a": { "balance": "0" }, + "000000000000000000000000000000000000005b": { "balance": "0" }, + "000000000000000000000000000000000000005c": { "balance": "0" }, + "000000000000000000000000000000000000005d": { "balance": "0" }, + "000000000000000000000000000000000000005e": { "balance": "0" }, + "000000000000000000000000000000000000005f": { "balance": "0" }, + "0000000000000000000000000000000000000060": { "balance": "0" }, + "0000000000000000000000000000000000000061": { "balance": "0" }, + "0000000000000000000000000000000000000062": { "balance": "0" }, + "0000000000000000000000000000000000000063": { "balance": "0" }, + "0000000000000000000000000000000000000064": { "balance": "0" }, + "0000000000000000000000000000000000000065": { "balance": "0" }, + "0000000000000000000000000000000000000066": { "balance": "0" }, + "0000000000000000000000000000000000000067": { "balance": "0" }, + "0000000000000000000000000000000000000068": { "balance": "0" }, + "0000000000000000000000000000000000000069": { "balance": "0" }, + "000000000000000000000000000000000000006a": { "balance": "0" }, + "000000000000000000000000000000000000006b": { "balance": "0" }, + "000000000000000000000000000000000000006c": { "balance": "0" }, + "000000000000000000000000000000000000006d": { "balance": "0" }, + "000000000000000000000000000000000000006e": { "balance": "0" }, + "000000000000000000000000000000000000006f": { "balance": "0" }, + "0000000000000000000000000000000000000070": { "balance": "0" }, + "0000000000000000000000000000000000000071": { "balance": "0" }, + "0000000000000000000000000000000000000072": { "balance": "0" }, + "0000000000000000000000000000000000000073": { "balance": "0" }, + "0000000000000000000000000000000000000074": { "balance": "0" }, + "0000000000000000000000000000000000000075": { "balance": "0" }, + "0000000000000000000000000000000000000076": { "balance": "0" }, + "0000000000000000000000000000000000000077": { "balance": "0" }, + "0000000000000000000000000000000000000078": { "balance": "0" }, + "0000000000000000000000000000000000000079": { "balance": "0" }, + "000000000000000000000000000000000000007a": { "balance": "0" }, + "000000000000000000000000000000000000007b": { "balance": "0" }, + "000000000000000000000000000000000000007c": { "balance": "0" }, + "000000000000000000000000000000000000007d": { "balance": "0" }, + "000000000000000000000000000000000000007e": { "balance": "0" }, + "000000000000000000000000000000000000007f": { "balance": "0" }, + "0000000000000000000000000000000000000080": { "balance": "0" }, + "0000000000000000000000000000000000000081": { "balance": "0" }, + "0000000000000000000000000000000000000082": { "balance": "0" }, + "0000000000000000000000000000000000000083": { "balance": "0" }, + "0000000000000000000000000000000000000084": { "balance": "0" }, + "0000000000000000000000000000000000000085": { "balance": "0" }, + "0000000000000000000000000000000000000086": { "balance": "0" }, + "0000000000000000000000000000000000000087": { "balance": "0" }, + "0000000000000000000000000000000000000088": { "balance": "0" }, + "0000000000000000000000000000000000000089": { "balance": "0" }, + "000000000000000000000000000000000000008a": { "balance": "0" }, + "000000000000000000000000000000000000008b": { "balance": "0" }, + "000000000000000000000000000000000000008c": { "balance": "0" }, + "000000000000000000000000000000000000008d": { "balance": "0" }, + "000000000000000000000000000000000000008e": { "balance": "0" }, + "000000000000000000000000000000000000008f": { "balance": "0" }, + "0000000000000000000000000000000000000090": { "balance": "0" }, + "0000000000000000000000000000000000000091": { "balance": "0" }, + "0000000000000000000000000000000000000092": { "balance": "0" }, + "0000000000000000000000000000000000000093": { "balance": "0" }, + "0000000000000000000000000000000000000094": { "balance": "0" }, + "0000000000000000000000000000000000000095": { "balance": "0" }, + "0000000000000000000000000000000000000096": { "balance": "0" }, + "0000000000000000000000000000000000000097": { "balance": "0" }, + "0000000000000000000000000000000000000098": { "balance": "0" }, + "0000000000000000000000000000000000000099": { "balance": "0" }, + "000000000000000000000000000000000000009a": { "balance": "0" }, + "000000000000000000000000000000000000009b": { "balance": "0" }, + "000000000000000000000000000000000000009c": { "balance": "0" }, + "000000000000000000000000000000000000009d": { "balance": "0" }, + "000000000000000000000000000000000000009e": { "balance": "0" }, + "000000000000000000000000000000000000009f": { "balance": "0" }, + "00000000000000000000000000000000000000a0": { "balance": "0" }, + "00000000000000000000000000000000000000a1": { "balance": "0" }, + "00000000000000000000000000000000000000a2": { "balance": "0" }, + "00000000000000000000000000000000000000a3": { "balance": "0" }, + "00000000000000000000000000000000000000a4": { "balance": "0" }, + "00000000000000000000000000000000000000a5": { "balance": "0" }, + "00000000000000000000000000000000000000a6": { "balance": "0" }, + "00000000000000000000000000000000000000a7": { "balance": "0" }, + "00000000000000000000000000000000000000a8": { "balance": "0" }, + "00000000000000000000000000000000000000a9": { "balance": "0" }, + "00000000000000000000000000000000000000aa": { "balance": "0" }, + "00000000000000000000000000000000000000ab": { "balance": "0" }, + "00000000000000000000000000000000000000ac": { "balance": "0" }, + "00000000000000000000000000000000000000ad": { "balance": "0" }, + "00000000000000000000000000000000000000ae": { "balance": "0" }, + "00000000000000000000000000000000000000af": { "balance": "0" }, + "00000000000000000000000000000000000000b0": { "balance": "0" }, + "00000000000000000000000000000000000000b1": { "balance": "0" }, + "00000000000000000000000000000000000000b2": { "balance": "0" }, + "00000000000000000000000000000000000000b3": { "balance": "0" }, + "00000000000000000000000000000000000000b4": { "balance": "0" }, + "00000000000000000000000000000000000000b5": { "balance": "0" }, + "00000000000000000000000000000000000000b6": { "balance": "0" }, + "00000000000000000000000000000000000000b7": { "balance": "0" }, + "00000000000000000000000000000000000000b8": { "balance": "0" }, + "00000000000000000000000000000000000000b9": { "balance": "0" }, + "00000000000000000000000000000000000000ba": { "balance": "0" }, + "00000000000000000000000000000000000000bb": { "balance": "0" }, + "00000000000000000000000000000000000000bc": { "balance": "0" }, + "00000000000000000000000000000000000000bd": { "balance": "0" }, + "00000000000000000000000000000000000000be": { "balance": "0" }, + "00000000000000000000000000000000000000bf": { "balance": "0" }, + "00000000000000000000000000000000000000c0": { "balance": "0" }, + "00000000000000000000000000000000000000c1": { "balance": "0" }, + "00000000000000000000000000000000000000c2": { "balance": "0" }, + "00000000000000000000000000000000000000c3": { "balance": "0" }, + "00000000000000000000000000000000000000c4": { "balance": "0" }, + "00000000000000000000000000000000000000c5": { "balance": "0" }, + "00000000000000000000000000000000000000c6": { "balance": "0" }, + "00000000000000000000000000000000000000c7": { "balance": "0" }, + "00000000000000000000000000000000000000c8": { "balance": "0" }, + "00000000000000000000000000000000000000c9": { "balance": "0" }, + "00000000000000000000000000000000000000ca": { "balance": "0" }, + "00000000000000000000000000000000000000cb": { "balance": "0" }, + "00000000000000000000000000000000000000cc": { "balance": "0" }, + "00000000000000000000000000000000000000cd": { "balance": "0" }, + "00000000000000000000000000000000000000ce": { "balance": "0" }, + "00000000000000000000000000000000000000cf": { "balance": "0" }, + "00000000000000000000000000000000000000d0": { "balance": "0" }, + "00000000000000000000000000000000000000d1": { "balance": "0" }, + "00000000000000000000000000000000000000d2": { "balance": "0" }, + "00000000000000000000000000000000000000d3": { "balance": "0" }, + "00000000000000000000000000000000000000d4": { "balance": "0" }, + "00000000000000000000000000000000000000d5": { "balance": "0" }, + "00000000000000000000000000000000000000d6": { "balance": "0" }, + "00000000000000000000000000000000000000d7": { "balance": "0" }, + "00000000000000000000000000000000000000d8": { "balance": "0" }, + "00000000000000000000000000000000000000d9": { "balance": "0" }, + "00000000000000000000000000000000000000da": { "balance": "0" }, + "00000000000000000000000000000000000000db": { "balance": "0" }, + "00000000000000000000000000000000000000dc": { "balance": "0" }, + "00000000000000000000000000000000000000dd": { "balance": "0" }, + "00000000000000000000000000000000000000de": { "balance": "0" }, + "00000000000000000000000000000000000000df": { "balance": "0" }, + "00000000000000000000000000000000000000e0": { "balance": "0" }, + "00000000000000000000000000000000000000e1": { "balance": "0" }, + "00000000000000000000000000000000000000e2": { "balance": "0" }, + "00000000000000000000000000000000000000e3": { "balance": "0" }, + "00000000000000000000000000000000000000e4": { "balance": "0" }, + "00000000000000000000000000000000000000e5": { "balance": "0" }, + "00000000000000000000000000000000000000e6": { "balance": "0" }, + "00000000000000000000000000000000000000e7": { "balance": "0" }, + "00000000000000000000000000000000000000e8": { "balance": "0" }, + "00000000000000000000000000000000000000e9": { "balance": "0" }, + "00000000000000000000000000000000000000ea": { "balance": "0" }, + "00000000000000000000000000000000000000eb": { "balance": "0" }, + "00000000000000000000000000000000000000ec": { "balance": "0" }, + "00000000000000000000000000000000000000ed": { "balance": "0" }, + "00000000000000000000000000000000000000ee": { "balance": "0" }, + "00000000000000000000000000000000000000ef": { "balance": "0" }, + "00000000000000000000000000000000000000f0": { "balance": "0" }, + "00000000000000000000000000000000000000f1": { "balance": "0" }, + "00000000000000000000000000000000000000f2": { "balance": "0" }, + "00000000000000000000000000000000000000f3": { "balance": "0" }, + "00000000000000000000000000000000000000f4": { "balance": "0" }, + "00000000000000000000000000000000000000f5": { "balance": "0" }, + "00000000000000000000000000000000000000f6": { "balance": "0" }, + "00000000000000000000000000000000000000f7": { "balance": "0" }, + "00000000000000000000000000000000000000f8": { "balance": "0" }, + "00000000000000000000000000000000000000f9": { "balance": "0" }, + "00000000000000000000000000000000000000fa": { "balance": "0" }, + "00000000000000000000000000000000000000fb": { "balance": "0" }, + "00000000000000000000000000000000000000fc": { "balance": "0" }, + "00000000000000000000000000000000000000fd": { "balance": "0" }, + "00000000000000000000000000000000000000fe": { "balance": "0" }, + "00000000000000000000000000000000000000ff": { "balance": "0" }, + "874b54a8bd152966d63f706bae1ffeb0411921e5": { "balance": "1000000000000000000000000000000" } + } +} diff --git a/ethcore/src/ethereum/mod.rs b/ethcore/src/ethereum/mod.rs index 253a12372..e236924ad 100644 --- a/ethcore/src/ethereum/mod.rs +++ b/ethcore/src/ethereum/mod.rs @@ -63,6 +63,9 @@ pub fn new_transition_test() -> Spec { load(include_bytes!("../../res/ethereum/t /// Create a new Frontier main net chain spec without genesis accounts. pub fn new_mainnet_like() -> Spec { load(include_bytes!("../../res/ethereum/frontier_like_test.json")) } +/// Create a new Ropsten chain spec. +pub fn new_ropsten() -> Spec { load(include_bytes!("../../res/ethereum/ropsten.json")) } + /// Create a new Morden chain spec. pub fn new_morden() -> Spec { load(include_bytes!("../../res/ethereum/morden.json")) } diff --git a/parity/params.rs b/parity/params.rs index 54e08da32..8af70b91d 100644 --- a/parity/params.rs +++ b/parity/params.rs @@ -28,6 +28,7 @@ use user_defaults::UserDefaults; pub enum SpecType { Mainnet, Testnet, + Ropsten, Olympic, Classic, Expanse, @@ -49,6 +50,7 @@ impl str::FromStr for SpecType { "frontier" | "homestead" | "mainnet" => SpecType::Mainnet, "frontier-dogmatic" | "homestead-dogmatic" | "classic" => SpecType::Classic, "morden" | "testnet" => SpecType::Testnet, + "ropsten" => SpecType::Ropsten, "olympic" => SpecType::Olympic, "expanse" => SpecType::Expanse, "dev" => SpecType::Dev, @@ -63,6 +65,7 @@ impl SpecType { match *self { SpecType::Mainnet => Ok(ethereum::new_frontier()), SpecType::Testnet => Ok(ethereum::new_morden()), + SpecType::Ropsten => Ok(ethereum::new_ropsten()), SpecType::Olympic => Ok(ethereum::new_olympic()), SpecType::Classic => Ok(ethereum::new_classic()), SpecType::Expanse => Ok(ethereum::new_expanse()), @@ -285,6 +288,7 @@ mod tests { assert_eq!(SpecType::Mainnet, "mainnet".parse().unwrap()); assert_eq!(SpecType::Testnet, "testnet".parse().unwrap()); assert_eq!(SpecType::Testnet, "morden".parse().unwrap()); + assert_eq!(SpecType::Ropsten, "ropsten".parse().unwrap()); assert_eq!(SpecType::Olympic, "olympic".parse().unwrap()); } From 92a34d9dd892cf53e2cc5b244e8c74fc31580711 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Sun, 20 Nov 2016 16:06:52 +0000 Subject: [PATCH 37/41] [ci skip] js-precompiled 20161120-160026 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a209e009..bffde3803 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1250,7 +1250,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#20e50a56709fbe3e39d0fea6cf31f48bd1da4bff" +source = "git+https://github.com/ethcore/js-precompiled.git#2f78566b69c96e21ca1799cbf04d7a4d510c6240" 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 d4ee6fe8e..d4c418f37 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.56", + "version": "0.2.57", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 845bc52e36003e2ad4553f72b6c547c7a105a89b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sun, 20 Nov 2016 17:40:28 +0100 Subject: [PATCH 38/41] Moving contract resolver to separate crate --- Cargo.lock | 13 +++++++++- dapps/Cargo.toml | 4 +-- dapps/src/apps/fetcher.rs | 7 +++--- dapps/src/apps/mod.rs | 1 - dapps/src/lib.rs | 7 +++--- dapps/src/tests/helpers.rs | 2 +- ethcore/hash-fetch/Cargo.toml | 14 +++++++++++ .../hash-fetch/res}/registrar.json | 0 .../hash-fetch/res}/urlhint.json | 0 ethcore/hash-fetch/src/lib.rs | 25 +++++++++++++++++++ .../hash-fetch/src}/urlhint.rs | 4 +-- 11 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 ethcore/hash-fetch/Cargo.toml rename {dapps/src/apps => ethcore/hash-fetch/res}/registrar.json (100%) rename {dapps/src/apps => ethcore/hash-fetch/res}/urlhint.json (100%) create mode 100644 ethcore/hash-fetch/src/lib.rs rename {dapps/src/apps => ethcore/hash-fetch/src}/urlhint.rs (98%) diff --git a/Cargo.lock b/Cargo.lock index 4a209e009..a590d479c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -334,8 +334,8 @@ version = "1.5.0" dependencies = [ "clippy 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore-devtools 1.4.0", + "ethcore-hash-fetch 1.5.0", "ethcore-rpc 1.5.0", "ethcore-util 1.5.0", "fetch 0.1.0", @@ -366,6 +366,17 @@ dependencies = [ "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ethcore-hash-fetch" +version = "1.5.0" +dependencies = [ + "ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore-util 1.5.0", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ethcore-io" version = "1.5.0" diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index f6e9d102d..15e537820 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -20,20 +20,20 @@ url = "1.0" rustc-serialize = "0.3" serde = "0.8" serde_json = "0.8" -ethabi = "0.2.2" linked-hash-map = "0.3" parity-dapps-glue = "1.4" mime = "0.2" +mime_guess = "1.6.1" time = "0.1.35" serde_macros = { version = "0.8", optional = true } zip = { version = "0.1", default-features = false } ethcore-devtools = { path = "../devtools" } ethcore-rpc = { path = "../rpc" } ethcore-util = { path = "../util" } +ethcore-hash-fetch = { path = "../ethcore/hash-fetch" } fetch = { path = "../util/fetch" } parity-ui = { path = "./ui" } -mime_guess = { version = "1.6.1" } clippy = { version = "0.0.96", optional = true} [build-dependencies] diff --git a/dapps/src/apps/fetcher.rs b/dapps/src/apps/fetcher.rs index 2c1414201..e7f36d144 100644 --- a/dapps/src/apps/fetcher.rs +++ b/dapps/src/apps/fetcher.rs @@ -24,6 +24,7 @@ use std::io::{self, Read, Write}; use std::path::PathBuf; use std::sync::Arc; use rustc_serialize::hex::FromHex; +use hash_fetch::urlhint::{URLHintContract, URLHint, URLHintResult}; use hyper; use hyper::status::StatusCode; @@ -37,7 +38,6 @@ use handlers::{ContentHandler, ContentFetcherHandler, ContentValidator}; use endpoint::{Endpoint, EndpointPath, Handler}; use apps::cache::{ContentCache, ContentStatus}; use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest, serialize_manifest, Manifest}; -use apps::urlhint::{URLHintContract, URLHint, URLHintResult}; /// Limit of cached dapps/content const MAX_CACHED_DAPPS: usize = 20; @@ -402,10 +402,11 @@ mod tests { use std::env; use std::sync::Arc; use util::Bytes; + use hash_fetch::urlhint::{URLHint, URLHintResult}; + + use apps::cache::ContentStatus; use endpoint::EndpointInfo; use page::LocalPageEndpoint; - use apps::cache::ContentStatus; - use apps::urlhint::{URLHint, URLHintResult}; use super::ContentFetcher; struct FakeResolver; diff --git a/dapps/src/apps/mod.rs b/dapps/src/apps/mod.rs index 3cb0d8256..4c9270aa5 100644 --- a/dapps/src/apps/mod.rs +++ b/dapps/src/apps/mod.rs @@ -21,7 +21,6 @@ use parity_dapps::WebApp; mod cache; mod fs; -pub mod urlhint; pub mod fetcher; pub mod manifest; diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 2c9fa33d1..7c7ea0a86 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -51,13 +51,13 @@ extern crate serde; extern crate serde_json; extern crate zip; extern crate rand; -extern crate ethabi; extern crate jsonrpc_core; extern crate jsonrpc_http_server; extern crate mime_guess; extern crate rustc_serialize; extern crate ethcore_rpc; extern crate ethcore_util as util; +extern crate ethcore_hash_fetch as hash_fetch; extern crate linked_hash_map; extern crate fetch; extern crate parity_dapps_glue as parity_dapps; @@ -84,12 +84,11 @@ mod url; #[cfg(test)] mod tests; -pub use self::apps::urlhint::ContractClient; - use std::sync::{Arc, Mutex}; use std::net::SocketAddr; use std::collections::HashMap; +use hash_fetch::urlhint::ContractClient; use jsonrpc_core::{IoHandler, IoDelegate}; use router::auth::{Authorization, NoAuth, HttpBasicAuth}; use ethcore_rpc::Extendable; @@ -219,7 +218,7 @@ impl Server { ) -> Result { let panic_handler = Arc::new(Mutex::new(None)); let authorization = Arc::new(authorization); - let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(apps::urlhint::URLHintContract::new(registrar), sync_status, signer_address.clone())); + let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(hash_fetch::urlhint::URLHintContract::new(registrar), sync_status, signer_address.clone())); let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_address.clone())); let cors_domains = Self::cors_domains(signer_address.clone()); diff --git a/dapps/src/tests/helpers.rs b/dapps/src/tests/helpers.rs index f7c9e8ba6..66bf0f8eb 100644 --- a/dapps/src/tests/helpers.rs +++ b/dapps/src/tests/helpers.rs @@ -22,7 +22,7 @@ use env_logger::LogBuilder; use ServerBuilder; use Server; -use apps::urlhint::ContractClient; +use hash_fetch::urlhint::ContractClient; use util::{Bytes, Address, Mutex, ToPretty}; use devtools::http_client; diff --git a/ethcore/hash-fetch/Cargo.toml b/ethcore/hash-fetch/Cargo.toml new file mode 100644 index 000000000..f1a65bfc8 --- /dev/null +++ b/ethcore/hash-fetch/Cargo.toml @@ -0,0 +1,14 @@ +[package] +description = "Fetching hash-addressed content." +homepage = "https://ethcore.io" +license = "GPL-3.0" +name = "ethcore-hash-fetch" +version = "1.5.0" +authors = ["Ethcore "] + +[dependencies] +log = "0.3" +rustc-serialize = "0.3" +ethabi = "0.2.2" +mime_guess = "1.6.1" +ethcore-util = { path = "../../util" } diff --git a/dapps/src/apps/registrar.json b/ethcore/hash-fetch/res/registrar.json similarity index 100% rename from dapps/src/apps/registrar.json rename to ethcore/hash-fetch/res/registrar.json diff --git a/dapps/src/apps/urlhint.json b/ethcore/hash-fetch/res/urlhint.json similarity index 100% rename from dapps/src/apps/urlhint.json rename to ethcore/hash-fetch/res/urlhint.json diff --git a/ethcore/hash-fetch/src/lib.rs b/ethcore/hash-fetch/src/lib.rs new file mode 100644 index 000000000..ac613a558 --- /dev/null +++ b/ethcore/hash-fetch/src/lib.rs @@ -0,0 +1,25 @@ +// Copyright 2015, 2016 Ethcore (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 rustc_serialize; +extern crate mime_guess; +extern crate ethabi; +extern crate ethcore_util as util; + +pub mod urlhint; + diff --git a/dapps/src/apps/urlhint.rs b/ethcore/hash-fetch/src/urlhint.rs similarity index 98% rename from dapps/src/apps/urlhint.rs rename to ethcore/hash-fetch/src/urlhint.rs index 27769d07a..b7f905144 100644 --- a/dapps/src/apps/urlhint.rs +++ b/ethcore/hash-fetch/src/urlhint.rs @@ -92,8 +92,8 @@ pub struct URLHintContract { impl URLHintContract { pub fn new(client: Arc) -> Self { - let urlhint = Interface::load(include_bytes!("./urlhint.json")).expect("urlhint.json is valid ABI"); - let registrar = Interface::load(include_bytes!("./registrar.json")).expect("registrar.json is valid ABI"); + let urlhint = Interface::load(include_bytes!("../res/urlhint.json")).expect("urlhint.json is valid ABI"); + let registrar = Interface::load(include_bytes!("../res/registrar.json")).expect("registrar.json is valid ABI"); URLHintContract { urlhint: Contract::new(urlhint), From cc8a9d410b235a17c59ebfc9067c56b53dac56b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sun, 20 Nov 2016 18:30:54 +0100 Subject: [PATCH 39/41] Adding fetch API to the crate --- Cargo.lock | 1 + ethcore/hash-fetch/Cargo.toml | 1 + ethcore/hash-fetch/src/client.rs | 114 ++++++++++++++++++++++++++++++ ethcore/hash-fetch/src/lib.rs | 8 +++ ethcore/hash-fetch/src/urlhint.rs | 48 ++++++++----- util/bigint/src/uint.rs | 1 + util/fetch/src/lib.rs | 2 +- 7 files changed, 155 insertions(+), 20 deletions(-) create mode 100644 ethcore/hash-fetch/src/client.rs diff --git a/Cargo.lock b/Cargo.lock index a590d479c..85aac6e54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -372,6 +372,7 @@ version = "1.5.0" dependencies = [ "ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore-util 1.5.0", + "fetch 0.1.0", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ethcore/hash-fetch/Cargo.toml b/ethcore/hash-fetch/Cargo.toml index f1a65bfc8..4fb724c08 100644 --- a/ethcore/hash-fetch/Cargo.toml +++ b/ethcore/hash-fetch/Cargo.toml @@ -11,4 +11,5 @@ log = "0.3" rustc-serialize = "0.3" ethabi = "0.2.2" mime_guess = "1.6.1" +fetch = { path = "../../util/fetch" } ethcore-util = { path = "../../util" } diff --git a/ethcore/hash-fetch/src/client.rs b/ethcore/hash-fetch/src/client.rs new file mode 100644 index 000000000..f5d19afa5 --- /dev/null +++ b/ethcore/hash-fetch/src/client.rs @@ -0,0 +1,114 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! Hash-addressed content resolver & fetcher. + +use std::{io, fs}; +use std::sync::Arc; +use std::path::PathBuf; + +use util::{Mutex, H256, sha3}; +use fetch::{Fetch, FetchError, Client as FetchClient}; + +use urlhint::{ContractClient, URLHintContract, URLHint, URLHintResult}; + +/// API for fetching by hash. +pub trait HashFetch { + /// Fetch hash-addressed content. + /// Parameters: + /// 1. `hash` - content hash + /// 2. `on_done` - callback function invoked when the content is ready (or there was error during fetch) + /// + /// This function may fail immediately when fetch cannot be initialized or content cannot be resolved. + fn fetch(&self, hash: H256, on_done: Box) + Send>) -> Result<(), Error>; +} + +/// Hash-fetching error. +#[derive(Debug)] +pub enum Error { + /// Hash could not be resolved to a valid content address. + NoResolution, + /// Downloaded content hash does not match. + HashMismatch { expected: H256, got: H256 }, + /// IO Error while validating hash. + IO(io::Error), + /// Error during fetch. + Fetch(FetchError), +} + +impl From for Error { + fn from(error: FetchError) -> Self { + Error::Fetch(error) + } +} + +impl From for Error { + fn from(error: io::Error) -> Self { + Error::IO(error) + } +} + +/// Default Hash-fetching client using on-chain contract to resolve hashes to URLs. +pub struct Client { + contract: URLHintContract, + fetch: Mutex, +} + +impl Client { + /// Creates new instance of the `Client` given on-chain contract client. + pub fn new(contract: Arc) -> Self { + Client { + contract: URLHintContract::new(contract), + fetch: Mutex::new(FetchClient::default()), + } + } +} + +impl HashFetch for Client { + fn fetch(&self, hash: H256, on_done: Box) + Send>) -> Result<(), Error> { + debug!(target: "dapps", "Fetching: {:?}", hash); + + let url = try!( + self.contract.resolve(hash.to_vec()).map(|content| match content { + URLHintResult::Dapp(dapp) => { + dapp.url() + }, + URLHintResult::Content(content) => { + content.url + }, + }).ok_or_else(|| Error::NoResolution) + ); + + debug!(target: "dapps", "Resolved {:?} to {:?}. Fetching...", hash, url); + + self.fetch.lock().request_async(&url, Default::default(), Box::new(move |result| { + fn validate_hash(hash: H256, result: Result) -> Result { + let path = try!(result); + let mut file_reader = io::BufReader::new(try!(fs::File::open(&path))); + let content_hash = try!(sha3(&mut file_reader)); + + if content_hash != hash { + Err(Error::HashMismatch{ got: content_hash, expected: hash }) + } else { + Ok(path) + } + } + + debug!(target: "dapps", "Content fetched, validating hash ({:?})", hash); + on_done(validate_hash(hash, result)) + })).map_err(Into::into) + } +} diff --git a/ethcore/hash-fetch/src/lib.rs b/ethcore/hash-fetch/src/lib.rs index ac613a558..ffb74b260 100644 --- a/ethcore/hash-fetch/src/lib.rs +++ b/ethcore/hash-fetch/src/lib.rs @@ -14,12 +14,20 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! Hash-addressed content resolver & fetcher. + +#![warn(missing_docs)] + #[macro_use] extern crate log; extern crate rustc_serialize; extern crate mime_guess; extern crate ethabi; extern crate ethcore_util as util; +extern crate fetch; + +mod client; pub mod urlhint; +pub use client::{HashFetch, Client}; diff --git a/ethcore/hash-fetch/src/urlhint.rs b/ethcore/hash-fetch/src/urlhint.rs index b7f905144..9cbd13b1e 100644 --- a/ethcore/hash-fetch/src/urlhint.rs +++ b/ethcore/hash-fetch/src/urlhint.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! URLHint Contract + use std::fmt; use std::sync::Arc; use rustc_serialize::hex::ToHex; @@ -24,15 +26,30 @@ use util::{Address, Bytes, Hashable}; const COMMIT_LEN: usize = 20; +/// RAW Contract interface. +/// Should execute transaction using current blockchain state. +pub trait ContractClient: Send + Sync { + /// Get registrar address + fn registrar(&self) -> Result; + /// Call Contract + fn call(&self, address: Address, data: Bytes) -> Result; +} + +/// Github-hosted dapp. #[derive(Debug, PartialEq)] pub struct GithubApp { + /// Github Account pub account: String, + /// Github Repository pub repo: String, + /// Commit on Github pub commit: [u8;COMMIT_LEN], + /// Dapp owner address pub owner: Address, } impl GithubApp { + /// Returns URL of this Github-hosted dapp package. pub fn url(&self) -> String { // Since https fetcher doesn't support redirections we use direct link // format!("https://github.com/{}/{}/archive/{}.zip", self.account, self.repo, self.commit.to_hex()) @@ -53,22 +70,17 @@ impl GithubApp { } } +/// Hash-Addressed Content #[derive(Debug, PartialEq)] pub struct Content { + /// URL of the content pub url: String, + /// MIME type of the content pub mime: String, + /// Content owner address pub owner: Address, } -/// RAW Contract interface. -/// Should execute transaction using current blockchain state. -pub trait ContractClient: Send + Sync { - /// Get registrar address - fn registrar(&self) -> Result; - /// Call Contract - fn call(&self, address: Address, data: Bytes) -> Result; -} - /// Result of resolving id to URL #[derive(Debug, PartialEq)] pub enum URLHintResult { @@ -84,6 +96,7 @@ pub trait URLHint { fn resolve(&self, id: Bytes) -> Option; } +/// `URLHintContract` API pub struct URLHintContract { urlhint: Contract, registrar: Contract, @@ -91,6 +104,7 @@ pub struct URLHintContract { } impl URLHintContract { + /// Creates new `URLHintContract` pub fn new(client: Arc) -> Self { let urlhint = Interface::load(include_bytes!("../res/urlhint.json")).expect("urlhint.json is valid ABI"); let registrar = Interface::load(include_bytes!("../res/registrar.json")).expect("registrar.json is valid ABI"); @@ -244,11 +258,6 @@ fn guess_mime_type(url: &str) -> Option { }) } -#[cfg(test)] -pub fn test_guess_mime_type(url: &str) -> Option { - guess_mime_type(url) -} - fn as_string(e: T) -> String { format!("{:?}", e) } @@ -260,6 +269,7 @@ mod tests { use rustc_serialize::hex::FromHex; use super::*; + use super::guess_mime_type; use util::{Bytes, Address, Mutex, ToPretty}; struct FakeRegistrar { @@ -390,10 +400,10 @@ mod tests { let url5 = "https://ethcore.io/parity.png"; - assert_eq!(test_guess_mime_type(url1), None); - assert_eq!(test_guess_mime_type(url2), Some("image/png".into())); - assert_eq!(test_guess_mime_type(url3), Some("image/png".into())); - assert_eq!(test_guess_mime_type(url4), Some("image/jpeg".into())); - assert_eq!(test_guess_mime_type(url5), Some("image/png".into())); + assert_eq!(guess_mime_type(url1), None); + assert_eq!(guess_mime_type(url2), Some("image/png".into())); + assert_eq!(guess_mime_type(url3), Some("image/png".into())); + assert_eq!(guess_mime_type(url4), Some("image/jpeg".into())); + assert_eq!(guess_mime_type(url5), Some("image/png".into())); } } diff --git a/util/bigint/src/uint.rs b/util/bigint/src/uint.rs index dab00537e..f4dd91140 100644 --- a/util/bigint/src/uint.rs +++ b/util/bigint/src/uint.rs @@ -683,6 +683,7 @@ macro_rules! construct_uint { bytes[i] = (arr[pos] >> ((rev % 8) * 8)) as u8; } } + #[inline] fn exp10(n: usize) -> Self { match n { diff --git a/util/fetch/src/lib.rs b/util/fetch/src/lib.rs index 7ab38604b..8ec9e0ddd 100644 --- a/util/fetch/src/lib.rs +++ b/util/fetch/src/lib.rs @@ -26,4 +26,4 @@ extern crate rand; pub mod client; pub mod fetch_file; -pub use self::client::{Client, Fetch, FetchError, FetchResult}; \ No newline at end of file +pub use self::client::{Client, Fetch, FetchError, FetchResult}; From 94328e97845aef1ced5078ebd9ab80b156fb8643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sun, 20 Nov 2016 22:40:23 +0100 Subject: [PATCH 40/41] Fixing main --- Cargo.lock | 1 + Cargo.toml | 1 + parity/dapps.rs | 2 +- parity/main.rs | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 85aac6e54..609d02dab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,7 @@ dependencies = [ "ethcore 1.5.0", "ethcore-dapps 1.5.0", "ethcore-devtools 1.4.0", + "ethcore-hash-fetch 1.5.0", "ethcore-io 1.5.0", "ethcore-ipc 1.4.0", "ethcore-ipc-codegen 1.4.0", diff --git a/Cargo.toml b/Cargo.toml index fe72d67ec..a8d7ba794 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ ethcore-ipc-nano = { path = "ipc/nano" } ethcore-ipc = { path = "ipc/rpc" } ethcore-ipc-hypervisor = { path = "ipc/hypervisor" } ethcore-logger = { path = "logger" } +ethcore-hash-fetch = { path = "ethcore/hash-fetch" } rlp = { path = "util/rlp" } ethcore-stratum = { path = "stratum" } ethcore-dapps = { path = "dapps", optional = true } diff --git a/parity/dapps.rs b/parity/dapps.rs index 80f2f7060..16ae4dd98 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -110,7 +110,7 @@ mod server { use rpc_apis; use ethcore_rpc::is_major_importing; - use ethcore_dapps::ContractClient; + use hash_fetch::urlhint::ContractClient; pub use ethcore_dapps::Server as WebappServer; diff --git a/parity/main.rs b/parity/main.rs index 0fc88d8e7..274d29de2 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -42,6 +42,7 @@ extern crate ethcore_ipc_nano as nanoipc; extern crate serde; extern crate serde_json; extern crate rlp; +extern crate ethcore_hash_fetch as hash_fetch; extern crate json_ipc_server as jsonipc; From 21b2b4ac273137839c5c7dcb4919f193d60a16c6 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Mon, 21 Nov 2016 09:37:54 +0000 Subject: [PATCH 41/41] [ci skip] js-precompiled 20161121-093611 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 69a964d11..02e45b49e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1263,7 +1263,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#2f78566b69c96e21ca1799cbf04d7a4d510c6240" +source = "git+https://github.com/ethcore/js-precompiled.git#587684374a12bf715151dd987a552a3d61e42972" 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 d4c418f37..7f4157cad 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.57", + "version": "0.2.58", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ",