diff --git a/Cargo.lock b/Cargo.lock index ad1cc15a1..46ddc5f07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#957c5a66c33f3b06a7ae804ac5edc59c20e4535b" +source = "git+https://github.com/ethcore/js-precompiled.git#b2513e92603b473799d653583bd86771e0063c08" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 9bed99e8b..ab2b41aec 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -68,6 +68,7 @@ pub struct AuthorityRound { builtins: BTreeMap, transition_service: IoService, message_channel: Mutex>>, + account_provider: Mutex>>, step: AtomicUsize, proposed: AtomicBool, } @@ -101,6 +102,7 @@ impl AuthorityRound { builtins: builtins, transition_service: try!(IoService::::start()), message_channel: Mutex::new(None), + account_provider: Mutex::new(None), step: AtomicUsize::new(initial_step), 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 /// be returned. - fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { + fn generate_seal(&self, block: &ExecutedBlock) -> Option> { if self.proposed.load(AtomicOrdering::SeqCst) { return None; } let header = block.header(); let step = self.step(); 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. if let Ok(signature) = ap.sign(*header.author(), None, header.bare_hash()) { 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) { - let mut guard = self.message_channel.lock(); - *guard = Some(message_channel); + *self.message_channel.lock() = Some(message_channel); + } + + fn register_account_provider(&self, account_provider: Arc) { + *self.account_provider.lock() = Some(account_provider); } } @@ -382,6 +387,7 @@ mod tests { let spec = Spec::new_test_round(); let engine = &*spec.engine; + engine.register_account_provider(Arc::new(tap)); let genesis_header = spec.genesis_header(); let mut db1 = get_temp_state_db().take(); 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 = 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()); // 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()); // Second proposal is forbidden. - assert!(engine.generate_seal(b2.block(), Some(&tap)).is_none()); + assert!(engine.generate_seal(b2.block()).is_none()); } } diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 23a97967c..c43495967 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -58,6 +58,7 @@ pub struct BasicAuthority { params: CommonParams, our_params: BasicAuthorityParams, builtins: BTreeMap, + account_provider: Mutex>> } impl BasicAuthority { @@ -67,6 +68,7 @@ impl BasicAuthority { params: params, our_params: our_params, 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 /// be returned. - fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { - if let Some(ap) = accounts { + fn generate_seal(&self, block: &ExecutedBlock) -> Option> { + if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let message = header.bare_hash(); // 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> { t.sender().map(|_|()) // Perform EC recovery and cache sender } + + fn register_account_provider(&self, ap: Arc) { + *self.account_provider.lock() = Some(ap); + } } #[cfg(test)] @@ -253,6 +259,7 @@ mod tests { let spec = new_test_authority(); let engine = &*spec.engine; + engine.register_account_provider(Arc::new(tap)); let genesis_header = spec.genesis_header(); let mut db_result = get_temp_state_db(); let mut db = db_result.take(); @@ -260,7 +267,7 @@ mod tests { 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 = 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()); } diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index 3dc78d1a2..7491d47f3 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -23,7 +23,6 @@ use spec::CommonParams; use evm::Schedule; use block::ExecutedBlock; use util::Bytes; -use account_provider::AccountProvider; /// An engine which does not provide any consensus mechanism, just seals blocks internally. pub struct InstantSeal { @@ -60,7 +59,7 @@ impl Engine for InstantSeal { fn is_sealer(&self, _author: &Address) -> Option { Some(true) } - fn generate_seal(&self, _block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option> { + fn generate_seal(&self, _block: &ExecutedBlock) -> Option> { Some(Vec::new()) } } @@ -69,16 +68,12 @@ impl Engine for InstantSeal { mod tests { use util::*; use tests::helpers::*; - use account_provider::AccountProvider; use spec::Spec; use header::Header; use block::*; #[test] fn instant_can_seal() { - let tap = AccountProvider::transient_provider(); - let addr = tap.insert_account("".sha3(), "").unwrap(); - let spec = Spec::new_instant(); let engine = &*spec.engine; let genesis_header = spec.genesis_header(); @@ -86,10 +81,9 @@ mod tests { let mut db = db_result.take(); spec.ensure_db_good(&mut db).unwrap(); 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(); - // Seal with empty AccountProvider. - 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()); } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 2f43792e5..5aae9ee78 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -114,7 +114,7 @@ pub trait Engine : Sync + Send { /// /// This operation is synchronous and may (quite reasonably) not be available, in which None will /// be returned. - fn generate_seal(&self, _block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option> { None } + fn generate_seal(&self, _block: &ExecutedBlock) -> Option> { None } /// 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. @@ -179,5 +179,6 @@ pub trait Engine : Sync + Send { 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) {} } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index f93203b00..9f99acf1d 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -19,7 +19,6 @@ use std::time::{Instant, Duration}; use util::*; use util::using_queue::{UsingQueue, GetAction}; -use account_provider::AccountProvider; use views::{BlockView, HeaderView}; use state::{State, CleanupMode}; use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics}; @@ -220,14 +219,13 @@ pub struct Miner { extra_data: RwLock, engine: Arc, - accounts: Option>, work_poster: Option, gas_pricer: Mutex, } impl Miner { /// Creates new instance of miner. - fn new_raw(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Miner { + fn new_raw(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec) -> Miner { let work_poster = match options.new_work_notify.is_empty() { true => None, false => Some(WorkPoster::new(&options.new_work_notify)) @@ -261,26 +259,20 @@ impl Miner { author: RwLock::new(Address::default()), extra_data: RwLock::new(Vec::new()), options: options, - accounts: accounts, engine: spec.engine.clone(), work_poster: work_poster, gas_pricer: Mutex::new(gas_pricer), } } - /// Creates new instance of miner with accounts and with given spec. - pub fn with_spec_and_accounts(spec: &Spec, accounts: Option>) -> 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. + /// Creates new instance of miner with given spec. 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. - pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Arc { - Arc::new(Miner::new_raw(options, gas_pricer, spec, accounts)) + pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec) -> Arc { + Arc::new(Miner::new_raw(options, gas_pricer, spec)) } fn forced_sealing(&self) -> bool { @@ -461,10 +453,7 @@ impl Miner { /// Err(Some(block)) returns for unsuccesful sealing while Err(None) indicates misspecified engine. fn seal_block_internally(&self, block: ClosedBlock) -> Result> { trace!(target: "miner", "seal_block_internally: block has transaction - attempting internal seal."); - let s = self.engine.generate_seal(block.block(), match self.accounts { - Some(ref x) => Some(&**x), - None => None, - }); + let s = self.engine.generate_seal(block.block()); if let Some(seal) = s { trace!(target: "miner", "seal_block_internally: managed internal seal. importing..."); block.lock().try_seal(&*self.engine, seal).or_else(|_| { @@ -1170,7 +1159,6 @@ mod tests { }, GasPricer::new_fixed(0u64.into()), &Spec::new_test(), - None, // accounts provider )).ok().expect("Miner was just created.") } diff --git a/ethcrypto/src/lib.rs b/ethcrypto/src/lib.rs index 103e750e6..c98d14027 100644 --- a/ethcrypto/src/lib.rs +++ b/ethcrypto/src/lib.rs @@ -34,16 +34,43 @@ pub const KEY_LENGTH: usize = 32; pub const KEY_ITERATIONS: usize = 10240; 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)] pub enum Error { Secp(SecpError), + Scrypt(ScryptError), InvalidMessage, } +impl From for Error { + fn from(err: ScryptError) -> Self { + Error::Scrypt(err) + } +} + impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { let s = match *self { Error::Secp(ref err) => err.to_string(), + Error::Scrypt(ref err) => err.to_string(), Error::InvalidMessage => "Invalid message".into(), }; @@ -80,13 +107,23 @@ pub fn derive_key_iterations(password: &str, salt: &[u8; 32], c: u32) -> (Vec (Vec, Vec) { +pub fn derive_key_scrypt(password: &str, salt: &[u8; 32], n: u32, p: u32, r: u32) -> Result<(Vec, Vec), 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 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); let derived_right_bits = &derived_key[0..KEY_LENGTH_AES]; 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 { diff --git a/ethstore/src/account/safe_account.rs b/ethstore/src/account/safe_account.rs index 5dab35251..336e72875 100644 --- a/ethstore/src/account/safe_account.rs +++ b/ethstore/src/account/safe_account.rs @@ -113,7 +113,7 @@ impl Crypto { let (derived_left_bits, derived_right_bits) = match self.kdf { Kdf::Pbkdf2(ref params) => crypto::derive_key_iterations(password, ¶ms.salt, params.c), - Kdf::Scrypt(ref params) => crypto::derive_key_scrypt(password, ¶ms.salt, params.n, params.p, params.r), + Kdf::Scrypt(ref params) => try!(crypto::derive_key_scrypt(password, ¶ms.salt, params.n, params.p, params.r)), }; let mac = crypto::derive_mac(&derived_right_bits, &self.ciphertext).keccak256(); diff --git a/js/package.json b/js/package.json index 2a2b6d590..af56140b1 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.50", + "version": "0.2.52", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", @@ -139,6 +139,7 @@ "mobx-react": "^3.5.8", "mobx-react-devtools": "^4.2.9", "moment": "^2.14.1", + "phoneformat.js": "^1.0.3", "qs": "^6.3.0", "react": "^15.2.1", "react-ace": "^4.0.0", diff --git a/js/src/api/format/input.js b/js/src/api/format/input.js index 830ca0e21..4cd1c8a56 100644 --- a/js/src/api/format/input.js +++ b/js/src/api/format/input.js @@ -166,3 +166,11 @@ export function inTraceFilter (filterObject) { return filterObject; } + +export function inTraceType (whatTrace) { + if (isString(whatTrace)) { + return [whatTrace]; + } + + return whatTrace; +} diff --git a/js/src/api/format/input.spec.js b/js/src/api/format/input.spec.js index 219886d05..a22c8d131 100644 --- a/js/src/api/format/input.spec.js +++ b/js/src/api/format/input.spec.js @@ -16,7 +16,7 @@ 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'; 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]); + }); + }); }); diff --git a/js/src/api/format/output.js b/js/src/api/format/output.js index 8461df20f..262a275a0 100644 --- a/js/src/api/format/output.js +++ b/js/src/api/format/output.js @@ -254,3 +254,25 @@ export function outTrace (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; +} diff --git a/js/src/api/rpc/trace/trace.e2e.js b/js/src/api/rpc/trace/trace.e2e.js index 1a0720927..88c0988f6 100644 --- a/js/src/api/rpc/trace/trace.e2e.js +++ b/js/src/api/rpc/trace/trace.e2e.js @@ -20,15 +20,25 @@ describe('ethapi.trace', () => { const ethapi = createHttpApi(); describe('block', () => { - it('returns the latest block', () => { - return ethapi.trace.block().then((block) => { - expect(block).to.be.ok; + it('returns the latest block traces', () => { + return ethapi.trace.block().then((traces) => { + expect(traces).to.be.ok; }); }); - it('returns a specified block', () => { - return ethapi.trace.block('0x65432').then((block) => { - expect(block).to.be.ok; + it('returns traces for a specified block', () => { + return ethapi.trace.block('0x65432').then((traces) => { + 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; + }); }); }); }); diff --git a/js/src/api/rpc/trace/trace.js b/js/src/api/rpc/trace/trace.js index 95fed4230..5c693c0b5 100644 --- a/js/src/api/rpc/trace/trace.js +++ b/js/src/api/rpc/trace/trace.js @@ -14,35 +14,53 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { inBlockNumber, inHex, inNumber16, inTraceFilter } from '../../format/input'; -import { outTrace } from '../../format/output'; +import { inBlockNumber, inData, inHex, inNumber16, inOptions, inTraceFilter, inTraceType } from '../../format/input'; +import { outTraces, outTraceReplay } from '../../format/output'; export default class Trace { constructor (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) { return this._transport .execute('trace_filter', inTraceFilter(filterObj)) - .then(traces => traces.map(trace => outTrace(trace))); + .then(outTraces); } get (txHash, position) { return this._transport .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) { return this._transport .execute('trace_transaction', inHex(txHash)) - .then(traces => traces.map(trace => outTrace(trace))); - } - - block (blockNumber = 'latest') { - return this._transport - .execute('trace_block', inBlockNumber(blockNumber)) - .then(traces => traces.map(trace => outTrace(trace))); + .then(outTraces); } } diff --git a/js/src/contracts/abi/index.js b/js/src/contracts/abi/index.js index 80f49dc5b..a6a7f0783 100644 --- a/js/src/contracts/abi/index.js +++ b/js/src/contracts/abi/index.js @@ -23,6 +23,7 @@ import githubhint from './githubhint.json'; import owned from './owned.json'; import registry from './registry.json'; import signaturereg from './signaturereg.json'; +import smsverification from './sms-verification.json'; import tokenreg from './tokenreg.json'; import wallet from './wallet.json'; @@ -36,6 +37,7 @@ export { owned, registry, signaturereg, + smsverification, tokenreg, wallet }; diff --git a/js/src/contracts/abi/sms-verification.json b/js/src/contracts/abi/sms-verification.json new file mode 100644 index 000000000..400d22b44 --- /dev/null +++ b/js/src/contracts/abi/sms-verification.json @@ -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"}] diff --git a/js/src/contracts/contracts.js b/js/src/contracts/contracts.js index a04321c7b..9d745762c 100644 --- a/js/src/contracts/contracts.js +++ b/js/src/contracts/contracts.js @@ -19,6 +19,7 @@ import Registry from './registry'; import SignatureReg from './signaturereg'; import TokenReg from './tokenreg'; import GithubHint from './githubhint'; +import smsVerification from './sms-verification'; let instance = null; @@ -54,6 +55,10 @@ export default class Contracts { return this._githubhint; } + get smsVerification () { + return smsVerification; + } + static create (api) { return new Contracts(api); } diff --git a/js/src/contracts/sms-verification.js b/js/src/contracts/sms-verification.js new file mode 100644 index 000000000..e93d57ffc --- /dev/null +++ b/js/src/contracts/sms-verification.js @@ -0,0 +1,52 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { 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'); + }); + }); +}; diff --git a/js/src/jsonrpc/interfaces/trace.js b/js/src/jsonrpc/interfaces/trace.js index 3dc4451f0..efe45f34e 100644 --- a/js/src/jsonrpc/interfaces/trace.js +++ b/js/src/jsonrpc/interfaces/trace.js @@ -14,9 +14,45 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { BlockNumber, Hash, Integer } from '../types'; +import { BlockNumber, Data, Hash, Integer } from '../types'; 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: { desc: 'Returns traces matching given filter', 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: { desc: 'Returns all traces of given transaction', params: [ @@ -61,19 +133,5 @@ export default { type: Array, 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' - } } }; diff --git a/js/src/modals/SMSVerification/Done/done.css b/js/src/modals/SMSVerification/Done/done.css new file mode 100644 index 000000000..18c410f3e --- /dev/null +++ b/js/src/modals/SMSVerification/Done/done.css @@ -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 . +*/ +.spacing { + margin-top: 1.5em; +} + +.container { + margin-top: .5em; + display: flex; + align-items: center; +} + +.message { + margin-top: 0; + margin-bottom: 0; + margin-left: .5em; +} diff --git a/js/src/modals/SMSVerification/Done/done.js b/js/src/modals/SMSVerification/Done/done.js new file mode 100644 index 000000000..f3f6fa515 --- /dev/null +++ b/js/src/modals/SMSVerification/Done/done.js @@ -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 . + +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 ( +
+ +

