Merge branch 'miner-no-ap' into auth-bft

This commit is contained in:
keorn 2016-11-17 22:04:07 +00:00
commit 9d61071dd7
51 changed files with 1548 additions and 126 deletions

2
Cargo.lock generated
View File

@ -1249,7 +1249,7 @@ dependencies = [
[[package]] [[package]]
name = "parity-ui-precompiled" name = "parity-ui-precompiled"
version = "1.4.0" version = "1.4.0"
source = "git+https://github.com/ethcore/js-precompiled.git#957c5a66c33f3b06a7ae804ac5edc59c20e4535b" source = "git+https://github.com/ethcore/js-precompiled.git#b2513e92603b473799d653583bd86771e0063c08"
dependencies = [ dependencies = [
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]

View File

@ -68,6 +68,7 @@ pub struct AuthorityRound {
builtins: BTreeMap<Address, Builtin>, builtins: BTreeMap<Address, Builtin>,
transition_service: IoService<BlockArrived>, transition_service: IoService<BlockArrived>,
message_channel: Mutex<Option<IoChannel<ClientIoMessage>>>, message_channel: Mutex<Option<IoChannel<ClientIoMessage>>>,
account_provider: Mutex<Option<Arc<AccountProvider>>>,
step: AtomicUsize, step: AtomicUsize,
proposed: AtomicBool, proposed: AtomicBool,
} }
@ -101,6 +102,7 @@ impl AuthorityRound {
builtins: builtins, builtins: builtins,
transition_service: try!(IoService::<BlockArrived>::start()), transition_service: try!(IoService::<BlockArrived>::start()),
message_channel: Mutex::new(None), message_channel: Mutex::new(None),
account_provider: Mutex::new(None),
step: AtomicUsize::new(initial_step), step: AtomicUsize::new(initial_step),
proposed: AtomicBool::new(false) proposed: AtomicBool::new(false)
}); });
@ -219,12 +221,12 @@ impl Engine for AuthorityRound {
/// ///
/// This operation is synchronous and may (quite reasonably) not be available, in which `false` will /// This operation is synchronous and may (quite reasonably) not be available, in which `false` will
/// be returned. /// be returned.
fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option<Vec<Bytes>> { fn generate_seal(&self, block: &ExecutedBlock) -> Option<Vec<Bytes>> {
if self.proposed.load(AtomicOrdering::SeqCst) { return None; } if self.proposed.load(AtomicOrdering::SeqCst) { return None; }
let header = block.header(); let header = block.header();
let step = self.step(); let step = self.step();
if self.is_step_proposer(step, header.author()) { if self.is_step_proposer(step, header.author()) {
if let Some(ap) = accounts { if let Some(ref ap) = *self.account_provider.lock() {
// Account should be permanently unlocked, otherwise sealing will fail. // Account should be permanently unlocked, otherwise sealing will fail.
if let Ok(signature) = ap.sign(*header.author(), None, header.bare_hash()) { if let Ok(signature) = ap.sign(*header.author(), None, header.bare_hash()) {
trace!(target: "poa", "generate_seal: Issuing a block for step {}.", step); trace!(target: "poa", "generate_seal: Issuing a block for step {}.", step);
@ -307,8 +309,11 @@ impl Engine for AuthorityRound {
} }
fn register_message_channel(&self, message_channel: IoChannel<ClientIoMessage>) { fn register_message_channel(&self, message_channel: IoChannel<ClientIoMessage>) {
let mut guard = self.message_channel.lock(); *self.message_channel.lock() = Some(message_channel);
*guard = Some(message_channel); }
fn register_account_provider(&self, account_provider: Arc<AccountProvider>) {
*self.account_provider.lock() = Some(account_provider);
} }
} }
@ -382,6 +387,7 @@ mod tests {
let spec = Spec::new_test_round(); let spec = Spec::new_test_round();
let engine = &*spec.engine; let engine = &*spec.engine;
engine.register_account_provider(Arc::new(tap));
let genesis_header = spec.genesis_header(); let genesis_header = spec.genesis_header();
let mut db1 = get_temp_state_db().take(); let mut db1 = get_temp_state_db().take();
spec.ensure_db_good(&mut db1).unwrap(); spec.ensure_db_good(&mut db1).unwrap();
@ -393,16 +399,16 @@ mod tests {
let b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes, addr2, (3141562.into(), 31415620.into()), vec![]).unwrap(); let b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes, addr2, (3141562.into(), 31415620.into()), vec![]).unwrap();
let b2 = b2.close_and_lock(); let b2 = b2.close_and_lock();
if let Some(seal) = engine.generate_seal(b1.block(), Some(&tap)) { if let Some(seal) = engine.generate_seal(b1.block()) {
assert!(b1.clone().try_seal(engine, seal).is_ok()); assert!(b1.clone().try_seal(engine, seal).is_ok());
// Second proposal is forbidden. // Second proposal is forbidden.
assert!(engine.generate_seal(b1.block(), Some(&tap)).is_none()); assert!(engine.generate_seal(b1.block()).is_none());
} }
if let Some(seal) = engine.generate_seal(b2.block(), Some(&tap)) { if let Some(seal) = engine.generate_seal(b2.block()) {
assert!(b2.clone().try_seal(engine, seal).is_ok()); assert!(b2.clone().try_seal(engine, seal).is_ok());
// Second proposal is forbidden. // Second proposal is forbidden.
assert!(engine.generate_seal(b2.block(), Some(&tap)).is_none()); assert!(engine.generate_seal(b2.block()).is_none());
} }
} }

View File

@ -58,6 +58,7 @@ pub struct BasicAuthority {
params: CommonParams, params: CommonParams,
our_params: BasicAuthorityParams, our_params: BasicAuthorityParams,
builtins: BTreeMap<Address, Builtin>, builtins: BTreeMap<Address, Builtin>,
account_provider: Mutex<Option<Arc<AccountProvider>>>
} }
impl BasicAuthority { impl BasicAuthority {
@ -67,6 +68,7 @@ impl BasicAuthority {
params: params, params: params,
our_params: our_params, our_params: our_params,
builtins: builtins, builtins: builtins,
account_provider: Mutex::new(None)
} }
} }
} }
@ -113,8 +115,8 @@ impl Engine for BasicAuthority {
/// ///
/// This operation is synchronous and may (quite reasonably) not be available, in which `false` will /// This operation is synchronous and may (quite reasonably) not be available, in which `false` will
/// be returned. /// be returned.
fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option<Vec<Bytes>> { fn generate_seal(&self, block: &ExecutedBlock) -> Option<Vec<Bytes>> {
if let Some(ap) = accounts { if let Some(ref ap) = *self.account_provider.lock() {
let header = block.header(); let header = block.header();
let message = header.bare_hash(); let message = header.bare_hash();
// account should be pernamently unlocked, otherwise sealing will fail // account should be pernamently unlocked, otherwise sealing will fail
@ -179,6 +181,10 @@ impl Engine for BasicAuthority {
fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> {
t.sender().map(|_|()) // Perform EC recovery and cache sender t.sender().map(|_|()) // Perform EC recovery and cache sender
} }
fn register_account_provider(&self, ap: Arc<AccountProvider>) {
*self.account_provider.lock() = Some(ap);
}
} }
#[cfg(test)] #[cfg(test)]
@ -253,6 +259,7 @@ mod tests {
let spec = new_test_authority(); let spec = new_test_authority();
let engine = &*spec.engine; let engine = &*spec.engine;
engine.register_account_provider(Arc::new(tap));
let genesis_header = spec.genesis_header(); let genesis_header = spec.genesis_header();
let mut db_result = get_temp_state_db(); let mut db_result = get_temp_state_db();
let mut db = db_result.take(); let mut db = db_result.take();
@ -260,7 +267,7 @@ mod tests {
let last_hashes = Arc::new(vec![genesis_header.hash()]); let last_hashes = Arc::new(vec![genesis_header.hash()]);
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, addr, (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, addr, (3141562.into(), 31415620.into()), vec![]).unwrap();
let b = b.close_and_lock(); let b = b.close_and_lock();
let seal = engine.generate_seal(b.block(), Some(&tap)).unwrap(); let seal = engine.generate_seal(b.block()).unwrap();
assert!(b.try_seal(engine, seal).is_ok()); assert!(b.try_seal(engine, seal).is_ok());
} }

View File

@ -23,7 +23,6 @@ use spec::CommonParams;
use evm::Schedule; use evm::Schedule;
use block::ExecutedBlock; use block::ExecutedBlock;
use util::Bytes; use util::Bytes;
use account_provider::AccountProvider;
/// An engine which does not provide any consensus mechanism, just seals blocks internally. /// An engine which does not provide any consensus mechanism, just seals blocks internally.
pub struct InstantSeal { pub struct InstantSeal {
@ -60,7 +59,7 @@ impl Engine for InstantSeal {
fn is_sealer(&self, _author: &Address) -> Option<bool> { Some(true) } fn is_sealer(&self, _author: &Address) -> Option<bool> { Some(true) }
fn generate_seal(&self, _block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option<Vec<Bytes>> { fn generate_seal(&self, _block: &ExecutedBlock) -> Option<Vec<Bytes>> {
Some(Vec::new()) Some(Vec::new())
} }
} }
@ -69,16 +68,12 @@ impl Engine for InstantSeal {
mod tests { mod tests {
use util::*; use util::*;
use tests::helpers::*; use tests::helpers::*;
use account_provider::AccountProvider;
use spec::Spec; use spec::Spec;
use header::Header; use header::Header;
use block::*; use block::*;
#[test] #[test]
fn instant_can_seal() { fn instant_can_seal() {
let tap = AccountProvider::transient_provider();
let addr = tap.insert_account("".sha3(), "").unwrap();
let spec = Spec::new_instant(); let spec = Spec::new_instant();
let engine = &*spec.engine; let engine = &*spec.engine;
let genesis_header = spec.genesis_header(); let genesis_header = spec.genesis_header();
@ -86,10 +81,9 @@ mod tests {
let mut db = db_result.take(); let mut db = db_result.take();
spec.ensure_db_good(&mut db).unwrap(); spec.ensure_db_good(&mut db).unwrap();
let last_hashes = Arc::new(vec![genesis_header.hash()]); let last_hashes = Arc::new(vec![genesis_header.hash()]);
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, addr, (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap();
let b = b.close_and_lock(); let b = b.close_and_lock();
// Seal with empty AccountProvider. let seal = engine.generate_seal(b.block()).unwrap();
let seal = engine.generate_seal(b.block(), Some(&tap)).unwrap();
assert!(b.try_seal(engine, seal).is_ok()); assert!(b.try_seal(engine, seal).is_ok());
} }

View File

@ -114,7 +114,7 @@ pub trait Engine : Sync + Send {
/// ///
/// This operation is synchronous and may (quite reasonably) not be available, in which None will /// This operation is synchronous and may (quite reasonably) not be available, in which None will
/// be returned. /// be returned.
fn generate_seal(&self, _block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option<Vec<Bytes>> { None } fn generate_seal(&self, _block: &ExecutedBlock) -> Option<Vec<Bytes>> { None }
/// Phase 1 quick block verification. Only does checks that are cheap. `block` (the header's full block) /// Phase 1 quick block verification. Only does checks that are cheap. `block` (the header's full block)
/// may be provided for additional checks. Returns either a null `Ok` or a general error detailing the problem with import. /// may be provided for additional checks. Returns either a null `Ok` or a general error detailing the problem with import.
@ -179,5 +179,6 @@ pub trait Engine : Sync + Send {
ethash::is_new_best_block(best_total_difficulty, parent_details, new_header) ethash::is_new_best_block(best_total_difficulty, parent_details, new_header)
} }
// TODO: sealing stuff - though might want to leave this for later. /// Add an account provider useful for Engines that sign stuff.
fn register_account_provider(&self, _account_provider: Arc<AccountProvider>) {}
} }

View File

@ -19,7 +19,6 @@ use std::time::{Instant, Duration};
use util::*; use util::*;
use util::using_queue::{UsingQueue, GetAction}; use util::using_queue::{UsingQueue, GetAction};
use account_provider::AccountProvider;
use views::{BlockView, HeaderView}; use views::{BlockView, HeaderView};
use state::{State, CleanupMode}; use state::{State, CleanupMode};
use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics}; use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics};
@ -220,14 +219,13 @@ pub struct Miner {
extra_data: RwLock<Bytes>, extra_data: RwLock<Bytes>,
engine: Arc<Engine>, engine: Arc<Engine>,
accounts: Option<Arc<AccountProvider>>,
work_poster: Option<WorkPoster>, work_poster: Option<WorkPoster>,
gas_pricer: Mutex<GasPricer>, gas_pricer: Mutex<GasPricer>,
} }
impl Miner { impl Miner {
/// Creates new instance of miner. /// Creates new instance of miner.
fn new_raw(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option<Arc<AccountProvider>>) -> Miner { fn new_raw(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec) -> Miner {
let work_poster = match options.new_work_notify.is_empty() { let work_poster = match options.new_work_notify.is_empty() {
true => None, true => None,
false => Some(WorkPoster::new(&options.new_work_notify)) false => Some(WorkPoster::new(&options.new_work_notify))
@ -261,26 +259,20 @@ impl Miner {
author: RwLock::new(Address::default()), author: RwLock::new(Address::default()),
extra_data: RwLock::new(Vec::new()), extra_data: RwLock::new(Vec::new()),
options: options, options: options,
accounts: accounts,
engine: spec.engine.clone(), engine: spec.engine.clone(),
work_poster: work_poster, work_poster: work_poster,
gas_pricer: Mutex::new(gas_pricer), gas_pricer: Mutex::new(gas_pricer),
} }
} }
/// Creates new instance of miner with accounts and with given spec. /// Creates new instance of miner with given spec.
pub fn with_spec_and_accounts(spec: &Spec, accounts: Option<Arc<AccountProvider>>) -> Miner {
Miner::new_raw(Default::default(), GasPricer::new_fixed(20_000_000_000u64.into()), spec, accounts)
}
/// Creates new instance of miner without accounts, but with given spec.
pub fn with_spec(spec: &Spec) -> Miner { pub fn with_spec(spec: &Spec) -> Miner {
Miner::new_raw(Default::default(), GasPricer::new_fixed(20_000_000_000u64.into()), spec, None) Miner::new_raw(Default::default(), GasPricer::new_fixed(20_000_000_000u64.into()), spec)
} }
/// Creates new instance of a miner Arc. /// Creates new instance of a miner Arc.
pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option<Arc<AccountProvider>>) -> Arc<Miner> { pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec) -> Arc<Miner> {
Arc::new(Miner::new_raw(options, gas_pricer, spec, accounts)) Arc::new(Miner::new_raw(options, gas_pricer, spec))
} }
fn forced_sealing(&self) -> bool { fn forced_sealing(&self) -> bool {
@ -461,10 +453,7 @@ impl Miner {
/// Err(Some(block)) returns for unsuccesful sealing while Err(None) indicates misspecified engine. /// Err(Some(block)) returns for unsuccesful sealing while Err(None) indicates misspecified engine.
fn seal_block_internally(&self, block: ClosedBlock) -> Result<SealedBlock, Option<ClosedBlock>> { fn seal_block_internally(&self, block: ClosedBlock) -> Result<SealedBlock, Option<ClosedBlock>> {
trace!(target: "miner", "seal_block_internally: block has transaction - attempting internal seal."); trace!(target: "miner", "seal_block_internally: block has transaction - attempting internal seal.");
let s = self.engine.generate_seal(block.block(), match self.accounts { let s = self.engine.generate_seal(block.block());
Some(ref x) => Some(&**x),
None => None,
});
if let Some(seal) = s { if let Some(seal) = s {
trace!(target: "miner", "seal_block_internally: managed internal seal. importing..."); trace!(target: "miner", "seal_block_internally: managed internal seal. importing...");
block.lock().try_seal(&*self.engine, seal).or_else(|_| { block.lock().try_seal(&*self.engine, seal).or_else(|_| {
@ -1170,7 +1159,6 @@ mod tests {
}, },
GasPricer::new_fixed(0u64.into()), GasPricer::new_fixed(0u64.into()),
&Spec::new_test(), &Spec::new_test(),
None, // accounts provider
)).ok().expect("Miner was just created.") )).ok().expect("Miner was just created.")
} }

View File

@ -34,16 +34,43 @@ pub const KEY_LENGTH: usize = 32;
pub const KEY_ITERATIONS: usize = 10240; pub const KEY_ITERATIONS: usize = 10240;
pub const KEY_LENGTH_AES: usize = KEY_LENGTH / 2; pub const KEY_LENGTH_AES: usize = KEY_LENGTH / 2;
#[derive(PartialEq, Debug)]
pub enum ScryptError {
// log(N) < r / 16
InvalidN,
// p <= (2^31-1 * 32)/(128 * r)
InvalidP,
}
impl fmt::Display for ScryptError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let s = match *self {
ScryptError::InvalidN => "Invalid N argument of the scrypt encryption" ,
ScryptError::InvalidP => "Invalid p argument of the scrypt encryption",
};
write!(f, "{}", s)
}
}
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub enum Error { pub enum Error {
Secp(SecpError), Secp(SecpError),
Scrypt(ScryptError),
InvalidMessage, InvalidMessage,
} }
impl From<ScryptError> for Error {
fn from(err: ScryptError) -> Self {
Error::Scrypt(err)
}
}
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let s = match *self { let s = match *self {
Error::Secp(ref err) => err.to_string(), Error::Secp(ref err) => err.to_string(),
Error::Scrypt(ref err) => err.to_string(),
Error::InvalidMessage => "Invalid message".into(), Error::InvalidMessage => "Invalid message".into(),
}; };
@ -80,13 +107,23 @@ pub fn derive_key_iterations(password: &str, salt: &[u8; 32], c: u32) -> (Vec<u8
(derived_right_bits.to_vec(), derived_left_bits.to_vec()) (derived_right_bits.to_vec(), derived_left_bits.to_vec())
} }
pub fn derive_key_scrypt(password: &str, salt: &[u8; 32], n: u32, p: u32, r: u32) -> (Vec<u8>, Vec<u8>) { pub fn derive_key_scrypt(password: &str, salt: &[u8; 32], n: u32, p: u32, r: u32) -> Result<(Vec<u8>, Vec<u8>), Error> {
// sanity checks
let log_n = (32 - n.leading_zeros() - 1) as u8;
if log_n as u32 >= r * 16 {
return Err(Error::Scrypt(ScryptError::InvalidN));
}
if p as u64 > ((u32::max_value() as u64 - 1) * 32)/(128 * (r as u64)) {
return Err(Error::Scrypt(ScryptError::InvalidP));
}
let mut derived_key = vec![0u8; KEY_LENGTH]; let mut derived_key = vec![0u8; KEY_LENGTH];
let scrypt_params = ScryptParams::new(n.trailing_zeros() as u8, r, p); let scrypt_params = ScryptParams::new(log_n, r, p);
scrypt(password.as_bytes(), salt, &scrypt_params, &mut derived_key); scrypt(password.as_bytes(), salt, &scrypt_params, &mut derived_key);
let derived_right_bits = &derived_key[0..KEY_LENGTH_AES]; let derived_right_bits = &derived_key[0..KEY_LENGTH_AES];
let derived_left_bits = &derived_key[KEY_LENGTH_AES..KEY_LENGTH]; let derived_left_bits = &derived_key[KEY_LENGTH_AES..KEY_LENGTH];
(derived_right_bits.to_vec(), derived_left_bits.to_vec()) Ok((derived_right_bits.to_vec(), derived_left_bits.to_vec()))
} }
pub fn derive_mac(derived_left_bits: &[u8], cipher_text: &[u8]) -> Vec<u8> { pub fn derive_mac(derived_left_bits: &[u8], cipher_text: &[u8]) -> Vec<u8> {

View File

@ -113,7 +113,7 @@ impl Crypto {
let (derived_left_bits, derived_right_bits) = match self.kdf { let (derived_left_bits, derived_right_bits) = match self.kdf {
Kdf::Pbkdf2(ref params) => crypto::derive_key_iterations(password, &params.salt, params.c), Kdf::Pbkdf2(ref params) => crypto::derive_key_iterations(password, &params.salt, params.c),
Kdf::Scrypt(ref params) => crypto::derive_key_scrypt(password, &params.salt, params.n, params.p, params.r), Kdf::Scrypt(ref params) => try!(crypto::derive_key_scrypt(password, &params.salt, params.n, params.p, params.r)),
}; };
let mac = crypto::derive_mac(&derived_right_bits, &self.ciphertext).keccak256(); let mac = crypto::derive_mac(&derived_right_bits, &self.ciphertext).keccak256();

View File

@ -1,6 +1,6 @@
{ {
"name": "parity.js", "name": "parity.js",
"version": "0.2.50", "version": "0.2.52",
"main": "release/index.js", "main": "release/index.js",
"jsnext:main": "src/index.js", "jsnext:main": "src/index.js",
"author": "Parity Team <admin@parity.io>", "author": "Parity Team <admin@parity.io>",
@ -139,6 +139,7 @@
"mobx-react": "^3.5.8", "mobx-react": "^3.5.8",
"mobx-react-devtools": "^4.2.9", "mobx-react-devtools": "^4.2.9",
"moment": "^2.14.1", "moment": "^2.14.1",
"phoneformat.js": "^1.0.3",
"qs": "^6.3.0", "qs": "^6.3.0",
"react": "^15.2.1", "react": "^15.2.1",
"react-ace": "^4.0.0", "react-ace": "^4.0.0",

View File

@ -166,3 +166,11 @@ export function inTraceFilter (filterObject) {
return filterObject; return filterObject;
} }
export function inTraceType (whatTrace) {
if (isString(whatTrace)) {
return [whatTrace];
}
return whatTrace;
}

View File

@ -16,7 +16,7 @@
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { inAddress, inBlockNumber, inData, inFilter, inHex, inNumber10, inNumber16, inOptions } from './input'; import { inAddress, inBlockNumber, inData, inFilter, inHex, inNumber10, inNumber16, inOptions, inTraceType } from './input';
import { isAddress } from '../../../test/types'; import { isAddress } from '../../../test/types';
describe('api/format/input', () => { describe('api/format/input', () => {
@ -242,4 +242,16 @@ describe('api/format/input', () => {
}); });
}); });
}); });
describe('inTraceType', () => {
it('returns array of types as is', () => {
const types = ['vmTrace', 'trace', 'stateDiff'];
expect(inTraceType(types)).to.deep.equal(types);
});
it('formats single string type into array', () => {
const type = 'vmTrace';
expect(inTraceType(type)).to.deep.equal([type]);
});
});
}); });

View File

@ -254,3 +254,25 @@ export function outTrace (trace) {
return trace; return trace;
} }
export function outTraces (traces) {
if (traces) {
return traces.map(outTrace);
}
return traces;
}
export function outTraceReplay (trace) {
if (trace) {
Object.keys(trace).forEach((key) => {
switch (key) {
case 'trace':
trace[key] = outTraces(trace[key]);
break;
}
});
}
return trace;
}

View File

@ -20,15 +20,25 @@ describe('ethapi.trace', () => {
const ethapi = createHttpApi(); const ethapi = createHttpApi();
describe('block', () => { describe('block', () => {
it('returns the latest block', () => { it('returns the latest block traces', () => {
return ethapi.trace.block().then((block) => { return ethapi.trace.block().then((traces) => {
expect(block).to.be.ok; expect(traces).to.be.ok;
}); });
}); });
it('returns a specified block', () => { it('returns traces for a specified block', () => {
return ethapi.trace.block('0x65432').then((block) => { return ethapi.trace.block('0x65432').then((traces) => {
expect(block).to.be.ok; expect(traces).to.be.ok;
});
});
});
describe('replayTransaction', () => {
it('returns traces for a specific transaction', () => {
return ethapi.eth.getBlockByNumber().then((latestBlock) => {
return ethapi.trace.replayTransaction(latestBlock.transactions[0]).then((traces) => {
expect(traces).to.be.ok;
});
}); });
}); });
}); });

View File

@ -14,35 +14,53 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { inBlockNumber, inHex, inNumber16, inTraceFilter } from '../../format/input'; import { inBlockNumber, inData, inHex, inNumber16, inOptions, inTraceFilter, inTraceType } from '../../format/input';
import { outTrace } from '../../format/output'; import { outTraces, outTraceReplay } from '../../format/output';
export default class Trace { export default class Trace {
constructor (transport) { constructor (transport) {
this._transport = transport; this._transport = transport;
} }
block (blockNumber = 'latest') {
return this._transport
.execute('trace_block', inBlockNumber(blockNumber))
.then(outTraces);
}
call (options, blockNumber = 'latest', whatTrace = ['trace']) {
return this._transport
.execute('trace_call', inOptions(options), inBlockNumber(blockNumber), inTraceType(whatTrace))
.then(outTraceReplay);
}
filter (filterObj) { filter (filterObj) {
return this._transport return this._transport
.execute('trace_filter', inTraceFilter(filterObj)) .execute('trace_filter', inTraceFilter(filterObj))
.then(traces => traces.map(trace => outTrace(trace))); .then(outTraces);
} }
get (txHash, position) { get (txHash, position) {
return this._transport return this._transport
.execute('trace_get', inHex(txHash), inNumber16(position)) .execute('trace_get', inHex(txHash), inNumber16(position))
.then(trace => outTrace(trace)); .then(outTraces);
}
rawTransaction (data, whatTrace = ['trace']) {
return this._transport
.execute('trace_rawTransaction', inData(data), inTraceType(whatTrace))
.then(outTraceReplay);
}
replayTransaction (txHash, whatTrace = ['trace']) {
return this._transport
.execute('trace_replayTransaction', txHash, inTraceType(whatTrace))
.then(outTraceReplay);
} }
transaction (txHash) { transaction (txHash) {
return this._transport return this._transport
.execute('trace_transaction', inHex(txHash)) .execute('trace_transaction', inHex(txHash))
.then(traces => traces.map(trace => outTrace(trace))); .then(outTraces);
}
block (blockNumber = 'latest') {
return this._transport
.execute('trace_block', inBlockNumber(blockNumber))
.then(traces => traces.map(trace => outTrace(trace)));
} }
} }

View File

@ -23,6 +23,7 @@ import githubhint from './githubhint.json';
import owned from './owned.json'; import owned from './owned.json';
import registry from './registry.json'; import registry from './registry.json';
import signaturereg from './signaturereg.json'; import signaturereg from './signaturereg.json';
import smsverification from './sms-verification.json';
import tokenreg from './tokenreg.json'; import tokenreg from './tokenreg.json';
import wallet from './wallet.json'; import wallet from './wallet.json';
@ -36,6 +37,7 @@ export {
owned, owned,
registry, registry,
signaturereg, signaturereg,
smsverification,
tokenreg, tokenreg,
wallet wallet
}; };

View File

@ -0,0 +1 @@
[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"certify","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"request","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"},{"name":"_puzzle","type":"bytes32"}],"name":"puzzle","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"revoke","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_code","type":"bytes32"}],"name":"confirm","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"delegate","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setDelegate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Requested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":false,"name":"puzzle","type":"bytes32"}],"name":"Puzzled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Confirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Revoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}]

