diff --git a/Cargo.lock b/Cargo.lock index 572d92616..f7bfc45e5 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#b2513e92603b473799d653583bd86771e0063c08" +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/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 } } } }, 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) diff --git a/js/package.json b/js/package.json index f9ef9c181..9a45fbf18 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.52", + "version": "0.2.54", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", @@ -102,9 +102,10 @@ "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-hot-loader": "^1.3.0", + "react-dom": "~15.3.2", + "react-hot-loader": "~1.3.0", "rucksack-css": "^0.8.6", "sinon": "^1.17.4", "sinon-as-promised": "^4.0.2", @@ -114,7 +115,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 +135,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 +143,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", 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..ba74ceaea --- /dev/null +++ b/js/src/dapps/githubhint/Events/events.js @@ -0,0 +1,52 @@ +// 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'; 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 } /> { + this._store.dispatch(statusCollection({ gasLimit: block.gasLimit })); + }) + .catch((error) => { + console.warn('status._subscribeBlockNumber', 'getBlockByNumber', error); + }); }) .then((subscriptionId) => { console.log('status._subscribeBlockNumber', 'subscriptionId', subscriptionId); diff --git a/js/src/redux/providers/statusReducer.js b/js/src/redux/providers/statusReducer.js index b0450083a..f0b6947c0 100644 --- a/js/src/redux/providers/statusReducer.js +++ b/js/src/redux/providers/statusReducer.js @@ -28,6 +28,7 @@ const initialState = { enode: '', extraData: '', gasFloorTarget: new BigNumber(0), + gasLimit: new BigNumber(0), hashrate: new BigNumber(0), minGasPrice: new BigNumber(0), netChain: 'morden', 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/util/validation.js b/js/src/util/validation.js index c41cae29e..c0a58655d 100644 --- a/js/src/util/validation.js +++ b/js/src/util/validation.js @@ -37,7 +37,7 @@ export function validateAbi (abi, api) { try { abiParsed = JSON.parse(abi); - if (!api.util.isArray(abiParsed) || !abiParsed.length) { + if (!api.util.isArray(abiParsed)) { abiError = ERRORS.invalidAbi; return { abi, abiError, abiParsed }; } 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 38bb64792..72b24bd2b 100644 --- a/js/src/views/Dapps/AddDapps/AddDapps.js +++ b/js/src/views/Dapps/AddDapps/AddDapps.js @@ -51,16 +51,37 @@ export default class AddDapps extends Component { ] } visible scroll> - - { store.apps.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 Parity. 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.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.css b/js/src/views/Dapps/dapps.css index 1a38af3cf..0fb1a07b5 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,25 @@ flex: 0 1 50%; box-sizing: border-box; } + +.overlay { + background: rgba(0, 0, 0, 0.85); + bottom: 0.5em; + left: -0.125em; + position: absolute; + right: -0.125em; + 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 708c52cb5..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,6 +38,24 @@ export default class Dapps extends Component { store = new DappsStore(this.context.api); render () { + 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 (
@@ -53,14 +72,27 @@ export default class Dapps extends Component { ] } /> -
- { this.store.visible.map(this.renderApp) } -
+ { this.renderList(this.store.visibleLocal) } + { this.renderList(this.store.visibleBuiltin) } + { this.renderList(this.store.visibleNetwork, externalOverlay) }
); } + renderList (items, overlay) { + if (!items || !items.length) { + return null; + } + + return ( +
+ { overlay } + { items.map(this.renderApp) } +
+ ); + } + renderApp = (app) => { return (
); } + + onClickAcceptExternal = () => { + this.store.closeExternalOverlay(); + } } diff --git a/js/src/views/Dapps/dappsStore.js b/js/src/views/Dapps/dappsStore.js index 599dc18e9..f9fc4863c 100644 --- a/js/src/views/Dapps/dappsStore.js +++ b/js/src/views/Dapps/dappsStore.js @@ -14,39 +14,65 @@ // 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'; +const LS_KEY_EXTERNAL_ACCEPT = 'acceptExternal'; export default class DappsStore { @observable apps = []; - @observable externalApps = []; - @observable hiddenApps = []; + @observable displayApps = {}; @observable modalOpen = false; + @observable externalOverlayVisible = true; constructor (api) { this._api = api; - this._readHiddenApps(); - this._readExternalApps(); + this.loadExternalOverlay(); + 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 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.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 = () => { @@ -57,14 +83,48 @@ 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.hiddenApps = this.hiddenApps.concat(id); - this._writeHiddenApps(); + this.displayApps = Object.assign({}, this.displayApps, { [id]: { visible: false } }); + this.writeDisplayApps(); } @action showApp = (id) => { - this.hiddenApps = this.hiddenApps.filter((_id) => _id !== id); - this._writeHiddenApps(); + this.displayApps = Object.assign({}, this.displayApps, { [id]: { visible: true } }); + this.writeDisplayApps(); + } + + @action readDisplayApps = () => { + this.displayApps = store.get(LS_KEY_DISPLAY) || {}; + } + + @action writeDisplayApps = () => { + store.set(LS_KEY_DISPLAY, this.displayApps); + } + + @action addApps = (apps) => { + transaction(() => { + this.apps = this.apps + .concat(apps || []) + .sort((a, b) => a.name.localeCompare(b.name)); + + const visibility = {}; + apps.forEach((app) => { + if (!this.displayApps[app.id]) { + visibility[app.id] = { visible: app.visible }; + } + }); + + this.displayApps = Object.assign({}, this.displayApps, visibility); + }); } _getHost (api) { @@ -79,13 +139,16 @@ 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.apps.push(app); - }); - }); + return app; + }) + ); + }) + .catch((error) => { + console.warn('DappsStore:fetchBuiltinApps', error); }); } @@ -100,15 +163,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.apps.push(app)); - }); - }) + .then(this.addApps) .catch((error) => { console.warn('DappsStore:fetchLocal', error); }); @@ -132,7 +192,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([ @@ -147,7 +209,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: true }; return app; @@ -179,11 +242,7 @@ export default class DappsStore { }); }); }) - .then((apps) => { - transaction(() => { - (apps || []).forEach((app) => this.apps.push(app)); - }); - }) + .then(this.addApps) .catch((error) => { console.warn('DappsStore:fetchRegistry', error); }); @@ -201,44 +260,4 @@ export default class DappsStore { return null; }); } - - _readHiddenApps () { - const stored = localStorage.getItem(LS_KEY_HIDDEN); - - if (stored) { - try { - this.hiddenApps = JSON.parse(stored); - } catch (error) { - console.warn('DappsStore:readHiddenApps', error); - } - } - } - - _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); - } - } } 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$/, 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(); 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();