Congratulations, your account is verified!

+
+ ); + } +} diff --git a/js/src/modals/SMSVerification/Done/index.js b/js/src/modals/SMSVerification/Done/index.js new file mode 100644 index 000000000..549306dbd --- /dev/null +++ b/js/src/modals/SMSVerification/Done/index.js @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default from './done'; diff --git a/js/src/modals/SMSVerification/GatherData/gatherData.css b/js/src/modals/SMSVerification/GatherData/gatherData.css new file mode 100644 index 000000000..680986981 --- /dev/null +++ b/js/src/modals/SMSVerification/GatherData/gatherData.css @@ -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 . +*/ + +.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; + } +} diff --git a/js/src/modals/SMSVerification/GatherData/gatherData.js b/js/src/modals/SMSVerification/GatherData/gatherData.js new file mode 100644 index 000000000..f4036a3bc --- /dev/null +++ b/js/src/modals/SMSVerification/GatherData/gatherData.js @@ -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 . + +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 ( +
+

The following steps will let you prove that you control both an account and a phone number.

+
    +
  1. You send a verification request to a specific contract.
  2. +
  3. Our server puts a puzzle into this contract.
  4. +
  5. The code you receive via SMS is the solution to this puzzle.
  6. +
+ { this.renderFee() } + { this.renderCertified() } + { this.renderRequested() } + + +
{ terms }
+ + ); + } + + renderFee () { + const { fee } = this.props; + + if (!fee) { + return (

Fetching the fee…

); + } + return ( +
+ +

The fee is { fromWei(fee).toFixed(3) } ETH.

+
+ ); + } + + renderCertified () { + const { isVerified } = this.props; + + if (isVerified) { + return ( +
+ +

Your account is already verified.

+
+ ); + } else if (isVerified === false) { + return ( +
+ +

Your account is not verified yet.

+
+ ); + } + return ( +

Checking if your account is verified…

+ ); + } + + 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 ( +
+ +

You already requested verification.

+
+ ); + } + if (hasRequested === false) { + return ( +
+ +

You did not request verification yet.

+
+ ); + } + return ( +

Checking if you requested verification…

+ ); + } + + numberOnSubmit = (value) => { + this.props.setNumber(value); + } + + numberOnChange = (_, value) => { + this.props.setNumber(value); + } + + consentOnChange = (_, consentGiven) => { + this.props.setConsentGiven(consentGiven); + } +} diff --git a/js/src/modals/SMSVerification/GatherData/index.js b/js/src/modals/SMSVerification/GatherData/index.js new file mode 100644 index 000000000..1c03d3400 --- /dev/null +++ b/js/src/modals/SMSVerification/GatherData/index.js @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default from './gatherData'; diff --git a/js/src/modals/SMSVerification/QueryCode/index.js b/js/src/modals/SMSVerification/QueryCode/index.js new file mode 100644 index 000000000..539c340f0 --- /dev/null +++ b/js/src/modals/SMSVerification/QueryCode/index.js @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default from './queryCode'; diff --git a/js/src/modals/SMSVerification/QueryCode/queryCode.js b/js/src/modals/SMSVerification/QueryCode/queryCode.js new file mode 100644 index 000000000..9598fe358 --- /dev/null +++ b/js/src/modals/SMSVerification/QueryCode/queryCode.js @@ -0,0 +1,52 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import React, { Component, PropTypes } from 'react'; + +import { 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 ( +
+

The verification code has been sent to { number }.

+ +
+ ); + } + + onChange = (_, code) => { + this.props.setCode(code.trim()); + } + + onSubmit = (code) => { + this.props.setCode(code.trim()); + } +} diff --git a/js/src/modals/SMSVerification/SMSVerification.js b/js/src/modals/SMSVerification/SMSVerification.js new file mode 100644 index 000000000..4ec0b608d --- /dev/null +++ b/js/src/modals/SMSVerification/SMSVerification.js @@ -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 . + +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 ( + + { this.renderStep(phase, error) } + + ); + } + + renderDialogActions (phase, error, isStepValid) { + const { store, account, onClose } = this.props; + + const cancel = ( +