View File

@ -19,6 +19,7 @@ import Registry from './registry';
import SignatureReg from './signaturereg'; import SignatureReg from './signaturereg';
import TokenReg from './tokenreg'; import TokenReg from './tokenreg';
import GithubHint from './githubhint'; import GithubHint from './githubhint';
import smsVerification from './sms-verification';
let instance = null; let instance = null;
@ -54,6 +55,10 @@ export default class Contracts {
return this._githubhint; return this._githubhint;
} }
get smsVerification () {
return smsVerification;
}
static create (api) { static create (api) {
return new Contracts(api); return new Contracts(api);
} }

View File

@ -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 <http://www.gnu.org/licenses/>.
import { stringify } from 'querystring';
export const checkIfVerified = (contract, account) => {
return contract.instance.certified.call({}, [account]);
};
export const checkIfRequested = (contract, account) => {
return new Promise((resolve, reject) => {
contract.subscribe('Requested', {
fromBlock: 0, toBlock: 'pending'
}, (err, logs) => {
if (err) {
return reject(err);
}
const e = logs.find((l) => {
return l.type === 'mined' && l.params.who && l.params.who.value === account;
});
resolve(e ? e.transactionHash : false);
});
});
};
export const postToServer = (query) => {
query = stringify(query);
return fetch('https://sms-verification.parity.io/?' + query, {
method: 'POST', mode: 'cors', cache: 'no-store'
})
.then((res) => {
return res.json().then((data) => {
if (res.ok) {
return data.message;
}
throw new Error(data.message || 'unknown error');
});
});
};

