diff --git a/js/src/util/check-if-tx-failed.js b/js/src/util/check-if-tx-failed.js
new file mode 100644
index 000000000..39689bedd
--- /dev/null
+++ b/js/src/util/check-if-tx-failed.js
@@ -0,0 +1,28 @@
+// 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 checkIfTxFailed = (api, tx, gasSent) => {
+ return api.pollMethod('eth_getTransactionReceipt', tx)
+ .then((receipt) => {
+ // TODO: Right now, there's no way to tell wether the EVM code crashed.
+ // Because you usually send a bit more gas than estimated (to make sure
+ // it gets mined quickly), we transaction probably failed if all the gas
+ // has been used up.
+ return receipt.gasUsed.eq(gasSent);
+ });
+};
+
+export default checkIfTxFailed;
diff --git a/js/src/util/nullable-proptype.js b/js/src/util/nullable-proptype.js
new file mode 100644
index 000000000..331be6c18
--- /dev/null
+++ b/js/src/util/nullable-proptype.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
.
+
+import { PropTypes } from 'react';
+
+export default function (type) {
+ return PropTypes.oneOfType([ PropTypes.oneOf([ null ]), type ]);
+}
diff --git a/js/src/util/wait-for-block-confirmations.js b/js/src/util/wait-for-block-confirmations.js
new file mode 100644
index 000000000..79ba2be25
--- /dev/null
+++ b/js/src/util/wait-for-block-confirmations.js
@@ -0,0 +1,44 @@
+// 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 isValidReceipt = (receipt) => {
+ return receipt && receipt.blockNumber && receipt.blockNumber.gt(0);
+};
+
+const waitForConfirmations = (api, tx, confirmations) => {
+ return new Promise((resolve, reject) => {
+ api.pollMethod('eth_getTransactionReceipt', tx, isValidReceipt)
+ .then((receipt) => {
+ let subscription;
+ api.subscribe('eth_blockNumber', (err, block) => {
+ if (err) {
+ reject(err);
+ } else if (block.minus(confirmations - 1).gte(receipt.blockNumber)) {
+ if (subscription) {
+ api.unsubscribe(subscription);
+ }
+ resolve();
+ }
+ })
+ .then((_subscription) => {
+ subscription = _subscription;
+ })
+ .catch(reject);
+ });
+ });
+};
+
+export default waitForConfirmations;
diff --git a/js/src/views/Account/account.js b/js/src/views/Account/account.js
index 902e3e7c1..86a76073e 100644
--- a/js/src/views/Account/account.js
+++ b/js/src/views/Account/account.js
@@ -20,8 +20,9 @@ import { bindActionCreators } from 'redux';
import ContentCreate from 'material-ui/svg-icons/content/create';
import ContentSend from 'material-ui/svg-icons/content/send';
import LockIcon from 'material-ui/svg-icons/action/lock';
+import VerifyIcon from 'material-ui/svg-icons/action/verified-user';
-import { EditMeta, Shapeshift, Transfer, PasswordManager } from '../../modals';
+import { EditMeta, Shapeshift, SMSVerification, Transfer, PasswordManager } from '../../modals';
import { Actionbar, Button, Page } from '../../ui';
import shapeshiftBtn from '../../../assets/images/shapeshift-btn.png';
@@ -29,9 +30,15 @@ import shapeshiftBtn from '../../../assets/images/shapeshift-btn.png';
import Header from './Header';
import Transactions from './Transactions';
+import VerificationStore from '../../modals/SMSVerification/store';
+
import styles from './account.css';
class Account extends Component {
+ static contextTypes = {
+ api: PropTypes.object.isRequired
+ }
+
static propTypes = {
params: PropTypes.object,
accounts: PropTypes.object,
@@ -45,10 +52,20 @@ class Account extends Component {
state = {
showEditDialog: false,
showFundDialog: false,
+ showVerificationDialog: false,
+ verificationStore: null,
showTransferDialog: false,
showPasswordDialog: false
}
+ componentDidMount () {
+ const { api } = this.context;
+ const { address } = this.props.params;
+
+ const store = new VerificationStore(api, address);
+ this.setState({ verificationStore: store });
+ }
+
render () {
const { accounts, balances, isTest } = this.props;
const { address } = this.props.params;
@@ -64,6 +81,7 @@ class Account extends Component {
{ this.renderEditDialog(account) }
{ this.renderFundDialog() }
+ { this.renderVerificationDialog() }
{ this.renderTransferDialog() }
{ this.renderPasswordDialog() }
{ this.renderActionbar() }
@@ -99,6 +117,11 @@ class Account extends Component {
icon={
}
label='shapeshift'
onClick={ this.onShapeshiftAccountClick } />,
+
}
+ label='Verify'
+ onClick={ this.openVerification } />,
}
@@ -149,6 +172,22 @@ class Account extends Component {
);
}
+ renderVerificationDialog () {
+ if (!this.state.showVerificationDialog) {
+ return null;
+ }
+
+ const store = this.state.verificationStore;
+ const { address } = this.props.params;
+
+ return (
+
+ );
+ }
+
renderTransferDialog () {
const { showTransferDialog } = this.state;
@@ -205,6 +244,14 @@ class Account extends Component {
this.onShapeshiftAccountClick();
}
+ openVerification = () => {
+ this.setState({ showVerificationDialog: true });
+ }
+
+ onVerificationClose = () => {
+ this.setState({ showVerificationDialog: false });
+ }
+
onTransferClick = () => {
this.setState({
showTransferDialog: !this.state.showTransferDialog
diff --git a/js/src/views/Signer/components/SignRequest/SignRequest.js b/js/src/views/Signer/components/SignRequest/SignRequest.js
index 395bc2c7f..719ddeec9 100644
--- a/js/src/views/Signer/components/SignRequest/SignRequest.js
+++ b/js/src/views/Signer/components/SignRequest/SignRequest.js
@@ -15,6 +15,7 @@
// along with Parity. If not, see
.
import React, { Component, PropTypes } from 'react';
+import nullable from '../../../../util/nullable-proptype';
import Account from '../Account';
import TransactionPendingForm from '../TransactionPendingForm';
@@ -22,8 +23,6 @@ import TxHashLink from '../TxHashLink';
import styles from './SignRequest.css';
-const nullable = (type) => React.PropTypes.oneOfType([ React.PropTypes.oneOf([ null ]), type ]);
-
export default class SignRequest extends Component {
static contextTypes = {
api: PropTypes.object
diff --git a/js/src/views/Signer/components/TransactionFinished/TransactionFinished.js b/js/src/views/Signer/components/TransactionFinished/TransactionFinished.js
index 08ed1f7ed..00d6a057f 100644
--- a/js/src/views/Signer/components/TransactionFinished/TransactionFinished.js
+++ b/js/src/views/Signer/components/TransactionFinished/TransactionFinished.js
@@ -15,6 +15,7 @@
// along with Parity. If not, see
.
import React, { Component, PropTypes } from 'react';
+import nullable from '../../../../util/nullable-proptype';
import CircularProgress from 'material-ui/CircularProgress';
@@ -29,8 +30,6 @@ import styles from './TransactionFinished.css';
import * as tUtil from '../util/transaction';
import { capitalize } from '../util/util';
-const nullable = (type) => React.PropTypes.oneOfType([ React.PropTypes.oneOf([ null ]), type ]);
-
export default class TransactionFinished extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
diff --git a/mac/install-readme.txt b/mac/install-readme.txt
index efc86ffdf..5a6de4580 100644
--- a/mac/install-readme.txt
+++ b/mac/install-readme.txt
@@ -14,7 +14,7 @@ To temporarily disable Parity Wallet (and stop Parity) use:
To completely uninstall Parity Wallet use:
- sudo -c /usr/local/libexec/uninstall-parity.sh
+ sudo /usr/local/libexec/uninstall-parity.sh
-Parity is distributed under the terms of the GPL.
\ No newline at end of file
+Parity is distributed under the terms of the GPL.
diff --git a/parity/run.rs b/parity/run.rs
index 2cc791f8c..aae4db748 100644
--- a/parity/run.rs
+++ b/parity/run.rs
@@ -203,11 +203,8 @@ pub fn execute(cmd: RunCmd, logger: Arc
) -> Result<(), String> {
sync_config.fork_block = spec.fork_block();
sync_config.warp_sync = cmd.warp_sync;
- // prepare account provider
- let account_provider = Arc::new(try!(prepare_account_provider(&cmd.dirs, cmd.acc_conf)));
-
// create miner
- let miner = Miner::new(cmd.miner_options, cmd.gas_pricer.into(), &spec, Some(account_provider.clone()));
+ let miner = Miner::new(cmd.miner_options, cmd.gas_pricer.into(), &spec);
miner.set_author(cmd.miner_extras.author);
miner.set_gas_floor_target(cmd.miner_extras.gas_floor_target);
miner.set_gas_ceil_target(cmd.miner_extras.gas_ceil_target);
@@ -241,6 +238,9 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> {
// create supervisor
let mut hypervisor = modules::hypervisor(&cmd.dirs.ipc_path());
+ // prepare account provider
+ let account_provider = Arc::new(try!(prepare_account_provider(&cmd.dirs, cmd.acc_conf)));
+
// create client service.
let service = try!(ClientService::start(
client_config,
diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs
index 2f5131f32..1ff5e1771 100644
--- a/rpc/src/v1/tests/eth.rs
+++ b/rpc/src/v1/tests/eth.rs
@@ -50,7 +50,7 @@ fn sync_provider() -> Arc {
}))
}
-fn miner_service(spec: &Spec, accounts: Arc) -> Arc {
+fn miner_service(spec: &Spec) -> Arc {
Miner::new(
MinerOptions {
new_work_notify: vec![],
@@ -69,7 +69,6 @@ fn miner_service(spec: &Spec, accounts: Arc) -> Arc {
},
GasPricer::new_fixed(20_000_000_000u64.into()),
&spec,
- Some(accounts),
)
}
@@ -116,7 +115,8 @@ impl EthTester {
fn from_spec(spec: Spec) -> Self {
let dir = RandomTempPath::new();
let account_provider = account_provider();
- let miner_service = miner_service(&spec, account_provider.clone());
+ spec.engine.register_account_provider(account_provider.clone());
+ let miner_service = miner_service(&spec);
let snapshot_service = snapshot_service();
let db_config = ::util::kvdb::DatabaseConfig::with_columns(::ethcore::db::NUM_COLUMNS);
diff --git a/sync/src/block_sync.rs b/sync/src/block_sync.rs
index 277067537..7c3cbf2d7 100644
--- a/sync/src/block_sync.rs
+++ b/sync/src/block_sync.rs
@@ -33,6 +33,7 @@ const MAX_BODIES_TO_REQUEST: usize = 64;
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;
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
/// Downloader state
@@ -62,6 +63,14 @@ pub enum BlockRequest {
},
}
+/// Indicates sync action
+pub enum DownloadAction {
+ /// Do nothing
+ None,
+ /// Reset downloads for all peers
+ Reset
+}
+
#[derive(Eq, PartialEq, Debug)]
pub enum BlockDownloaderImportError {
/// Imported data is rejected as invalid.
@@ -175,11 +184,11 @@ impl BlockDownloader {
}
/// Add new block headers.
- pub fn import_headers(&mut self, io: &mut SyncIo, r: &UntrustedRlp, expected_hash: Option) -> Result<(), BlockDownloaderImportError> {
+ pub fn import_headers(&mut self, io: &mut SyncIo, r: &UntrustedRlp, expected_hash: Option) -> Result {
let item_count = r.item_count();
if self.state == State::Idle {
trace!(target: "sync", "Ignored unexpected block headers");
- return Ok(())
+ return Ok(DownloadAction::None)
}
if item_count == 0 && (self.state == State::Blocks) {
return Err(BlockDownloaderImportError::Invalid);
@@ -188,6 +197,7 @@ impl BlockDownloader {
let mut headers = Vec::new();
let mut hashes = Vec::new();
let mut valid_response = item_count == 0; //empty response is valid
+ let mut any_known = false;
for i in 0..item_count {
let info: BlockHeader = try!(r.val_at(i).map_err(|e| {
trace!(target: "sync", "Error decoding block header RLP: {:?}", e);
@@ -200,6 +210,7 @@ impl BlockDownloader {
valid_response = expected == info.hash()
}
}
+ any_known = any_known || self.blocks.contains_head(&info.hash());
if self.blocks.contains(&info.hash()) {
trace!(target: "sync", "Skipping existing block header {} ({:?})", number, info.hash());
continue;
@@ -245,17 +256,22 @@ impl BlockDownloader {
trace!(target: "sync", "Received {} subchain heads, proceeding to download", headers.len());
self.blocks.reset_to(hashes);
self.state = State::Blocks;
+ return Ok(DownloadAction::Reset);
}
},
State::Blocks => {
let count = headers.len();
+ // At least one of the heades must advance the subchain. Otherwise they are all useless.
+ if !any_known {
+ return Err(BlockDownloaderImportError::Useless);
+ }
self.blocks.insert_headers(headers);
trace!(target: "sync", "Inserted {} headers", count);
},
_ => trace!(target: "sync", "Unexpected headers({})", headers.len()),
}
- Ok(())
+ Ok(DownloadAction::None)
}
/// Called by peer once it has new block bodies
@@ -342,22 +358,24 @@ impl BlockDownloader {
}
/// Find some headers or blocks to download for a peer.
- pub fn request_blocks(&mut self, io: &mut SyncIo) -> Option {
+ pub fn request_blocks(&mut self, io: &mut SyncIo, num_active_peers: usize) -> Option {
match self.state {
State::Idle => {
self.start_sync_round(io);
- return self.request_blocks(io);
+ return self.request_blocks(io, num_active_peers);
},
State::ChainHead => {
- // Request subchain headers
- trace!(target: "sync", "Starting sync with better chain");
- // Request MAX_HEADERS_TO_REQUEST - 2 headers apart so that
- // MAX_HEADERS_TO_REQUEST would include headers for neighbouring subchains
- return Some(BlockRequest::Headers {
- start: self.last_imported_hash.clone(),
- count: SUBCHAIN_SIZE,
- skip: (MAX_HEADERS_TO_REQUEST - 2) as u64,
- });
+ if num_active_peers < MAX_PARALLEL_SUBCHAIN_DOWNLOAD {
+ // Request subchain headers
+ trace!(target: "sync", "Starting sync with better chain");
+ // Request MAX_HEADERS_TO_REQUEST - 2 headers apart so that
+ // MAX_HEADERS_TO_REQUEST would include headers for neighbouring subchains
+ return Some(BlockRequest::Headers {
+ start: self.last_imported_hash.clone(),
+ count: SUBCHAIN_SIZE,
+ skip: (MAX_HEADERS_TO_REQUEST - 2) as u64,
+ });
+ }
},
State::Blocks => {
// check to see if we need to download any block bodies first
diff --git a/sync/src/blocks.rs b/sync/src/blocks.rs
index ed608d9c1..bf0c4b244 100644
--- a/sync/src/blocks.rs
+++ b/sync/src/blocks.rs
@@ -301,11 +301,16 @@ impl BlockCollection {
self.heads.len() == 0 || (self.heads.len() == 1 && self.head.map_or(false, |h| h == self.heads[0]))
}
- /// Chech is collection contains a block header.
+ /// Check if collection contains a block header.
pub fn contains(&self, hash: &H256) -> bool {
self.blocks.contains_key(hash)
}
+ /// Check if collection contains a block header.
+ pub fn contains_head(&self, hash: &H256) -> bool {
+ self.heads.contains(hash)
+ }
+
/// Return used heap size.
pub fn heap_size(&self) -> usize {
self.heads.heap_size_of_children()
diff --git a/sync/src/chain.rs b/sync/src/chain.rs
index e9cae7813..d4db3736d 100644
--- a/sync/src/chain.rs
+++ b/sync/src/chain.rs
@@ -37,7 +37,7 @@
/// Workflow for `ChainHead` state.
/// In this state we try to get subchain headers with a single `GetBlockHeaders` request.
/// On `NewPeer` / On `Restart`:
-/// If peer's total difficulty is higher, request N/M headers with interval M+1 starting from l
+/// If peer's total difficulty is higher and there are less than 5 peers downloading, request N/M headers with interval M+1 starting from l
/// On `BlockHeaders(R)`:
/// If R is empty:
/// If l is equal to genesis block hash or l is more than 1000 blocks behind our best hash:
@@ -49,8 +49,8 @@
/// Else
/// Set S to R, set s to `Blocks`.
///
-///
/// All other messages are ignored.
+///
/// Workflow for `Blocks` state.
/// In this state we download block headers and bodies from multiple peers.
/// On `NewPeer` / On `Restart`:
@@ -62,7 +62,9 @@
///
/// On `BlockHeaders(R)`:
/// If R is empty remove current peer from P and restart.
-/// Validate received headers. For each header find a parent in H or R or the blockchain. Restart if there is a block with unknown parent.
+/// Validate received headers:
+/// For each header find a parent in H or R or the blockchain. Restart if there is a block with unknown parent.
+/// Find at least one header from the received list in S. Restart if there is none.
/// Go to `CollectBlocks`.
///
/// On `BlockBodies(R)`:
@@ -98,7 +100,7 @@ use ethcore::snapshot::{ManifestData, RestorationStatus};
use sync_io::SyncIo;
use time;
use super::SyncConfig;
-use block_sync::{BlockDownloader, BlockRequest, BlockDownloaderImportError as DownloaderImportError};
+use block_sync::{BlockDownloader, BlockRequest, BlockDownloaderImportError as DownloaderImportError, DownloadAction};
use snapshot::{Snapshot, ChunkType};
use rand::{thread_rng, Rng};
use api::{PeerInfo as PeerInfoDigest, WARP_SYNC_PROTOCOL_ID};
@@ -306,6 +308,15 @@ impl PeerInfo {
fn is_allowed(&self) -> bool {
self.confirmation != ForkConfirmation::Unconfirmed && !self.expired
}
+
+ fn reset_asking(&mut self) {
+ self.asking_blocks.clear();
+ self.asking_hash = None;
+ // mark any pending requests as expired
+ if self.asking != PeerAsking::Nothing && self.is_allowed() {
+ self.expired = true;
+ }
+ }
}
/// Blockchain sync handler.
@@ -425,12 +436,7 @@ impl ChainSync {
}
for (_, ref mut p) in &mut self.peers {
if p.block_set != Some(BlockSet::OldBlocks) {
- p.asking_blocks.clear();
- p.asking_hash = None;
- // mark any pending requests as expired
- if p.asking != PeerAsking::Nothing && p.is_allowed() {
- p.expired = true;
- }
+ p.reset_asking();
}
}
self.state = SyncState::Idle;
@@ -643,8 +649,9 @@ impl ChainSync {
self.clear_peer_download(peer_id);
let expected_hash = self.peers.get(&peer_id).and_then(|p| p.asking_hash);
+ let allowed = self.peers.get(&peer_id).map(|p| p.is_allowed()).unwrap_or(false);
let block_set = self.peers.get(&peer_id).and_then(|p| p.block_set).unwrap_or(BlockSet::NewBlocks);
- if !self.reset_peer_asking(peer_id, PeerAsking::BlockHeaders) || expected_hash.is_none() {
+ if !self.reset_peer_asking(peer_id, PeerAsking::BlockHeaders) || expected_hash.is_none() || !allowed {
trace!(target: "sync", "{}: Ignored unexpected headers, expected_hash = {:?}", peer_id, expected_hash);
self.continue_sync(io);
return Ok(());
@@ -689,7 +696,15 @@ impl ChainSync {
self.continue_sync(io);
return Ok(());
},
- Ok(()) => (),
+ Ok(DownloadAction::Reset) => {
+ // mark all outstanding requests as expired
+ trace!("Resetting downloads for {:?}", block_set);
+ for (_, ref mut p) in self.peers.iter_mut().filter(|&(_, ref p)| p.block_set == Some(block_set)) {
+ p.reset_asking();
+ }
+
+ }
+ Ok(DownloadAction::None) => {},
}
self.collect_blocks(io, block_set);
@@ -981,7 +996,7 @@ impl ChainSync {
return Ok(());
}
self.clear_peer_download(peer_id);
- if !self.reset_peer_asking(peer_id, PeerAsking::SnapshotData) || self.state != SyncState::SnapshotData {
+ if !self.reset_peer_asking(peer_id, PeerAsking::SnapshotData) || (self.state != SyncState::SnapshotData && self.state != SyncState::SnapshotWaiting) {
trace!(target: "sync", "{}: Ignored unexpected snapshot data", peer_id);
self.continue_sync(io);
return Ok(());
@@ -1113,6 +1128,7 @@ impl ChainSync {
};
let chain_info = io.chain().chain_info();
let syncing_difficulty = chain_info.pending_total_difficulty;
+ let num_active_peers = self.peers.values().filter(|p| p.asking != PeerAsking::Nothing).count();
let higher_difficulty = peer_difficulty.map_or(true, |pd| pd > syncing_difficulty);
if force || self.state == SyncState::NewBlocks || higher_difficulty || self.old_blocks.is_some() {
@@ -1130,7 +1146,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
- if let Some(request) = self.new_blocks.request_blocks(io) {
+ 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 {
self.state = SyncState::Blocks;
@@ -1139,7 +1155,7 @@ impl ChainSync {
}
}
- if let Some(request) = self.old_blocks.as_mut().and_then(|d| d.request_blocks(io)) {
+ if let Some(request) = self.old_blocks.as_mut().and_then(|d| d.request_blocks(io, num_active_peers)) {
self.request_blocks(io, peer_id, request, BlockSet::OldBlocks);
return;
}