View File

@ -14,9 +14,45 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { BlockNumber, Hash, Integer } from '../types'; import { BlockNumber, Data, Hash, Integer } from '../types';
export default { export default {
block: {
desc: 'Returns traces created at given block',
params: [
{
type: BlockNumber,
desc: 'Integer block number, or \'latest\' for the last mined block or \'pending\', \'earliest\' for not yet mined transactions'
}
],
returns: {
type: Array,
desc: 'Block traces'
}
},
call: {
desc: 'Returns traces for a specific call',
params: [
{
type: Object,
desc: 'Call options'
},
{
type: BlockNumber,
desc: 'The blockNumber'
},
{
type: Array,
desc: 'Type of trace, one or more of \'vmTrace\', \'trace\' and/or \'stateDiff\''
}
],
returns: {
type: Array,
desc: 'Block traces'
}
},
filter: { filter: {
desc: 'Returns traces matching given filter', desc: 'Returns traces matching given filter',
params: [ params: [
@ -49,6 +85,42 @@ export default {
} }
}, },
rawTransaction: {
desc: 'Traces a call to eth_sendRawTransaction without making the call, returning the traces',
params: [
{
type: Data,
desc: 'Transaction data'
},
{
type: Array,
desc: 'Type of trace, one or more of \'vmTrace\', \'trace\' and/or \'stateDiff\''
}
],
returns: {
type: Array,
desc: 'Block traces'
}
},
replayTransaction: {
desc: 'Replays a transaction, returning the traces',
params: [
{
type: Hash,
desc: 'Transaction hash'
},
{
type: Array,
desc: 'Type of trace, one or more of \'vmTrace\', \'trace\' and/or \'stateDiff\''
}
],
returns: {
type: Array,
desc: 'Block traces'
}
},
transaction: { transaction: {
desc: 'Returns all traces of given transaction', desc: 'Returns all traces of given transaction',
params: [ params: [
@ -61,19 +133,5 @@ export default {
type: Array, type: Array,
desc: 'Traces of given transaction' desc: 'Traces of given transaction'
} }
},
block: {
desc: 'Returns traces created at given block',
params: [
{
type: BlockNumber,
desc: 'Integer block number, or \'latest\' for the last mined block or \'pending\', \'earliest\' for not yet mined transactions'
}
],
returns: {
type: Array,
desc: 'Block traces'
}
} }
}; };

View File

@ -0,0 +1,31 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.spacing {
margin-top: 1.5em;
}
.container {
margin-top: .5em;
display: flex;
align-items: center;
}
.message {
margin-top: 0;
margin-bottom: 0;
margin-left: .5em;
}

View File

@ -0,0 +1,31 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import SuccessIcon from 'material-ui/svg-icons/navigation/check';
import styles from './done.css';
export default class Done extends Component {
render () {
return (
<div className={ styles.container }>
<SuccessIcon />
<p className={ styles.message }>Congratulations, your account is verified!</p>
</div>
);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
export default from './done';

View File

@ -0,0 +1,49 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.list li {
padding: .1em 0;
}
.spacing {
margin-top: 1.5em;
}
.container {
margin-top: .5em;
display: flex;
align-items: center;
}
.message {
margin-top: 0;
margin-bottom: 0;
margin-left: .5em;
}
.terms {
line-height: 1.3;
opacity: .7;
ul {
padding-left: 1.5em;
}
li {
margin-top: .2em;
margin-bottom: .2em;
}
}

View File

@ -0,0 +1,151 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import nullable from '../../../util/nullable-proptype';
import BigNumber from 'bignumber.js';
import { Checkbox } from 'material-ui';
import InfoIcon from 'material-ui/svg-icons/action/info-outline';
import SuccessIcon from 'material-ui/svg-icons/navigation/check';
import ErrorIcon from 'material-ui/svg-icons/navigation/close';
import { fromWei } from '../../../api/util/wei';
import { Form, Input } from '../../../ui';
import terms from '../terms-of-service';
import styles from './gatherData.css';
export default class GatherData extends Component {
static propTypes = {
fee: React.PropTypes.instanceOf(BigNumber),
isNumberValid: PropTypes.bool.isRequired,
isVerified: nullable(PropTypes.bool.isRequired),
hasRequested: nullable(PropTypes.bool.isRequired),
setNumber: PropTypes.func.isRequired,
setConsentGiven: PropTypes.func.isRequired
}
render () {
const { isNumberValid, isVerified } = this.props;
return (
<Form>
<p>The following steps will let you prove that you control both an account and a phone number.</p>
<ol className={ styles.list }>
<li>You send a verification request to a specific contract.</li>
<li>Our server puts a puzzle into this contract.</li>
<li>The code you receive via SMS is the solution to this puzzle.</li>
</ol>
{ this.renderFee() }
{ this.renderCertified() }
{ this.renderRequested() }
<Input
label={ 'phone number' }
hint={ 'the SMS will be sent to this number' }
error={ isNumberValid ? null : 'invalid number' }
disabled={ isVerified }
onChange={ this.numberOnChange }
onSubmit={ this.numberOnSubmit }
/>
<Checkbox
className={ styles.spacing }
label={ 'I agree to the terms and conditions below.' }
disabled={ isVerified }
onCheck={ this.consentOnChange }
/>
<div className={ styles.terms }>{ terms }</div>
</Form>
);
}
renderFee () {
const { fee } = this.props;
if (!fee) {
return (<p>Fetching the fee</p>);
}
return (
<div className={ styles.container }>
<InfoIcon />
<p className={ styles.message }>The fee is { fromWei(fee).toFixed(3) } ETH.</p>
</div>
);
}
renderCertified () {
const { isVerified } = this.props;
if (isVerified) {
return (
<div className={ styles.container }>
<ErrorIcon />
<p className={ styles.message }>Your account is already verified.</p>
</div>
);
} else if (isVerified === false) {
return (
<div className={ styles.container }>
<SuccessIcon />
<p className={ styles.message }>Your account is not verified yet.</p>
</div>
);
}
return (
<p className={ styles.message }>Checking if your account is verified</p>
);
}
renderRequested () {
const { isVerified, hasRequested } = this.props;
// If the account is verified, don't show that it has requested verification.
if (isVerified) {
return null;
}
if (hasRequested) {
return (
<div className={ styles.container }>
<InfoIcon />
<p className={ styles.message }>You already requested verification.</p>
</div>
);
}
if (hasRequested === false) {
return (
<div className={ styles.container }>
<SuccessIcon />
<p className={ styles.message }>You did not request verification yet.</p>
</div>
);
}
return (
<p className={ styles.message }>Checking if you requested verification</p>
);
}
numberOnSubmit = (value) => {
this.props.setNumber(value);
}
numberOnChange = (_, value) => {
this.props.setNumber(value);
}
consentOnChange = (_, consentGiven) => {
this.props.setConsentGiven(consentGiven);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
export default from './gatherData';

View File

@ -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 <http://www.gnu.org/licenses/>.
export default from './queryCode';

View File

@ -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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { Form, Input } from '../../../ui';
export default class QueryCode extends Component {
static propTypes = {
number: PropTypes.string.isRequired,
isCodeValid: PropTypes.bool.isRequired,
setCode: PropTypes.func.isRequired
}
render () {
const { number, isCodeValid } = this.props;
return (
<Form>
<p>The verification code has been sent to { number }.</p>
<Input
label={ 'verification code' }
hint={ 'Enter the code you received via SMS.' }
error={ isCodeValid ? null : 'invalid code' }
onChange={ this.onChange }
onSubmit={ this.onSubmit }
/>
</Form>
);
}
onChange = (_, code) => {
this.props.setCode(code.trim());
}
onSubmit = (code) => {
this.props.setCode(code.trim());
}
}

View File

@ -0,0 +1,176 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { observer } from 'mobx-react';
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import ContentClear from 'material-ui/svg-icons/content/clear';
import { Button, IdentityIcon, Modal } from '../../ui';
import {
LOADING,
QUERY_DATA,
POSTING_REQUEST, POSTED_REQUEST,
REQUESTING_SMS, REQUESTED_SMS,
POSTING_CONFIRMATION, POSTED_CONFIRMATION,
DONE
} from './store';
import GatherData from './GatherData';
import SendRequest from './SendRequest';
import QueryCode from './QueryCode';
import SendConfirmation from './SendConfirmation';
import Done from './Done';
@observer
export default class SMSVerification extends Component {
static propTypes = {
store: PropTypes.any.isRequired,
account: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired
}
static phases = { // mapping (store steps -> steps)
[LOADING]: 0,
[QUERY_DATA]: 1,
[POSTING_REQUEST]: 2, [POSTED_REQUEST]: 2, [REQUESTING_SMS]: 2,
[REQUESTED_SMS]: 3,
[POSTING_CONFIRMATION]: 4, [POSTED_CONFIRMATION]: 4,
[DONE]: 5
}
render () {
const phase = SMSVerification.phases[this.props.store.step];
const { error, isStepValid } = this.props.store;
return (
<Modal
actions={ this.renderDialogActions(phase, error, isStepValid) }
title='verify your account via SMS'
visible scroll
current={ phase }
steps={ ['Prepare', 'Enter Data', 'Request', 'Enter Code', 'Confirm', 'Done!'] }
waiting={ error ? [] : [ 0, 2, 4 ] }
>
{ this.renderStep(phase, error) }
</Modal>
);
}
renderDialogActions (phase, error, isStepValid) {
const { store, account, onClose } = this.props;
const cancel = (
<Button
key='cancel' label='Cancel'
icon={ <ContentClear /> }
onClick={ onClose }
/>
);
if (error) {
return (<div>{ cancel }</div>);
}
if (phase === 5) {
return (
<div>
{ cancel }
<Button
key='done' label='Done'
disabled={ !isStepValid }
icon={ <ActionDoneAll /> }
onClick={ onClose }
/>
</div>
);
}
let action = () => {};
switch (phase) {
case 1:
action = store.sendRequest;
break;
case 2:
action = store.queryCode;
break;
case 3:
action = store.sendConfirmation;
break;
case 4:
action = store.done;
break;
}
return (
<div>
{ cancel }
<Button
key='next' label='Next'
disabled={ !isStepValid }
icon={ <IdentityIcon address={ account } button /> }
onClick={ action }
/>
</div>
);
}
renderStep (phase, error) {
if (error) {
return (<p>{ error }</p>);
}
const {
step,
fee, number, isNumberValid, isVerified, hasRequested,
requestTx, isCodeValid, confirmationTx,
setNumber, setConsentGiven, setCode
} = this.props.store;
if (phase === 5) {
return (<Done />);
}
if (phase === 4) {
return (<SendConfirmation step={ step } tx={ confirmationTx } />);
}
if (phase === 3) {
return (
<QueryCode
number={ number } fee={ fee } isCodeValid={ isCodeValid }
setCode={ setCode }
/>
);
}
if (phase === 2) {
return (<SendRequest step={ step } tx={ requestTx } />);
}
if (phase === 1) {
const { setNumber, setConsentGiven } = this.props.store;
return (
<GatherData
fee={ fee } isNumberValid={ isNumberValid }
isVerified={ isVerified } hasRequested={ hasRequested }
setNumber={ setNumber } setConsentGiven={ setConsentGiven }
/>
);
}
if (phase === 0) {
return (<p>Preparing awesomeness!</p>);
}
return null;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
export default from './sendConfirmation';

View File

@ -0,0 +1,20 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.centered {
text-align: center;
}

View File

@ -0,0 +1,51 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import nullable from '../../../util/nullable-proptype';
import TxHash from '../../../ui/TxHash';
import {
POSTING_CONFIRMATION, POSTED_CONFIRMATION
} from '../store';
import styles from './sendConfirmation.css';
export default class SendConfirmation extends Component {
static propTypes = {
step: PropTypes.any.isRequired,
tx: nullable(PropTypes.any.isRequired)
}
render () {
const { step, tx } = this.props;
if (step === POSTING_CONFIRMATION) {
return (<p>The verification code will be sent to the contract. Please authorize this using the Parity Signer.</p>);
}
if (step === POSTED_CONFIRMATION) {
return (
<div className={ styles.centered }>
<TxHash hash={ tx } maxConfirmations={ 2 } />
<p>Please keep this window open.</p>
</div>
);
}
return null;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
export default from './sendRequest';

View File

@ -0,0 +1,20 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.centered {
text-align: center;
}

View File

@ -0,0 +1,57 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import nullable from '../../../util/nullable-proptype';
import TxHash from '../../../ui/TxHash';
import {
POSTING_REQUEST, POSTED_REQUEST, REQUESTING_SMS
} from '../store';
import styles from './sendRequest.css';
export default class SendRequest extends Component {
static propTypes = {
step: PropTypes.any.isRequired,
tx: nullable(PropTypes.any.isRequired)
}
render () {
const { step, tx } = this.props;
switch (step) {
case POSTING_REQUEST:
return (<p>A verification request will be sent to the contract. Please authorize this using the Parity Signer.</p>);
case POSTED_REQUEST:
return (
<div className={ styles.centered }>
<TxHash hash={ tx } maxConfirmations={ 1 } />
<p>Please keep this window open.</p>
</div>
);
case REQUESTING_SMS:
return (
<p>Requesting an SMS from the Parity server.</p>
);
default:
return null;
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
export default from './SMSVerification';

View File

@ -0,0 +1,246 @@
// 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 <http://www.gnu.org/licenses/>.
import { observable, computed, autorun, action } from 'mobx';
import phone from 'phoneformat.js';
import { sha3 } from '../../api/util/sha3';
import Contracts from '../../contracts';
import { checkIfVerified, checkIfRequested, postToServer } from '../../contracts/sms-verification';
import checkIfTxFailed from '../../util/check-if-tx-failed';
import waitForConfirmations from '../../util/wait-for-block-confirmations';
const validCode = /^[A-Z\s]+$/i;
export const LOADING = 'fetching-contract';
export const QUERY_DATA = 'query-data';
export const POSTING_REQUEST = 'posting-request';
export const POSTED_REQUEST = 'posted-request';
export const REQUESTING_SMS = 'requesting-sms';
export const REQUESTED_SMS = 'requested-sms';
export const QUERY_CODE = 'query-code';
export const POSTING_CONFIRMATION = 'posting-confirmation';
export const POSTED_CONFIRMATION = 'posted-confirmation';
export const DONE = 'done';
export default class VerificationStore {
@observable step = null;
@observable error = null;
@observable contract = null;
@observable fee = null;
@observable isVerified = null;
@observable hasRequested = null;
@observable consentGiven = false;
@observable number = '';
@observable requestTx = null;
@observable code = '';
@observable confirmationTx = null;
@computed get isCodeValid () {
return validCode.test(this.code);
}
@computed get isNumberValid () {
return phone.isValidNumber(this.number);
}
@computed get isStepValid () {
if (this.step === DONE) {
return true;
}
if (this.error) {
return false;
}
switch (this.step) {
case LOADING:
return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null;
case QUERY_DATA:
return this.isNumberValid && this.consentGiven;
case REQUESTED_SMS:
return this.requestTx;
case QUERY_CODE:
return this.isCodeValid;
case POSTED_CONFIRMATION:
return this.confirmationTx;
default:
return false;
}
}
constructor (api, account) {
this.api = api;
this.account = account;
this.step = LOADING;
Contracts.create(api).registry.getContract('smsVerification')
.then((contract) => {
this.contract = contract;
this.load();
})
.catch((err) => {
this.error = 'Failed to fetch the contract: ' + err.message;
});
autorun(() => {
if (this.error) {
console.error('sms verification: ' + this.error);
}
});
}
@action load = () => {
const { contract, account } = this;
this.step = LOADING;
const fee = contract.instance.fee.call()
.then((fee) => {
this.fee = fee;
})
.catch((err) => {
this.error = 'Failed to fetch the fee: ' + err.message;
});
const isVerified = checkIfVerified(contract, account)
.then((isVerified) => {
this.isVerified = isVerified;
})
.catch((err) => {
this.error = 'Failed to check if verified: ' + err.message;
});
const hasRequested = checkIfRequested(contract, account)
.then((txHash) => {
this.hasRequested = !!txHash;
if (txHash) {
this.requestTx = txHash;
}
})
.catch((err) => {
this.error = 'Failed to check if requested: ' + err.message;
});
Promise
.all([ fee, isVerified, hasRequested ])
.then(() => {
this.step = QUERY_DATA;
});
}
@action setNumber = (number) => {
this.number = number;
}
@action setConsentGiven = (consentGiven) => {
this.consentGiven = consentGiven;
}
@action setCode = (code) => {
this.code = code;
}
@action sendRequest = () => {
const { api, account, contract, fee, number, hasRequested } = this;
const request = contract.functions.find((fn) => fn.name === 'request');
const options = { from: account, value: fee.toString() };
let chain = Promise.resolve();
if (!hasRequested) {
this.step = POSTING_REQUEST;
chain = request.estimateGas(options, [])
.then((gas) => {
options.gas = gas.mul(1.2).toFixed(0);
return request.postTransaction(options, []);
})
.then((handle) => {
// TODO: The "request rejected" error doesn't have any property to
// distinguish it from other errors, so we can't give a meaningful error here.
return api.pollMethod('parity_checkRequest', handle);
})
.then((txHash) => {
this.requestTx = txHash;
return checkIfTxFailed(api, txHash, options.gas)
.then((hasFailed) => {
if (hasFailed) {
throw new Error('Transaction failed, all gas used up.');
}
this.step = POSTED_REQUEST;
return waitForConfirmations(api, txHash, 1);
});
});
}
chain
.then(() => {
this.step = REQUESTING_SMS;
return postToServer({ number, address: account });
})
.then(() => {
this.step = REQUESTED_SMS;
})
.catch((err) => {
this.error = 'Failed to request a confirmation SMS: ' + err.message;
});
}
@action queryCode = () => {
this.step = QUERY_CODE;
}
@action sendConfirmation = () => {
const { api, account, contract, code } = this;
const token = sha3(code);
const confirm = contract.functions.find((fn) => fn.name === 'confirm');
const options = { from: account };
const values = [ token ];
this.step = POSTING_CONFIRMATION;
confirm.estimateGas(options, values)
.then((gas) => {
options.gas = gas.mul(1.2).toFixed(0);
return confirm.postTransaction(options, values);
})
.then((handle) => {
// TODO: The "request rejected" error doesn't have any property to
// distinguish it from other errors, so we can't give a meaningful error here.
return api.pollMethod('parity_checkRequest', handle);
})
.then((txHash) => {
this.confirmationTx = txHash;
return checkIfTxFailed(api, txHash, options.gas)
.then((hasFailed) => {
if (hasFailed) {
throw new Error('Transaction failed, all gas used up.');
}
this.step = POSTED_CONFIRMATION;
return waitForConfirmations(api, txHash, 1);
});
})
.then(() => {
this.step = DONE;
})
.catch((err) => {
this.error = 'Failed to send the verification code: ' + err.message;
});
}
@action done = () => {
this.step = DONE;
}
}

View File

@ -0,0 +1,27 @@
// 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 <http://www.gnu.org/licenses/>.
import React from 'react';
export default (
<ul>
<li>This privacy notice relates to your use of the Parity SMS verification service. We take your privacy seriously and deal in an honest, direct and transparent way when it comes to your data.</li>
<li>We collect your phone number when you use this service. This is temporarily kept in memory, and then encrypted and stored in our EU servers. We only retain the cryptographic hash of the number to prevent duplicated accounts. You consent to this use.</li>
<li>You pay a fee for the cost of this service using the account you want to verify.</li>
<li>Your phone number is transmitted to a third party US SMS verification service Twilio for the sole purpose of the SMS verification. You consent to this use. Twilios privacy policy is here: <a href={ 'https://www.twilio.com/legal/privacy/developer' }>https://www.twilio.com/legal/privacy/developer</a>.</li>
<li><i>Parity Technology Limited</i> is registered in England and Wales under company number <code>09760015</code> and complies with the Data Protection Act 1998 (UK). You may contact us via email at <a href={ 'mailto:admin@parity.io' }>admin@parity.io</a>. Our general privacy policy can be found here: <a href={ 'https://ethcore.io/legal.html' }>https://ethcore.io/legal.html</a>.</li>
</ul>
);

View File

@ -22,6 +22,7 @@ import EditMeta from './EditMeta';
import ExecuteContract from './ExecuteContract'; import ExecuteContract from './ExecuteContract';
import FirstRun from './FirstRun'; import FirstRun from './FirstRun';
import Shapeshift from './Shapeshift'; import Shapeshift from './Shapeshift';
import SMSVerification from './SMSVerification';
import Transfer from './Transfer'; import Transfer from './Transfer';
import PasswordManager from './PasswordManager'; import PasswordManager from './PasswordManager';
import SaveContract from './SaveContract'; import SaveContract from './SaveContract';
@ -36,6 +37,7 @@ export {
ExecuteContract, ExecuteContract,
FirstRun, FirstRun,
Shapeshift, Shapeshift,
SMSVerification,
Transfer, Transfer,
PasswordManager, PasswordManager,
LoadContract, LoadContract,

View File

@ -31,9 +31,14 @@ class TxHash extends Component {
static propTypes = { static propTypes = {
hash: PropTypes.string.isRequired, hash: PropTypes.string.isRequired,
isTest: PropTypes.bool, isTest: PropTypes.bool,
summary: PropTypes.bool summary: PropTypes.bool,
maxConfirmations: PropTypes.number
} }
static defaultProps = {
maxConfirmations: 10
};
state = { state = {
blockNumber: new BigNumber(0), blockNumber: new BigNumber(0),
transaction: null, transaction: null,
@ -79,6 +84,7 @@ class TxHash extends Component {
} }
renderConfirmations () { renderConfirmations () {
const { maxConfirmations } = this.props;
const { blockNumber, transaction } = this.state; const { blockNumber, transaction } = this.state;
let txBlock = 'Pending'; let txBlock = 'Pending';
@ -89,14 +95,16 @@ class TxHash extends Component {
const num = blockNumber.minus(transaction.blockNumber).plus(1); const num = blockNumber.minus(transaction.blockNumber).plus(1);
txBlock = `#${transaction.blockNumber.toFormat(0)}`; txBlock = `#${transaction.blockNumber.toFormat(0)}`;
confirmations = num.toFormat(0); confirmations = num.toFormat(0);
value = num.gt(10) ? 10 : num.toNumber(); value = num.gt(maxConfirmations) ? maxConfirmations : num.toNumber();
} }
return ( return (
<div className={ styles.confirm }> <div className={ styles.confirm }>
<LinearProgress <LinearProgress
className={ styles.progressbar } className={ styles.progressbar }
min={ 0 } max={ 10 } value={ value } min={ 0 }
max={ maxConfirmations }
value={ value }
color='white' color='white'
mode='determinate' /> mode='determinate' />
<div className={ styles.progressinfo }> <div className={ styles.progressinfo }>

View File

@ -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 <http://www.gnu.org/licenses/>.
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;

View File

@ -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 <http://www.gnu.org/licenses/>.
import { PropTypes } from 'react';
export default function (type) {
return PropTypes.oneOfType([ PropTypes.oneOf([ null ]), type ]);
}

View File

@ -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 <http://www.gnu.org/licenses/>.
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;

View File

@ -20,8 +20,9 @@ import { bindActionCreators } from 'redux';
import ContentCreate from 'material-ui/svg-icons/content/create'; import ContentCreate from 'material-ui/svg-icons/content/create';
import ContentSend from 'material-ui/svg-icons/content/send'; import ContentSend from 'material-ui/svg-icons/content/send';
import LockIcon from 'material-ui/svg-icons/action/lock'; 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 { Actionbar, Button, Page } from '../../ui';
import shapeshiftBtn from '../../../assets/images/shapeshift-btn.png'; 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 Header from './Header';
import Transactions from './Transactions'; import Transactions from './Transactions';
import VerificationStore from '../../modals/SMSVerification/store';
import styles from './account.css'; import styles from './account.css';
class Account extends Component { class Account extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
}
static propTypes = { static propTypes = {
params: PropTypes.object, params: PropTypes.object,
accounts: PropTypes.object, accounts: PropTypes.object,
@ -45,10 +52,20 @@ class Account extends Component {
state = { state = {
showEditDialog: false, showEditDialog: false,
showFundDialog: false, showFundDialog: false,
showVerificationDialog: false,
verificationStore: null,
showTransferDialog: false, showTransferDialog: false,
showPasswordDialog: false showPasswordDialog: false
} }
componentDidMount () {
const { api } = this.context;
const { address } = this.props.params;
const store = new VerificationStore(api, address);
this.setState({ verificationStore: store });
}
render () { render () {
const { accounts, balances, isTest } = this.props; const { accounts, balances, isTest } = this.props;
const { address } = this.props.params; const { address } = this.props.params;
@ -64,6 +81,7 @@ class Account extends Component {
<div className={ styles.account }> <div className={ styles.account }>
{ this.renderEditDialog(account) } { this.renderEditDialog(account) }
{ this.renderFundDialog() } { this.renderFundDialog() }
{ this.renderVerificationDialog() }
{ this.renderTransferDialog() } { this.renderTransferDialog() }
{ this.renderPasswordDialog() } { this.renderPasswordDialog() }
{ this.renderActionbar() } { this.renderActionbar() }
@ -99,6 +117,11 @@ class Account extends Component {
icon={ <img src={ shapeshiftBtn } className={ styles.btnicon } /> } icon={ <img src={ shapeshiftBtn } className={ styles.btnicon } /> }
label='shapeshift' label='shapeshift'
onClick={ this.onShapeshiftAccountClick } />, onClick={ this.onShapeshiftAccountClick } />,
<Button
key='sms-verification'
icon={ <VerifyIcon /> }
label='Verify'
onClick={ this.openVerification } />,
<Button <Button
key='editmeta' key='editmeta'
icon={ <ContentCreate /> } icon={ <ContentCreate /> }
@ -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 (
<SMSVerification
store={ store } account={ address }
onClose={ this.onVerificationClose }
/>
);
}
renderTransferDialog () { renderTransferDialog () {
const { showTransferDialog } = this.state; const { showTransferDialog } = this.state;
@ -205,6 +244,14 @@ class Account extends Component {
this.onShapeshiftAccountClick(); this.onShapeshiftAccountClick();
} }
openVerification = () => {
this.setState({ showVerificationDialog: true });
}
onVerificationClose = () => {
this.setState({ showVerificationDialog: false });
}
onTransferClick = () => { onTransferClick = () => {
this.setState({ this.setState({
showTransferDialog: !this.state.showTransferDialog showTransferDialog: !this.state.showTransferDialog

View File

@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import nullable from '../../../../util/nullable-proptype';
import Account from '../Account'; import Account from '../Account';
import TransactionPendingForm from '../TransactionPendingForm'; import TransactionPendingForm from '../TransactionPendingForm';
@ -22,8 +23,6 @@ import TxHashLink from '../TxHashLink';
import styles from './SignRequest.css'; import styles from './SignRequest.css';
const nullable = (type) => React.PropTypes.oneOfType([ React.PropTypes.oneOf([ null ]), type ]);
export default class SignRequest extends Component { export default class SignRequest extends Component {
static contextTypes = { static contextTypes = {
api: PropTypes.object api: PropTypes.object

View File

@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import nullable from '../../../../util/nullable-proptype';
import CircularProgress from 'material-ui/CircularProgress'; import CircularProgress from 'material-ui/CircularProgress';
@ -29,8 +30,6 @@ import styles from './TransactionFinished.css';
import * as tUtil from '../util/transaction'; import * as tUtil from '../util/transaction';
import { capitalize } from '../util/util'; import { capitalize } from '../util/util';
const nullable = (type) => React.PropTypes.oneOfType([ React.PropTypes.oneOf([ null ]), type ]);
export default class TransactionFinished extends Component { export default class TransactionFinished extends Component {
static contextTypes = { static contextTypes = {
api: PropTypes.object.isRequired api: PropTypes.object.isRequired

View File

@ -14,7 +14,7 @@ To temporarily disable Parity Wallet (and stop Parity) use:
To completely uninstall Parity Wallet 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. Parity is distributed under the terms of the GPL.

View File

@ -203,11 +203,8 @@ pub fn execute(cmd: RunCmd, logger: Arc<RotatingLogger>) -> Result<(), String> {
sync_config.fork_block = spec.fork_block(); sync_config.fork_block = spec.fork_block();
sync_config.warp_sync = cmd.warp_sync; 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 // 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_author(cmd.miner_extras.author);
miner.set_gas_floor_target(cmd.miner_extras.gas_floor_target); miner.set_gas_floor_target(cmd.miner_extras.gas_floor_target);
miner.set_gas_ceil_target(cmd.miner_extras.gas_ceil_target); miner.set_gas_ceil_target(cmd.miner_extras.gas_ceil_target);
@ -241,6 +238,9 @@ pub fn execute(cmd: RunCmd, logger: Arc<RotatingLogger>) -> Result<(), String> {
// create supervisor // create supervisor
let mut hypervisor = modules::hypervisor(&cmd.dirs.ipc_path()); 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. // create client service.
let service = try!(ClientService::start( let service = try!(ClientService::start(
client_config, client_config,

View File

@ -50,7 +50,7 @@ fn sync_provider() -> Arc<TestSyncProvider> {
})) }))
} }
fn miner_service(spec: &Spec, accounts: Arc<AccountProvider>) -> Arc<Miner> { fn miner_service(spec: &Spec) -> Arc<Miner> {
Miner::new( Miner::new(
MinerOptions { MinerOptions {
new_work_notify: vec![], new_work_notify: vec![],
@ -69,7 +69,6 @@ fn miner_service(spec: &Spec, accounts: Arc<AccountProvider>) -> Arc<Miner> {
}, },
GasPricer::new_fixed(20_000_000_000u64.into()), GasPricer::new_fixed(20_000_000_000u64.into()),
&spec, &spec,
Some(accounts),
) )
} }
@ -116,7 +115,8 @@ impl EthTester {
fn from_spec(spec: Spec) -> Self { fn from_spec(spec: Spec) -> Self {
let dir = RandomTempPath::new(); let dir = RandomTempPath::new();
let account_provider = account_provider(); 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 snapshot_service = snapshot_service();
let db_config = ::util::kvdb::DatabaseConfig::with_columns(::ethcore::db::NUM_COLUMNS); let db_config = ::util::kvdb::DatabaseConfig::with_columns(::ethcore::db::NUM_COLUMNS);

View File

@ -33,6 +33,7 @@ const MAX_BODIES_TO_REQUEST: usize = 64;
const MAX_RECEPITS_TO_REQUEST: usize = 128; const MAX_RECEPITS_TO_REQUEST: usize = 128;
const SUBCHAIN_SIZE: u64 = 256; const SUBCHAIN_SIZE: u64 = 256;
const MAX_ROUND_PARENTS: usize = 32; const MAX_ROUND_PARENTS: usize = 32;
const MAX_PARALLEL_SUBCHAIN_DOWNLOAD: usize = 5;
#[derive(Copy, Clone, Eq, PartialEq, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Debug)]
/// Downloader state /// 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)] #[derive(Eq, PartialEq, Debug)]
pub enum BlockDownloaderImportError { pub enum BlockDownloaderImportError {
/// Imported data is rejected as invalid. /// Imported data is rejected as invalid.
@ -175,11 +184,11 @@ impl BlockDownloader {
} }
/// Add new block headers. /// Add new block headers.
pub fn import_headers(&mut self, io: &mut SyncIo, r: &UntrustedRlp, expected_hash: Option<H256>) -> Result<(), BlockDownloaderImportError> { pub fn import_headers(&mut self, io: &mut SyncIo, r: &UntrustedRlp, expected_hash: Option<H256>) -> Result<DownloadAction, BlockDownloaderImportError> {
let item_count = r.item_count(); let item_count = r.item_count();
if self.state == State::Idle { if self.state == State::Idle {
trace!(target: "sync", "Ignored unexpected block headers"); trace!(target: "sync", "Ignored unexpected block headers");
return Ok(()) return Ok(DownloadAction::None)
} }
if item_count == 0 && (self.state == State::Blocks) { if item_count == 0 && (self.state == State::Blocks) {
return Err(BlockDownloaderImportError::Invalid); return Err(BlockDownloaderImportError::Invalid);
@ -188,6 +197,7 @@ impl BlockDownloader {
let mut headers = Vec::new(); let mut headers = Vec::new();
let mut hashes = Vec::new(); let mut hashes = Vec::new();
let mut valid_response = item_count == 0; //empty response is valid let mut valid_response = item_count == 0; //empty response is valid
let mut any_known = false;
for i in 0..item_count { for i in 0..item_count {
let info: BlockHeader = try!(r.val_at(i).map_err(|e| { let info: BlockHeader = try!(r.val_at(i).map_err(|e| {
trace!(target: "sync", "Error decoding block header RLP: {:?}", e); trace!(target: "sync", "Error decoding block header RLP: {:?}", e);
@ -200,6 +210,7 @@ impl BlockDownloader {
valid_response = expected == info.hash() valid_response = expected == info.hash()
} }
} }
any_known = any_known || self.blocks.contains_head(&info.hash());
if self.blocks.contains(&info.hash()) { if self.blocks.contains(&info.hash()) {
trace!(target: "sync", "Skipping existing block header {} ({:?})", number, info.hash()); trace!(target: "sync", "Skipping existing block header {} ({:?})", number, info.hash());
continue; continue;
@ -245,17 +256,22 @@ impl BlockDownloader {
trace!(target: "sync", "Received {} subchain heads, proceeding to download", headers.len()); trace!(target: "sync", "Received {} subchain heads, proceeding to download", headers.len());
self.blocks.reset_to(hashes); self.blocks.reset_to(hashes);
self.state = State::Blocks; self.state = State::Blocks;
return Ok(DownloadAction::Reset);
} }
}, },
State::Blocks => { State::Blocks => {
let count = headers.len(); 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); self.blocks.insert_headers(headers);
trace!(target: "sync", "Inserted {} headers", count); trace!(target: "sync", "Inserted {} headers", count);
}, },
_ => trace!(target: "sync", "Unexpected headers({})", headers.len()), _ => trace!(target: "sync", "Unexpected headers({})", headers.len()),
} }
Ok(()) Ok(DownloadAction::None)
} }
/// Called by peer once it has new block bodies /// Called by peer once it has new block bodies
@ -342,13 +358,14 @@ impl BlockDownloader {
} }
/// Find some headers or blocks to download for a peer. /// Find some headers or blocks to download for a peer.
pub fn request_blocks(&mut self, io: &mut SyncIo) -> Option<BlockRequest> { pub fn request_blocks(&mut self, io: &mut SyncIo, num_active_peers: usize) -> Option<BlockRequest> {
match self.state { match self.state {
State::Idle => { State::Idle => {
self.start_sync_round(io); self.start_sync_round(io);
return self.request_blocks(io); return self.request_blocks(io, num_active_peers);
}, },
State::ChainHead => { State::ChainHead => {
if num_active_peers < MAX_PARALLEL_SUBCHAIN_DOWNLOAD {
// Request subchain headers // Request subchain headers
trace!(target: "sync", "Starting sync with better chain"); trace!(target: "sync", "Starting sync with better chain");
// Request MAX_HEADERS_TO_REQUEST - 2 headers apart so that // Request MAX_HEADERS_TO_REQUEST - 2 headers apart so that
@ -358,6 +375,7 @@ impl BlockDownloader {
count: SUBCHAIN_SIZE, count: SUBCHAIN_SIZE,
skip: (MAX_HEADERS_TO_REQUEST - 2) as u64, skip: (MAX_HEADERS_TO_REQUEST - 2) as u64,
}); });
}
}, },
State::Blocks => { State::Blocks => {
// check to see if we need to download any block bodies first // check to see if we need to download any block bodies first

View File

@ -301,11 +301,16 @@ impl BlockCollection {
self.heads.len() == 0 || (self.heads.len() == 1 && self.head.map_or(false, |h| h == self.heads[0])) 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 { pub fn contains(&self, hash: &H256) -> bool {
self.blocks.contains_key(hash) 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. /// Return used heap size.
pub fn heap_size(&self) -> usize { pub fn heap_size(&self) -> usize {
self.heads.heap_size_of_children() self.heads.heap_size_of_children()

View File

@ -37,7 +37,7 @@
/// Workflow for `ChainHead` state. /// Workflow for `ChainHead` state.
/// In this state we try to get subchain headers with a single `GetBlockHeaders` request. /// In this state we try to get subchain headers with a single `GetBlockHeaders` request.
/// On `NewPeer` / On `Restart`: /// 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)`: /// On `BlockHeaders(R)`:
/// If R is empty: /// If R is empty:
/// If l is equal to genesis block hash or l is more than 1000 blocks behind our best hash: /// If l is equal to genesis block hash or l is more than 1000 blocks behind our best hash:
@ -49,8 +49,8 @@
/// Else /// Else
/// Set S to R, set s to `Blocks`. /// Set S to R, set s to `Blocks`.
/// ///
///
/// All other messages are ignored. /// All other messages are ignored.
///
/// Workflow for `Blocks` state. /// Workflow for `Blocks` state.
/// In this state we download block headers and bodies from multiple peers. /// In this state we download block headers and bodies from multiple peers.
/// On `NewPeer` / On `Restart`: /// On `NewPeer` / On `Restart`:
@ -62,7 +62,9 @@
/// ///
/// On `BlockHeaders(R)`: /// On `BlockHeaders(R)`:
/// If R is empty remove current peer from P and restart. /// 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`. /// Go to `CollectBlocks`.
/// ///
/// On `BlockBodies(R)`: /// On `BlockBodies(R)`:
@ -98,7 +100,7 @@ use ethcore::snapshot::{ManifestData, RestorationStatus};
use sync_io::SyncIo; use sync_io::SyncIo;
use time; use time;
use super::SyncConfig; 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 snapshot::{Snapshot, ChunkType};
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use api::{PeerInfo as PeerInfoDigest, WARP_SYNC_PROTOCOL_ID}; use api::{PeerInfo as PeerInfoDigest, WARP_SYNC_PROTOCOL_ID};
@ -306,6 +308,15 @@ impl PeerInfo {
fn is_allowed(&self) -> bool { fn is_allowed(&self) -> bool {
self.confirmation != ForkConfirmation::Unconfirmed && !self.expired 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. /// Blockchain sync handler.
@ -425,12 +436,7 @@ impl ChainSync {
} }
for (_, ref mut p) in &mut self.peers { for (_, ref mut p) in &mut self.peers {
if p.block_set != Some(BlockSet::OldBlocks) { if p.block_set != Some(BlockSet::OldBlocks) {
p.asking_blocks.clear(); p.reset_asking();
p.asking_hash = None;
// mark any pending requests as expired
if p.asking != PeerAsking::Nothing && p.is_allowed() {
p.expired = true;
}
} }
} }
self.state = SyncState::Idle; self.state = SyncState::Idle;
@ -643,8 +649,9 @@ impl ChainSync {
self.clear_peer_download(peer_id); self.clear_peer_download(peer_id);
let expected_hash = self.peers.get(&peer_id).and_then(|p| p.asking_hash); 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); 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); trace!(target: "sync", "{}: Ignored unexpected headers, expected_hash = {:?}", peer_id, expected_hash);
self.continue_sync(io); self.continue_sync(io);
return Ok(()); return Ok(());
@ -689,7 +696,15 @@ impl ChainSync {
self.continue_sync(io); self.continue_sync(io);
return Ok(()); 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); self.collect_blocks(io, block_set);
@ -981,7 +996,7 @@ impl ChainSync {
return Ok(()); return Ok(());
} }
self.clear_peer_download(peer_id); 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); trace!(target: "sync", "{}: Ignored unexpected snapshot data", peer_id);
self.continue_sync(io); self.continue_sync(io);
return Ok(()); return Ok(());
@ -1113,6 +1128,7 @@ impl ChainSync {
}; };
let chain_info = io.chain().chain_info(); let chain_info = io.chain().chain_info();
let syncing_difficulty = chain_info.pending_total_difficulty; 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); 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() { 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; let have_latest = io.chain().block_status(BlockID::Hash(peer_latest)) != BlockStatus::Unknown;
if !have_latest && (higher_difficulty || force || self.state == SyncState::NewBlocks) { if !have_latest && (higher_difficulty || force || self.state == SyncState::NewBlocks) {
// check if got new blocks to download // 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); self.request_blocks(io, peer_id, request, BlockSet::NewBlocks);
if self.state == SyncState::Idle { if self.state == SyncState::Idle {
self.state = SyncState::Blocks; 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); self.request_blocks(io, peer_id, request, BlockSet::OldBlocks);
return; return;
} }