From 6a1b17d5a9bfaa7576b83484578efebb9d3d8e56 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 9 Nov 2016 11:53:40 +0100 Subject: [PATCH 01/55] Change to more common focused spelling (#3264) --- js/src/views/Settings/Views/defaults.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/src/views/Settings/Views/defaults.js b/js/src/views/Settings/Views/defaults.js index 819e3938d..dce593d2b 100644 --- a/js/src/views/Settings/Views/defaults.js +++ b/js/src/views/Settings/Views/defaults.js @@ -58,7 +58,7 @@ const defaultViews = { label: 'Contracts', route: '/contracts', value: 'contract', - description: 'Watch and interact with specific contracts that have been deployed on the network. This is a more technically-focussed environment, specifically for advanced users that understand the inner working of certain contracts.' + description: 'Watch and interact with specific contracts that have been deployed on the network. This is a more technically-focused environment, specifically for advanced users that understand the inner working of certain contracts.' }, status: { @@ -77,7 +77,7 @@ const defaultViews = { label: 'Signer', route: '/signer', value: 'signer', - description: 'The security focussed area of the application where you can approve any outgoing transactions made from the application as well as those placed into the queue by distributed applications.' + description: 'The secure transaction management area of the application where you can approve any outgoing transactions made from the application as well as those placed into the queue by distributed applications.' }, settings: { From 29add4e40a8563ea80a9ae5fad983fb59358edb1 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Wed, 9 Nov 2016 11:14:24 +0000 Subject: [PATCH 02/55] [ci skip] js-precompiled 20161109-111303 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b11ea606..34afa6a13 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#5d1134b5f836aafccb648e300817524081ac91e3" +source = "git+https://github.com/ethcore/js-precompiled.git#2508e1385a551abea3e884ce958b82a7a8bc4955" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 7a7748ad6..ee179359f 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.21", + "version": "0.2.22", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 3c6f148a165822bc481ac1c7414f22c8dc4094a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Wagner?= Date: Wed, 9 Nov 2016 13:12:28 +0100 Subject: [PATCH 03/55] Fix typo (#3298) --- js/src/dapps/basiccoin/Deploy/Deployment/deployment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/dapps/basiccoin/Deploy/Deployment/deployment.js b/js/src/dapps/basiccoin/Deploy/Deployment/deployment.js index 0fa7dc863..be08d616d 100644 --- a/js/src/dapps/basiccoin/Deploy/Deployment/deployment.js +++ b/js/src/dapps/basiccoin/Deploy/Deployment/deployment.js @@ -148,7 +148,7 @@ export default class Deployment extends Component { addresses={ addresses } onChange={ this.onChangeFrom } />
- the owner account to eploy from + the owner account to deploy from
From b33b237f7661af0dc36d74d085bb76e2537f826d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 9 Nov 2016 13:13:35 +0100 Subject: [PATCH 04/55] Refactoring Signer to auto_args + eth_signTransaction (#3261) * Sign transaction initial * Refactoring signer to auto_args --- rpc/src/v1/helpers/dispatch.rs | 147 +++++++++++++----- rpc/src/v1/helpers/requests.rs | 17 ++- rpc/src/v1/helpers/signing_queue.rs | 14 +- rpc/src/v1/impls/eth.rs | 4 +- rpc/src/v1/impls/personal.rs | 16 +- rpc/src/v1/impls/signer.rs | 30 ++-- rpc/src/v1/impls/signing.rs | 222 ++++++++++++++-------------- rpc/src/v1/impls/signing_unsafe.rs | 78 +++++++--- rpc/src/v1/tests/mocked/eth.rs | 41 ++++- rpc/src/v1/tests/mocked/signer.rs | 12 +- rpc/src/v1/tests/mocked/signing.rs | 65 +++++++- rpc/src/v1/traits/eth.rs | 2 +- rpc/src/v1/traits/eth_signing.rs | 35 ++--- rpc/src/v1/traits/parity_signing.rs | 50 +++---- rpc/src/v1/traits/signer.rs | 6 +- rpc/src/v1/types/confirmations.rs | 102 +++++++++++-- rpc/src/v1/types/mod.rs.in | 2 +- 17 files changed, 560 insertions(+), 283 deletions(-) diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index 457f96552..cbae49cc3 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -14,84 +14,153 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use rlp; use util::{Address, H256, U256, Uint, Bytes}; use util::bytes::ToPretty; + use ethkey::Signature; use ethcore::miner::MinerService; use ethcore::client::MiningBlockChainClient; use ethcore::transaction::{Action, SignedTransaction, Transaction}; use ethcore::account_provider::AccountProvider; -use jsonrpc_core::{Error, Value, to_value}; -use v1::helpers::TransactionRequest; -use v1::types::{H256 as RpcH256, H520 as RpcH520, Bytes as RpcBytes}; -use v1::helpers::errors; + +use jsonrpc_core::Error; +use v1::helpers::{errors, TransactionRequest, FilledTransactionRequest, ConfirmationPayload}; +use v1::types::{ + H256 as RpcH256, H520 as RpcH520, Bytes as RpcBytes, + ConfirmationPayload as RpcConfirmationPayload, + ConfirmationResponse, + SignRequest as RpcSignRequest, + DecryptRequest as RpcDecryptRequest, +}; pub const DEFAULT_MAC: [u8; 2] = [0, 0]; -pub fn dispatch_transaction(client: &C, miner: &M, signed_transaction: SignedTransaction) -> Result - where C: MiningBlockChainClient, M: MinerService { - let hash = RpcH256::from(signed_transaction.hash()); - - miner.import_own_transaction(client, signed_transaction) - .map_err(errors::from_transaction_error) - .map(|_| hash) +pub fn execute(client: &C, miner: &M, accounts: &AccountProvider, payload: ConfirmationPayload, pass: Option) -> Result + where C: MiningBlockChainClient, M: MinerService +{ + match payload { + ConfirmationPayload::SendTransaction(request) => { + sign_and_dispatch(client, miner, accounts, request, pass) + .map(RpcH256::from) + .map(ConfirmationResponse::SendTransaction) + }, + ConfirmationPayload::SignTransaction(request) => { + sign_no_dispatch(client, miner, accounts, request, pass) + .map(|tx| rlp::encode(&tx).to_vec()) + .map(RpcBytes) + .map(ConfirmationResponse::SignTransaction) + }, + ConfirmationPayload::Signature(address, hash) => { + signature(accounts, address, hash, pass) + .map(RpcH520::from) + .map(ConfirmationResponse::Signature) + }, + ConfirmationPayload::Decrypt(address, data) => { + decrypt(accounts, address, data, pass) + .map(RpcBytes) + .map(ConfirmationResponse::Decrypt) + }, + } } -fn signature(accounts: &AccountProvider, address: Address, password: Option, hash: H256) -> Result { +fn signature(accounts: &AccountProvider, address: Address, hash: H256, password: Option) -> Result { accounts.sign(address, password.clone(), hash).map_err(|e| match password { Some(_) => errors::from_password_error(e), None => errors::from_signing_error(e), }) } -pub fn sign(accounts: &AccountProvider, address: Address, password: Option, hash: H256) -> Result { - signature(accounts, address, password, hash) - .map(RpcH520::from) - .map(to_value) -} - -pub fn decrypt(accounts: &AccountProvider, address: Address, password: Option, msg: Bytes) -> Result { +fn decrypt(accounts: &AccountProvider, address: Address, msg: Bytes, password: Option) -> Result { accounts.decrypt(address, password.clone(), &DEFAULT_MAC, &msg) .map_err(|e| match password { Some(_) => errors::from_password_error(e), None => errors::from_signing_error(e), }) - .map(RpcBytes::from) - .map(to_value) } -pub fn sign_and_dispatch(client: &C, miner: &M, accounts: &AccountProvider, request: TransactionRequest, password: Option) -> Result +pub fn dispatch_transaction(client: &C, miner: &M, signed_transaction: SignedTransaction) -> Result + where C: MiningBlockChainClient, M: MinerService { + let hash = signed_transaction.hash(); + + miner.import_own_transaction(client, signed_transaction) + .map_err(errors::from_transaction_error) + .map(|_| hash) +} + +pub fn sign_no_dispatch(client: &C, miner: &M, accounts: &AccountProvider, filled: FilledTransactionRequest, password: Option) -> Result where C: MiningBlockChainClient, M: MinerService { let network_id = client.signing_network_id(); - let address = request.from; + let address = filled.from; let signed_transaction = { - let t = prepare_transaction(client, miner, request); + let t = Transaction { + nonce: filled.nonce + .or_else(|| miner + .last_nonce(&filled.from) + .map(|nonce| nonce + U256::one())) + .unwrap_or_else(|| client.latest_nonce(&filled.from)), + + action: filled.to.map_or(Action::Create, Action::Call), + gas: filled.gas, + gas_price: filled.gas_price, + value: filled.value, + data: filled.data, + }; + let hash = t.hash(network_id); - let signature = try!(signature(accounts, address, password, hash)); + let signature = try!(signature(accounts, address, hash, password)); t.with_signature(signature, network_id) }; + Ok(signed_transaction) +} - trace!(target: "miner", "send_transaction: dispatching tx: {} for network ID {:?}", ::rlp::encode(&signed_transaction).to_vec().pretty(), network_id); +pub fn sign_and_dispatch(client: &C, miner: &M, accounts: &AccountProvider, filled: FilledTransactionRequest, password: Option) -> Result + where C: MiningBlockChainClient, M: MinerService +{ + + let network_id = client.signing_network_id(); + let signed_transaction = try!(sign_no_dispatch(client, miner, accounts, filled, password)); + + trace!(target: "miner", "send_transaction: dispatching tx: {} for network ID {:?}", rlp::encode(&signed_transaction).to_vec().pretty(), network_id); dispatch_transaction(&*client, &*miner, signed_transaction) } -fn prepare_transaction(client: &C, miner: &M, request: TransactionRequest) -> Transaction where C: MiningBlockChainClient, M: MinerService { - Transaction { - nonce: request.nonce - .or_else(|| miner - .last_nonce(&request.from) - .map(|nonce| nonce + U256::one())) - .unwrap_or_else(|| client.latest_nonce(&request.from)), - - action: request.to.map_or(Action::Create, Action::Call), - gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()), +pub fn fill_optional_fields(request: TransactionRequest, client: &C, miner: &M) -> FilledTransactionRequest + where C: MiningBlockChainClient, M: MinerService +{ + FilledTransactionRequest { + from: request.from, + to: request.to, + nonce: request.nonce, gas_price: request.gas_price.unwrap_or_else(|| default_gas_price(client, miner)), - value: request.value.unwrap_or_else(U256::zero), - data: request.data.map_or_else(Vec::new, |b| b.to_vec()), + gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()), + value: request.value.unwrap_or_else(|| 0.into()), + data: request.data.unwrap_or_else(Vec::new), } } -pub fn default_gas_price(client: &C, miner: &M) -> U256 where C: MiningBlockChainClient, M: MinerService { +pub fn default_gas_price(client: &C, miner: &M) -> U256 + where C: MiningBlockChainClient, M: MinerService +{ client.gas_price_median(100).unwrap_or_else(|| miner.sensible_gas_price()) } + +pub fn from_rpc(payload: RpcConfirmationPayload, client: &C, miner: &M) -> ConfirmationPayload + where C: MiningBlockChainClient, M: MinerService { + + match payload { + RpcConfirmationPayload::SendTransaction(request) => { + ConfirmationPayload::SendTransaction(fill_optional_fields(request.into(), client, miner)) + }, + RpcConfirmationPayload::SignTransaction(request) => { + ConfirmationPayload::SignTransaction(fill_optional_fields(request.into(), client, miner)) + }, + RpcConfirmationPayload::Decrypt(RpcDecryptRequest { address, msg }) => { + ConfirmationPayload::Decrypt(address.into(), msg.into()) + }, + RpcConfirmationPayload::Signature(RpcSignRequest { address, hash }) => { + ConfirmationPayload::Signature(address.into(), hash.into()) + }, + } +} diff --git a/rpc/src/v1/helpers/requests.rs b/rpc/src/v1/helpers/requests.rs index 5cb6108c1..7c5e89b87 100644 --- a/rpc/src/v1/helpers/requests.rs +++ b/rpc/src/v1/helpers/requests.rs @@ -100,9 +100,22 @@ pub struct ConfirmationRequest { #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum ConfirmationPayload { /// Transaction - Transaction(FilledTransactionRequest), + SendTransaction(FilledTransactionRequest), + /// Sign Transaction + SignTransaction(FilledTransactionRequest), /// Sign request - Sign(Address, H256), + Signature(Address, H256), /// Decrypt request Decrypt(Address, Bytes), } + +impl ConfirmationPayload { + pub fn sender(&self) -> Address { + match *self { + ConfirmationPayload::SendTransaction(ref request) => request.from, + ConfirmationPayload::SignTransaction(ref request) => request.from, + ConfirmationPayload::Signature(ref address, _) => *address, + ConfirmationPayload::Decrypt(ref address, _) => *address, + } + } +} diff --git a/rpc/src/v1/helpers/signing_queue.rs b/rpc/src/v1/helpers/signing_queue.rs index 22e15d0d6..144e672c1 100644 --- a/rpc/src/v1/helpers/signing_queue.rs +++ b/rpc/src/v1/helpers/signing_queue.rs @@ -21,9 +21,10 @@ use std::collections::BTreeMap; use jsonrpc_core; use util::{Mutex, RwLock, U256}; use v1::helpers::{ConfirmationRequest, ConfirmationPayload}; +use v1::types::ConfirmationResponse; /// Result that can be returned from JSON RPC. -pub type RpcResult = Result; +pub type RpcResult = Result; /// Possible events happening in the queue that can be listened to. #[derive(Debug, PartialEq)] @@ -314,13 +315,12 @@ mod test { use std::time::Duration; use std::thread; use std::sync::{mpsc, Arc}; - use util::{Address, U256, H256, Mutex}; + use util::{Address, U256, Mutex}; use v1::helpers::{SigningQueue, ConfirmationsQueue, QueueEvent, FilledTransactionRequest, ConfirmationPayload}; - use v1::types::H256 as NH256; - use jsonrpc_core::to_value; + use v1::types::ConfirmationResponse; fn request() -> ConfirmationPayload { - ConfirmationPayload::Transaction(FilledTransactionRequest { + ConfirmationPayload::SendTransaction(FilledTransactionRequest { from: Address::from(1), to: Some(Address::from(2)), gas_price: 0.into(), @@ -353,10 +353,10 @@ mod test { // Just wait for the other thread to start thread::sleep(Duration::from_millis(100)); } - queue.request_confirmed(id, Ok(to_value(&NH256::from(H256::from(1))))); + queue.request_confirmed(id, Ok(ConfirmationResponse::SendTransaction(1.into()))); // then - assert_eq!(handle.join().expect("Thread should finish nicely"), Ok(to_value(&NH256::from(H256::from(1))))); + assert_eq!(handle.join().expect("Thread should finish nicely"), Ok(ConfirmationResponse::SendTransaction(1.into()))); } #[test] diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 4e54078fb..6986cf0ed 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -49,7 +49,7 @@ use v1::types::{ H64 as RpcH64, H256 as RpcH256, H160 as RpcH160, U256 as RpcU256, }; use v1::helpers::{CallRequest as CRequest, errors, limit_logs}; -use v1::helpers::dispatch::{default_gas_price, dispatch_transaction}; +use v1::helpers::dispatch::{dispatch_transaction, default_gas_price}; use v1::helpers::block_import::is_major_importing; use v1::helpers::auto_args::Trailing; @@ -610,7 +610,7 @@ impl Eth for EthClient where let raw_transaction = raw.to_vec(); match UntrustedRlp::new(&raw_transaction).as_val() { - Ok(signed_transaction) => dispatch_transaction(&*take_weak!(self.client), &*take_weak!(self.miner), signed_transaction), + Ok(signed_transaction) => dispatch_transaction(&*take_weak!(self.client), &*take_weak!(self.miner), signed_transaction).map(Into::into), Err(e) => Err(errors::from_rlp_error(e)), } } diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index ffb20610c..5c4a2ff51 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -26,7 +26,7 @@ use jsonrpc_core::Error; use v1::traits::Personal; use v1::types::{H160 as RpcH160, H256 as RpcH256, TransactionRequest}; use v1::helpers::errors; -use v1::helpers::dispatch::sign_and_dispatch; +use v1::helpers::dispatch::{self, sign_and_dispatch}; /// Account management (personal) rpc implementation. pub struct PersonalClient where C: MiningBlockChainClient, M: MinerService { @@ -92,13 +92,17 @@ impl Personal for PersonalClient where C: MiningBl fn sign_and_send_transaction(&self, request: TransactionRequest, password: String) -> Result { try!(self.active()); + let client = take_weak!(self.client); + let miner = take_weak!(self.miner); + let accounts = take_weak!(self.accounts); + let request = dispatch::fill_optional_fields(request.into(), &*client, &*miner); sign_and_dispatch( - &*take_weak!(self.client), - &*take_weak!(self.miner), - &*take_weak!(self.accounts), - request.into(), + &*client, + &*miner, + &*accounts, + request, Some(password) - ) + ).map(Into::into) } } diff --git a/rpc/src/v1/impls/signer.rs b/rpc/src/v1/impls/signer.rs index 61453e02b..0ee06b5c5 100644 --- a/rpc/src/v1/impls/signer.rs +++ b/rpc/src/v1/impls/signer.rs @@ -17,14 +17,15 @@ //! Transactions Confirmations rpc implementation use std::sync::{Arc, Weak}; + use jsonrpc_core::*; use ethcore::account_provider::AccountProvider; use ethcore::client::MiningBlockChainClient; use ethcore::miner::MinerService; use v1::traits::Signer; -use v1::types::{TransactionModification, ConfirmationRequest, U256}; +use v1::types::{TransactionModification, ConfirmationRequest, ConfirmationResponse, U256}; use v1::helpers::{errors, SignerService, SigningQueue, ConfirmationPayload}; -use v1::helpers::dispatch::{sign_and_dispatch, sign, decrypt}; +use v1::helpers::dispatch; /// Transactions confirmation (personal) rpc implementation. pub struct SignerClient where C: MiningBlockChainClient, M: MinerService { @@ -73,7 +74,7 @@ impl Signer for SignerClient where C: MiningBlockC // TODO [ToDr] TransactionModification is redundant for some calls // might be better to replace it in future - fn confirm_request(&self, id: U256, modification: TransactionModification, pass: String) -> Result { + fn confirm_request(&self, id: U256, modification: TransactionModification, pass: String) -> Result { try!(self.active()); let id = id.into(); @@ -83,21 +84,16 @@ impl Signer for SignerClient where C: MiningBlockC let miner = take_weak!(self.miner); signer.peek(&id).map(|confirmation| { - let result = match confirmation.payload { - ConfirmationPayload::Transaction(mut request) => { - // apply modification - if let Some(gas_price) = modification.gas_price { - request.gas_price = gas_price.into(); - } - sign_and_dispatch(&*client, &*miner, &*accounts, request.into(), Some(pass)).map(to_value) + let mut payload = confirmation.payload.clone(); + // Modify payload + match (&mut payload, modification.gas_price) { + (&mut ConfirmationPayload::SendTransaction(ref mut request), Some(gas_price)) => { + request.gas_price = gas_price.into(); }, - ConfirmationPayload::Sign(address, hash) => { - sign(&*accounts, address, Some(pass), hash) - }, - ConfirmationPayload::Decrypt(address, msg) => { - decrypt(&*accounts, address, Some(pass), msg) - }, - }; + _ => {}, + } + // Execute + let result = dispatch::execute(&*client, &*miner, &*accounts, payload, Some(pass)); if let Ok(ref response) = result { signer.request_confirmed(id, Ok(response.clone())); } diff --git a/rpc/src/v1/impls/signing.rs b/rpc/src/v1/impls/signing.rs index f54847757..cf20f1045 100644 --- a/rpc/src/v1/impls/signing.rs +++ b/rpc/src/v1/impls/signing.rs @@ -17,24 +17,33 @@ //! Signing RPC implementation. use std::sync::{Arc, Weak}; +use transient_hashmap::TransientHashMap; +use util::{U256, Mutex}; use ethcore::account_provider::AccountProvider; use ethcore::miner::MinerService; use ethcore::client::MiningBlockChainClient; -use transient_hashmap::TransientHashMap; -use util::{U256, Address, H256, Mutex}; -use jsonrpc_core::*; -use v1::helpers::{errors, SigningQueue, ConfirmationPromise, ConfirmationResult, ConfirmationPayload, TransactionRequest as TRequest, FilledTransactionRequest as FilledRequest, SignerService}; -use v1::helpers::dispatch::{default_gas_price, sign_and_dispatch, sign, decrypt}; +use jsonrpc_core::Error; +use v1::helpers::auto_args::Ready; +use v1::helpers::{ + errors, dispatch, + SigningQueue, ConfirmationPromise, ConfirmationResult, ConfirmationPayload, SignerService +}; use v1::traits::{EthSigning, ParitySigning}; -use v1::types::{TransactionRequest, H160 as RpcH160, H256 as RpcH256, U256 as RpcU256, Bytes as RpcBytes}; +use v1::types::{ + H160 as RpcH160, H256 as RpcH256, U256 as RpcU256, Bytes as RpcBytes, H520 as RpcH520, + Either as RpcEither, + TransactionRequest as RpcTransactionRequest, + ConfirmationPayload as RpcConfirmationPayload, + ConfirmationResponse as RpcConfirmationResponse +}; const MAX_PENDING_DURATION: u64 = 60 * 60; pub enum DispatchResult { Promise(ConfirmationPromise), - Value(Value), + Value(RpcConfirmationResponse), } /// Implementation of functions that require signing when no trusted signer is used. @@ -68,59 +77,41 @@ impl SigningQueueClient where Ok(()) } - fn add_to_queue(&self, sender: Address, when_unlocked: WhenUnlocked, payload: Payload) - -> Result where - WhenUnlocked: Fn(&AccountProvider) -> Result, - Payload: Fn() -> ConfirmationPayload, { + fn handle_dispatch(&self, res: Result, on_response: OnResponse) + where OnResponse: FnOnce(Result) + Send + 'static + { + match res { + Ok(DispatchResult::Value(result)) => on_response(Ok(result)), + Ok(DispatchResult::Promise(promise)) => { + promise.wait_for_result(move |result| { + on_response(result.unwrap_or_else(|| Err(errors::request_rejected()))) + }) + }, + Err(e) => on_response(Err(e)), + } + } + fn add_to_queue(&self, payload: ConfirmationPayload) -> Result { + let client = take_weak!(self.client); + let miner = take_weak!(self.miner); let accounts = take_weak!(self.accounts); + + let sender = payload.sender(); if accounts.is_unlocked(sender) { - return when_unlocked(&accounts).map(DispatchResult::Value); + return dispatch::execute(&*client, &*miner, &*accounts, payload, None).map(DispatchResult::Value); } - take_weak!(self.signer).add_request(payload()) + take_weak!(self.signer).add_request(payload) .map(DispatchResult::Promise) .map_err(|_| errors::request_rejected_limit()) } - fn handle_dispatch(&self, res: Result, ready: Ready) { - match res { - Ok(DispatchResult::Value(v)) => ready.ready(Ok(v)), - Ok(DispatchResult::Promise(promise)) => { - promise.wait_for_result(move |result| { - ready.ready(result.unwrap_or_else(|| Err(errors::request_rejected()))) - }) - }, - Err(e) => ready.ready(Err(e)), - } - } + fn dispatch(&self, payload: RpcConfirmationPayload) -> Result { + let client = take_weak!(self.client); + let miner = take_weak!(self.miner); - fn dispatch_sign(&self, params: Params) -> Result { - from_params::<(RpcH160, RpcH256)>(params).and_then(|(address, msg)| { - let address: Address = address.into(); - let msg: H256 = msg.into(); - - self.add_to_queue( - address, - |accounts| sign(accounts, address, None, msg.clone()), - || ConfirmationPayload::Sign(address, msg.clone()), - ) - }) - } - - fn dispatch_transaction(&self, params: Params) -> Result { - from_params::<(TransactionRequest, )>(params).and_then(|(request, )| { - let request: TRequest = request.into(); - let (client, miner) = (take_weak!(self.client), take_weak!(self.miner)); - self.add_to_queue( - request.from, - |accounts| sign_and_dispatch(&*client, &*miner, accounts, request.clone(), None).map(to_value), - || { - let request = fill_optional_fields(request.clone(), &*client, &*miner); - ConfirmationPayload::Transaction(request) - } - ) - }) + let payload = dispatch::from_rpc(payload, &*client, &*miner); + self.add_to_queue(payload) } } @@ -128,62 +119,59 @@ impl ParitySigning for SigningQueueClient where C: MiningBlockChainClient, M: MinerService, { - fn post_sign(&self, params: Params) -> Result { + fn post_sign(&self, address: RpcH160, hash: RpcH256) -> Result, Error> { try!(self.active()); - self.dispatch_sign(params).map(|result| match result { - DispatchResult::Value(v) => v, - DispatchResult::Promise(promise) => { - let id = promise.id(); - self.pending.lock().insert(id, promise); - to_value(&RpcU256::from(id)) - }, - }) + self.dispatch(RpcConfirmationPayload::Signature((address, hash).into())) + .map(|result| match result { + DispatchResult::Value(v) => RpcEither::Or(v), + DispatchResult::Promise(promise) => { + let id = promise.id(); + self.pending.lock().insert(id, promise); + RpcEither::Either(id.into()) + }, + }) } - fn post_transaction(&self, params: Params) -> Result { + fn post_transaction(&self, request: RpcTransactionRequest) -> Result, Error> { try!(self.active()); - self.dispatch_transaction(params).map(|result| match result { - DispatchResult::Value(v) => v, - DispatchResult::Promise(promise) => { - let id = promise.id(); - self.pending.lock().insert(id, promise); - to_value(&RpcU256::from(id)) - }, - }) + self.dispatch(RpcConfirmationPayload::SendTransaction(request)) + .map(|result| match result { + DispatchResult::Value(v) => RpcEither::Or(v), + DispatchResult::Promise(promise) => { + let id = promise.id(); + self.pending.lock().insert(id, promise); + RpcEither::Either(id.into()) + }, + }) } - fn check_request(&self, params: Params) -> Result { + fn check_request(&self, id: RpcU256) -> Result, Error> { try!(self.active()); let mut pending = self.pending.lock(); - from_params::<(RpcU256, )>(params).and_then(|(id, )| { - let id: U256 = id.into(); - let res = match pending.get(&id) { - Some(ref promise) => match promise.result() { - ConfirmationResult::Waiting => { return Ok(Value::Null); } - ConfirmationResult::Rejected => Err(errors::request_rejected()), - ConfirmationResult::Confirmed(rpc_response) => rpc_response, - }, - _ => { return Err(errors::request_not_found()); } - }; - pending.remove(&id); - res - }) + let id: U256 = id.into(); + let res = match pending.get(&id) { + Some(ref promise) => match promise.result() { + ConfirmationResult::Waiting => { return Ok(None); } + ConfirmationResult::Rejected => Err(errors::request_rejected()), + ConfirmationResult::Confirmed(rpc_response) => rpc_response.map(Some), + }, + _ => { return Err(errors::request_not_found()); } + }; + pending.remove(&id); + res } - fn decrypt_message(&self, params: Params, ready: Ready) { + fn decrypt_message(&self, ready: Ready, address: RpcH160, data: RpcBytes) { let res = self.active() - .and_then(|_| from_params::<(RpcH160, RpcBytes)>(params)) - .and_then(|(address, msg)| { - let address: Address = address.into(); - - self.add_to_queue( - address, - |accounts| decrypt(accounts, address, None, msg.clone().into()), - || ConfirmationPayload::Decrypt(address, msg.clone().into()) - ) - }); - - self.handle_dispatch(res, ready); + .and_then(|_| self.dispatch(RpcConfirmationPayload::Decrypt((address, data).into()))); + // TODO [todr] typed handle_dispatch + self.handle_dispatch(res, |response| { + match response { + Ok(RpcConfirmationResponse::Decrypt(data)) => ready.ready(Ok(data)), + Err(e) => ready.ready(Err(e)), + e => ready.ready(Err(errors::internal("Unexpected result.", e))), + } + }); } } @@ -191,26 +179,36 @@ impl EthSigning for SigningQueueClient where C: MiningBlockChainClient, M: MinerService, { - fn sign(&self, params: Params, ready: Ready) { - let res = self.active().and_then(|_| self.dispatch_sign(params)); - self.handle_dispatch(res, ready); + fn sign(&self, ready: Ready, address: RpcH160, hash: RpcH256) { + let res = self.active().and_then(|_| self.dispatch(RpcConfirmationPayload::Signature((address, hash).into()))); + self.handle_dispatch(res, |response| { + match response { + Ok(RpcConfirmationResponse::Signature(signature)) => ready.ready(Ok(signature)), + Err(e) => ready.ready(Err(e)), + e => ready.ready(Err(errors::internal("Unexpected result.", e))), + } + }); } - fn send_transaction(&self, params: Params, ready: Ready) { - let res = self.active().and_then(|_| self.dispatch_transaction(params)); - self.handle_dispatch(res, ready); + fn send_transaction(&self, ready: Ready, request: RpcTransactionRequest) { + let res = self.active().and_then(|_| self.dispatch(RpcConfirmationPayload::SendTransaction(request))); + self.handle_dispatch(res, |response| { + match response { + Ok(RpcConfirmationResponse::SendTransaction(hash)) => ready.ready(Ok(hash)), + Err(e) => ready.ready(Err(e)), + e => ready.ready(Err(errors::internal("Unexpected result.", e))), + } + }); } -} -fn fill_optional_fields(request: TRequest, client: &C, miner: &M) -> FilledRequest - where C: MiningBlockChainClient, M: MinerService { - FilledRequest { - from: request.from, - to: request.to, - nonce: request.nonce, - gas_price: request.gas_price.unwrap_or_else(|| default_gas_price(client, miner)), - gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()), - value: request.value.unwrap_or_else(|| 0.into()), - data: request.data.unwrap_or_else(Vec::new), + fn sign_transaction(&self, ready: Ready, request: RpcTransactionRequest) { + let res = self.active().and_then(|_| self.dispatch(RpcConfirmationPayload::SignTransaction(request))); + self.handle_dispatch(res, |response| { + match response { + Ok(RpcConfirmationResponse::SignTransaction(rlp)) => ready.ready(Ok(rlp)), + Err(e) => ready.ready(Err(e)), + e => ready.ready(Err(errors::internal("Unexpected result.", e))), + } + }); } } diff --git a/rpc/src/v1/impls/signing_unsafe.rs b/rpc/src/v1/impls/signing_unsafe.rs index 251ce9329..da9aeb901 100644 --- a/rpc/src/v1/impls/signing_unsafe.rs +++ b/rpc/src/v1/impls/signing_unsafe.rs @@ -22,11 +22,19 @@ use ethcore::account_provider::AccountProvider; use ethcore::miner::MinerService; use ethcore::client::MiningBlockChainClient; -use jsonrpc_core::*; +use jsonrpc_core::Error; +use v1::helpers::auto_args::Ready; use v1::helpers::errors; -use v1::helpers::dispatch::{sign_and_dispatch, sign, decrypt}; +use v1::helpers::dispatch; use v1::traits::{EthSigning, ParitySigning}; -use v1::types::{TransactionRequest, H160 as RpcH160, H256 as RpcH256, Bytes as RpcBytes}; +use v1::types::{ + U256 as RpcU256, + H160 as RpcH160, H256 as RpcH256, H520 as RpcH520, Bytes as RpcBytes, + Either as RpcEither, + TransactionRequest as RpcTransactionRequest, + ConfirmationPayload as RpcConfirmationPayload, + ConfirmationResponse as RpcConfirmationResponse, +}; /// Implementation of functions that require signing when no trusted signer is used. pub struct SigningUnsafeClient where @@ -58,26 +66,47 @@ impl SigningUnsafeClient where take_weak!(self.client).keep_alive(); Ok(()) } + + fn handle(&self, payload: RpcConfirmationPayload) -> Result { + try!(self.active()); + let client = take_weak!(self.client); + let miner = take_weak!(self.miner); + let accounts = take_weak!(self.accounts); + + let payload = dispatch::from_rpc(payload, &*client, &*miner); + dispatch::execute(&*client, &*miner, &*accounts, payload, None) + } } impl EthSigning for SigningUnsafeClient where C: MiningBlockChainClient, M: MinerService, { - fn sign(&self, params: Params, ready: Ready) { - ready.ready(self.active() - .and_then(|_| from_params::<(RpcH160, RpcH256)>(params)) - .and_then(|(address, msg)| { - sign(&*take_weak!(self.accounts), address.into(), None, msg.into()) - })) + fn sign(&self, ready: Ready, address: RpcH160, hash: RpcH256) { + let result = match self.handle(RpcConfirmationPayload::Signature((address, hash).into())) { + Ok(RpcConfirmationResponse::Signature(signature)) => Ok(signature), + Err(e) => Err(e), + e => Err(errors::internal("Unexpected result", e)), + }; + ready.ready(result); } - fn send_transaction(&self, params: Params, ready: Ready) { - ready.ready(self.active() - .and_then(|_| from_params::<(TransactionRequest, )>(params)) - .and_then(|(request, )| { - sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), &*take_weak!(self.accounts), request.into(), None).map(to_value) - })) + fn send_transaction(&self, ready: Ready, request: RpcTransactionRequest) { + let result = match self.handle(RpcConfirmationPayload::SendTransaction(request)) { + Ok(RpcConfirmationResponse::SendTransaction(hash)) => Ok(hash), + Err(e) => Err(e), + e => Err(errors::internal("Unexpected result", e)), + }; + ready.ready(result); + } + + fn sign_transaction(&self, ready: Ready, request: RpcTransactionRequest) { + let result = match self.handle(RpcConfirmationPayload::SignTransaction(request)) { + Ok(RpcConfirmationResponse::SignTransaction(rlp)) => Ok(rlp), + Err(e) => Err(e), + e => Err(errors::internal("Unexpected result", e)), + }; + ready.ready(result); } } @@ -85,25 +114,26 @@ impl ParitySigning for SigningUnsafeClient where C: MiningBlockChainClient, M: MinerService, { - fn decrypt_message(&self, params: Params, ready: Ready) { - ready.ready(self.active() - .and_then(|_| from_params::<(RpcH160, RpcBytes)>(params)) - .and_then(|(address, ciphertext)| { - decrypt(&*take_weak!(self.accounts), address.into(), None, ciphertext.0) - })) + fn decrypt_message(&self, ready: Ready, address: RpcH160, data: RpcBytes) { + let result = match self.handle(RpcConfirmationPayload::Decrypt((address, data).into())) { + Ok(RpcConfirmationResponse::Decrypt(data)) => Ok(data), + Err(e) => Err(e), + e => Err(errors::internal("Unexpected result", e)), + }; + ready.ready(result); } - fn post_sign(&self, _: Params) -> Result { + fn post_sign(&self, _: RpcH160, _: RpcH256) -> Result, Error> { // We don't support this in non-signer mode. Err(errors::signer_disabled()) } - fn post_transaction(&self, _: Params) -> Result { + fn post_transaction(&self, _: RpcTransactionRequest) -> Result, Error> { // We don't support this in non-signer mode. Err(errors::signer_disabled()) } - fn check_request(&self, _: Params) -> Result { + fn check_request(&self, _: RpcU256) -> Result, Error> { // We don't support this in non-signer mode. Err(errors::signer_disabled()) } diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index 9f654e7e0..7119de2c1 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -18,6 +18,7 @@ use std::str::FromStr; use std::collections::HashMap; use std::sync::Arc; use std::time::{Instant, Duration}; +use rlp; use jsonrpc_core::IoHandler; use util::{Uint, U256, Address, H256, FixedHash, Mutex}; use ethcore::account_provider::AccountProvider; @@ -761,6 +762,44 @@ fn rpc_eth_send_transaction() { assert_eq!(tester.io.handle_request_sync(&request), Some(response)); } + +#[test] +fn rpc_eth_sign_transaction() { + let tester = EthTester::default(); + let address = tester.accounts_provider.new_account("").unwrap(); + tester.accounts_provider.unlock_account_permanently(address, "".into()).unwrap(); + let request = r#"{ + "jsonrpc": "2.0", + "method": "eth_signTransaction", + "params": [{ + "from": ""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", + "gas": "0x76c0", + "gasPrice": "0x9184e72a000", + "value": "0x9184e72a" + }], + "id": 1 + }"#; + + let t = Transaction { + nonce: U256::one(), + gas_price: U256::from(0x9184e72a000u64), + gas: U256::from(0x76c0), + action: Action::Call(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), + value: U256::from(0x9184e72au64), + data: vec![] + }; + let signature = tester.accounts_provider.sign(address, None, t.hash(None)).unwrap(); + let t = t.with_signature(signature, None); + let rlp = rlp::encode(&t); + + let response = r#"{"jsonrpc":"2.0","result":"0x"#.to_owned() + &rlp.to_hex() + r#"","id":1}"#; + + tester.miner.last_nonces.write().insert(address.clone(), U256::zero()); + + assert_eq!(tester.io.handle_request_sync(&request), Some(response)); +} + #[test] fn rpc_eth_send_transaction_with_bad_to() { let tester = EthTester::default(); @@ -839,7 +878,7 @@ fn rpc_eth_send_raw_transaction() { let signature = tester.accounts_provider.sign(address, None, t.hash(None)).unwrap(); let t = t.with_signature(signature, None); - let rlp = ::rlp::encode(&t).to_vec().to_hex(); + let rlp = rlp::encode(&t).to_vec().to_hex(); let req = r#"{ "jsonrpc": "2.0", diff --git a/rpc/src/v1/tests/mocked/signer.rs b/rpc/src/v1/tests/mocked/signer.rs index 92e20676f..8807e2373 100644 --- a/rpc/src/v1/tests/mocked/signer.rs +++ b/rpc/src/v1/tests/mocked/signer.rs @@ -71,7 +71,7 @@ fn signer_tester() -> SignerTester { fn should_return_list_of_items_to_confirm() { // given let tester = signer_tester(); - tester.signer.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest { + tester.signer.add_request(ConfirmationPayload::SendTransaction(FilledTransactionRequest { from: Address::from(1), to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), gas_price: U256::from(10_000), @@ -80,7 +80,7 @@ fn should_return_list_of_items_to_confirm() { data: vec![], nonce: None, })).unwrap(); - tester.signer.add_request(ConfirmationPayload::Sign(1.into(), 5.into())).unwrap(); + tester.signer.add_request(ConfirmationPayload::Signature(1.into(), 5.into())).unwrap(); // when let request = r#"{"jsonrpc":"2.0","method":"signer_requestsToConfirm","params":[],"id":1}"#; @@ -100,7 +100,7 @@ fn should_return_list_of_items_to_confirm() { fn should_reject_transaction_from_queue_without_dispatching() { // given let tester = signer_tester(); - tester.signer.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest { + tester.signer.add_request(ConfirmationPayload::SendTransaction(FilledTransactionRequest { from: Address::from(1), to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), gas_price: U256::from(10_000), @@ -125,7 +125,7 @@ fn should_reject_transaction_from_queue_without_dispatching() { fn should_not_remove_transaction_if_password_is_invalid() { // given let tester = signer_tester(); - tester.signer.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest { + tester.signer.add_request(ConfirmationPayload::SendTransaction(FilledTransactionRequest { from: Address::from(1), to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), gas_price: U256::from(10_000), @@ -149,7 +149,7 @@ fn should_not_remove_transaction_if_password_is_invalid() { fn should_not_remove_sign_if_password_is_invalid() { // given let tester = signer_tester(); - tester.signer.add_request(ConfirmationPayload::Sign(0.into(), 5.into())).unwrap(); + tester.signer.add_request(ConfirmationPayload::Signature(0.into(), 5.into())).unwrap(); assert_eq!(tester.signer.requests().len(), 1); // when @@ -167,7 +167,7 @@ fn should_confirm_transaction_and_dispatch() { let tester = signer_tester(); let address = tester.accounts.new_account("test").unwrap(); let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(); - tester.signer.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest { + tester.signer.add_request(ConfirmationPayload::SendTransaction(FilledTransactionRequest { from: address, to: Some(recipient), gas_price: U256::from(10_000), diff --git a/rpc/src/v1/tests/mocked/signing.rs b/rpc/src/v1/tests/mocked/signing.rs index fc460409c..b43c1e180 100644 --- a/rpc/src/v1/tests/mocked/signing.rs +++ b/rpc/src/v1/tests/mocked/signing.rs @@ -16,15 +16,17 @@ use std::str::FromStr; use std::sync::Arc; -use jsonrpc_core::{IoHandler, to_value, Success}; +use rlp; + +use jsonrpc_core::{IoHandler, Success}; use v1::impls::SigningQueueClient; use v1::traits::{EthSigning, ParitySigning, Parity}; use v1::helpers::{SignerService, SigningQueue}; -use v1::types::{H256 as RpcH256, H520 as RpcH520, Bytes}; +use v1::types::ConfirmationResponse; use v1::tests::helpers::TestMinerService; use v1::tests::mocked::parity; -use util::{Address, FixedHash, Uint, U256, H256, H520}; +use util::{Address, FixedHash, Uint, U256, H256, ToPretty}; use ethcore::account_provider::AccountProvider; use ethcore::client::TestBlockChainClient; use ethcore::transaction::{Transaction, Action}; @@ -88,7 +90,7 @@ fn should_add_sign_to_queue() { let async_result = tester.io.handle_request(&request).unwrap(); assert_eq!(tester.signer.requests().len(), 1); // respond - tester.signer.request_confirmed(U256::from(1), Ok(to_value(&RpcH520::from(H520::default())))); + tester.signer.request_confirmed(1.into(), Ok(ConfirmationResponse::Signature(0.into()))); assert!(async_result.on_result(move |res| { assert_eq!(res, response.to_owned()); })); @@ -162,7 +164,7 @@ fn should_check_status_of_request_when_its_resolved() { "id": 1 }"#; tester.io.handle_request_sync(&request).expect("Sent"); - tester.signer.request_confirmed(U256::from(1), Ok(to_value(&"Hello World!"))); + tester.signer.request_confirmed(1.into(), Ok(ConfirmationResponse::Signature(1.into()))); // when let request = r#"{ @@ -171,7 +173,7 @@ fn should_check_status_of_request_when_its_resolved() { "params": ["0x1"], "id": 1 }"#; - let response = r#"{"jsonrpc":"2.0","result":"Hello World!","id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001","id":1}"#; // then assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned())); @@ -228,7 +230,54 @@ fn should_add_transaction_to_queue() { let async_result = tester.io.handle_request(&request).unwrap(); assert_eq!(tester.signer.requests().len(), 1); // respond - tester.signer.request_confirmed(U256::from(1), Ok(to_value(&RpcH256::from(H256::default())))); + tester.signer.request_confirmed(1.into(), Ok(ConfirmationResponse::SendTransaction(0.into()))); + assert!(async_result.on_result(move |res| { + assert_eq!(res, response.to_owned()); + })); +} + +#[test] +fn should_add_sign_transaction_to_the_queue() { + // given + let tester = eth_signing(); + let address = tester.accounts.new_account("test").unwrap(); + + assert_eq!(tester.signer.requests().len(), 0); + + // when + let request = r#"{ + "jsonrpc": "2.0", + "method": "eth_signTransaction", + "params": [{ + "from": ""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", + "gas": "0x76c0", + "gasPrice": "0x9184e72a000", + "value": "0x9184e72a" + }], + "id": 1 + }"#; + + let t = Transaction { + nonce: U256::one(), + gas_price: U256::from(0x9184e72a000u64), + gas: U256::from(0x76c0), + action: Action::Call(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), + value: U256::from(0x9184e72au64), + data: vec![] + }; + let signature = tester.accounts.sign(address, Some("test".into()), t.hash(None)).unwrap(); + let t = t.with_signature(signature, None); + let rlp = rlp::encode(&t); + + let response = r#"{"jsonrpc":"2.0","result":"0x"#.to_owned() + &rlp.to_hex() + r#"","id":1}"#; + + // then + tester.miner.last_nonces.write().insert(address.clone(), U256::zero()); + let async_result = tester.io.handle_request(&request).unwrap(); + assert_eq!(tester.signer.requests().len(), 1); + // respond + tester.signer.request_confirmed(1.into(), Ok(ConfirmationResponse::SignTransaction(rlp.to_vec().into()))); assert!(async_result.on_result(move |res| { assert_eq!(res, response.to_owned()); })); @@ -325,7 +374,7 @@ fn should_add_decryption_to_the_queue() { let async_result = tester.io.handle_request(&request).unwrap(); assert_eq!(tester.signer.requests().len(), 1); // respond - tester.signer.request_confirmed(U256::from(1), Ok(to_value(Bytes(vec![0x1, 0x2])))); + tester.signer.request_confirmed(1.into(), Ok(ConfirmationResponse::Decrypt(vec![0x1, 0x2].into()))); assert!(async_result.on_result(move |res| { assert_eq!(res, response.to_owned()); })); diff --git a/rpc/src/v1/traits/eth.rs b/rpc/src/v1/traits/eth.rs index 49f433ffd..b524c616f 100644 --- a/rpc/src/v1/traits/eth.rs +++ b/rpc/src/v1/traits/eth.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . //! Eth rpc interface. -use jsonrpc_core::*; +use jsonrpc_core::Error; use v1::types::{RichBlock, BlockNumber, Bytes, CallRequest, Filter, FilterChanges, Index}; use v1::types::{Log, Receipt, SyncStatus, Transaction, Work}; diff --git a/rpc/src/v1/traits/eth_signing.rs b/rpc/src/v1/traits/eth_signing.rs index 80979a7db..bd1d1ecdc 100644 --- a/rpc/src/v1/traits/eth_signing.rs +++ b/rpc/src/v1/traits/eth_signing.rs @@ -15,26 +15,27 @@ // along with Parity. If not, see . //! Eth rpc interface. -use std::sync::Arc; -use jsonrpc_core::*; -/// Signing methods implementation relying on unlocked accounts. -pub trait EthSigning: Sized + Send + Sync + 'static { - /// Signs the data with given address signature. - fn sign(&self, _: Params, _: Ready); +use v1::helpers::auto_args::{WrapAsync, Ready}; +use v1::types::{H160, H256, H520, TransactionRequest, Bytes}; - /// Sends transaction; will block for 20s to try to return the - /// transaction hash. - /// If it cannot yet be signed, it will return a transaction ID for - /// later use with check_transaction. - fn send_transaction(&self, _: Params, _: Ready); +build_rpc_trait! { + /// Signing methods implementation relying on unlocked accounts. + pub trait EthSigning { + /// Signs the data with given address signature. + #[rpc(async, name = "eth_sign")] + fn sign(&self, Ready, H160, H256); - /// Should be used to convert object to io delegate. - fn to_delegate(self) -> IoDelegate { - let mut delegate = IoDelegate::new(Arc::new(self)); - delegate.add_async_method("eth_sign", EthSigning::sign); - delegate.add_async_method("eth_sendTransaction", EthSigning::send_transaction); + /// Sends transaction; will block waiting for signer to return the + /// transaction hash. + /// If Signer is disable it will require the account to be unlocked. + #[rpc(async, name = "eth_sendTransaction")] + fn send_transaction(&self, Ready, TransactionRequest); - delegate + /// Signs transactions without dispatching it to the network. + /// Returns signed transaction RLP representation. + /// It can be later submitted using `eth_sendRawTransaction`. + #[rpc(async, name = "eth_signTransaction")] + fn sign_transaction(&self, Ready, TransactionRequest); } } diff --git a/rpc/src/v1/traits/parity_signing.rs b/rpc/src/v1/traits/parity_signing.rs index 1c2778b31..d97b9882b 100644 --- a/rpc/src/v1/traits/parity_signing.rs +++ b/rpc/src/v1/traits/parity_signing.rs @@ -15,38 +15,32 @@ // along with Parity. If not, see . //! ParitySigning rpc interface. -use std::sync::Arc; -use jsonrpc_core::*; +use jsonrpc_core::Error; -/// Signing methods implementation relying on unlocked accounts. -pub trait ParitySigning: Sized + Send + Sync + 'static { - /// Posts sign request asynchronously. - /// Will return a confirmation ID for later use with check_transaction. - fn post_sign(&self, _: Params) -> Result; +use v1::helpers::auto_args::{Wrap, WrapAsync, Ready}; +use v1::types::{U256, H160, H256, Bytes, ConfirmationResponse, TransactionRequest, Either}; - /// Posts transaction asynchronously. - /// Will return a transaction ID for later use with check_transaction. - fn post_transaction(&self, _: Params) -> Result; +build_rpc_trait! { + /// Signing methods implementation. + pub trait ParitySigning { + /// Posts sign request asynchronously. + /// Will return a confirmation ID for later use with check_transaction. + #[rpc(name = "parity_postSign")] + fn post_sign(&self, H160, H256) -> Result, Error>; - /// Checks the progress of a previously posted request (transaction/sign). - /// Should be given a valid send_transaction ID. - /// Returns the transaction hash, the zero hash (not yet available), - /// or the signature, - /// or an error. - fn check_request(&self, _: Params) -> Result; + /// Posts transaction asynchronously. + /// Will return a transaction ID for later use with check_transaction. + #[rpc(name = "parity_postTransaction")] + fn post_transaction(&self, TransactionRequest) -> Result, Error>; - /// Decrypt some ECIES-encrypted message. - /// First parameter is the address with which it is encrypted, second is the ciphertext. - fn decrypt_message(&self, _: Params, _: Ready); + /// Checks the progress of a previously posted request (transaction/sign). + /// Should be given a valid send_transaction ID. + #[rpc(name = "parity_checkRequest")] + fn check_request(&self, U256) -> Result, Error>; - /// Should be used to convert object to io delegate. - fn to_delegate(self) -> IoDelegate { - let mut delegate = IoDelegate::new(Arc::new(self)); - delegate.add_method("parity_postSign", ParitySigning::post_sign); - delegate.add_method("parity_postTransaction", ParitySigning::post_transaction); - delegate.add_method("parity_checkRequest", ParitySigning::check_request); - delegate.add_async_method("parity_decryptMessage", ParitySigning::decrypt_message); - - delegate + /// Decrypt some ECIES-encrypted message. + /// First parameter is the address with which it is encrypted, second is the ciphertext. + #[rpc(async, name = "parity_decryptMessage")] + fn decrypt_message(&self, Ready, H160, Bytes); } } diff --git a/rpc/src/v1/traits/signer.rs b/rpc/src/v1/traits/signer.rs index d80c6f1a6..26d6899cb 100644 --- a/rpc/src/v1/traits/signer.rs +++ b/rpc/src/v1/traits/signer.rs @@ -15,10 +15,10 @@ // along with Parity. If not, see . //! Parity Signer-related rpc interface. -use jsonrpc_core::{Value, Error}; +use jsonrpc_core::Error; use v1::helpers::auto_args::Wrap; -use v1::types::{U256, TransactionModification, ConfirmationRequest}; +use v1::types::{U256, TransactionModification, ConfirmationRequest, ConfirmationResponse}; build_rpc_trait! { @@ -31,7 +31,7 @@ build_rpc_trait! { /// Confirm specific request. #[rpc(name = "signer_confirmRequest")] - fn confirm_request(&self, U256, TransactionModification, String) -> Result; + fn confirm_request(&self, U256, TransactionModification, String) -> Result; /// Reject the confirmation request. #[rpc(name = "signer_rejectRequest")] diff --git a/rpc/src/v1/types/confirmations.rs b/rpc/src/v1/types/confirmations.rs index c5ef4efa9..e83989326 100644 --- a/rpc/src/v1/types/confirmations.rs +++ b/rpc/src/v1/types/confirmations.rs @@ -16,10 +16,11 @@ //! Types used in Confirmations queue (Trusted Signer) -use v1::types::{U256, TransactionRequest, H160, H256, Bytes}; +use std::fmt; +use serde::{Serialize, Serializer}; +use v1::types::{U256, TransactionRequest, H160, H256, H520, Bytes}; use v1::helpers; - /// Confirmation waiting in a queue #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)] pub struct ConfirmationRequest { @@ -47,6 +48,15 @@ pub struct SignRequest { pub hash: H256, } +impl From<(H160, H256)> for SignRequest { + fn from(tuple: (H160, H256)) -> Self { + SignRequest { + address: tuple.0, + hash: tuple.1, + } + } +} + /// Decrypt request #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)] pub struct DecryptRequest { @@ -56,15 +66,53 @@ pub struct DecryptRequest { pub msg: Bytes, } +impl From<(H160, Bytes)> for DecryptRequest { + fn from(tuple: (H160, Bytes)) -> Self { + DecryptRequest { + address: tuple.0, + msg: tuple.1, + } + } +} + +/// Confirmation response for particular payload +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum ConfirmationResponse { + /// Transaction Hash + SendTransaction(H256), + /// Transaction RLP + SignTransaction(Bytes), + /// Signature + Signature(H520), + /// Decrypted data + Decrypt(Bytes), +} + +impl Serialize for ConfirmationResponse { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> + where S: Serializer + { + match *self { + ConfirmationResponse::SendTransaction(ref hash) => hash.serialize(serializer), + ConfirmationResponse::SignTransaction(ref rlp) => rlp.serialize(serializer), + ConfirmationResponse::Signature(ref signature) => signature.serialize(serializer), + ConfirmationResponse::Decrypt(ref data) => data.serialize(serializer), + } + } +} + /// Confirmation payload, i.e. the thing to be confirmed #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)] pub enum ConfirmationPayload { - /// Transaction + /// Send Transaction #[serde(rename="transaction")] - Transaction(TransactionRequest), + SendTransaction(TransactionRequest), + /// Sign Transaction + #[serde(rename="transaction")] + SignTransaction(TransactionRequest), /// Signature #[serde(rename="sign")] - Sign(SignRequest), + Signature(SignRequest), /// Decryption #[serde(rename="decrypt")] Decrypt(DecryptRequest), @@ -73,8 +121,9 @@ pub enum ConfirmationPayload { impl From for ConfirmationPayload { fn from(c: helpers::ConfirmationPayload) -> Self { match c { - helpers::ConfirmationPayload::Transaction(t) => ConfirmationPayload::Transaction(t.into()), - helpers::ConfirmationPayload::Sign(address, hash) => ConfirmationPayload::Sign(SignRequest { + helpers::ConfirmationPayload::SendTransaction(t) => ConfirmationPayload::SendTransaction(t.into()), + helpers::ConfirmationPayload::SignTransaction(t) => ConfirmationPayload::SignTransaction(t.into()), + helpers::ConfirmationPayload::Signature(address, hash) => ConfirmationPayload::Signature(SignRequest { address: address.into(), hash: hash.into(), }), @@ -94,6 +143,41 @@ pub struct TransactionModification { pub gas_price: Option, } +/// Represents two possible return values. +#[derive(Debug, Clone)] +pub enum Either where + A: fmt::Debug + Clone, + B: fmt::Debug + Clone, +{ + /// Primary value + Either(A), + /// Secondary value + Or(B), +} + +impl From for Either where + A: fmt::Debug + Clone, + B: fmt::Debug + Clone, +{ + fn from(a: A) -> Self { + Either::Either(a) + } +} + +impl Serialize for Either where + A: Serialize + fmt::Debug + Clone, + B: Serialize + fmt::Debug + Clone, +{ + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> + where S: Serializer + { + match *self { + Either::Either(ref a) => a.serialize(serializer), + Either::Or(ref b) => b.serialize(serializer), + } + } +} + #[cfg(test)] mod tests { use std::str::FromStr; @@ -107,7 +191,7 @@ mod tests { // given let request = helpers::ConfirmationRequest { id: 15.into(), - payload: helpers::ConfirmationPayload::Sign(1.into(), 5.into()), + payload: helpers::ConfirmationPayload::Signature(1.into(), 5.into()), }; // when @@ -123,7 +207,7 @@ mod tests { // given let request = helpers::ConfirmationRequest { id: 15.into(), - payload: helpers::ConfirmationPayload::Transaction(helpers::FilledTransactionRequest { + payload: helpers::ConfirmationPayload::SendTransaction(helpers::FilledTransactionRequest { from: 0.into(), to: None, gas: 15_000.into(), diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index cba7487fc..b1e4ae2c9 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -38,7 +38,7 @@ pub use self::bytes::Bytes; pub use self::block::{RichBlock, Block, BlockTransactions}; pub use self::block_number::BlockNumber; pub use self::call_request::CallRequest; -pub use self::confirmations::{ConfirmationPayload, ConfirmationRequest, TransactionModification}; +pub use self::confirmations::{ConfirmationPayload, ConfirmationRequest, ConfirmationResponse, TransactionModification, SignRequest, DecryptRequest, Either}; pub use self::filter::{Filter, FilterChanges}; pub use self::hash::{H64, H160, H256, H512, H520, H2048}; pub use self::index::Index; From b17ce6c9a5666756a5ecd02d16c233ff958d7c5d Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 9 Nov 2016 13:16:17 +0100 Subject: [PATCH 05/55] Updated blance display with max decimals (#3266) --- js/src/ui/Balance/balance.css | 2 +- js/src/ui/Balance/balance.js | 29 ++++++++++++++++++++++------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/js/src/ui/Balance/balance.css b/js/src/ui/Balance/balance.css index 6fe1b2a51..f36133fe6 100644 --- a/js/src/ui/Balance/balance.css +++ b/js/src/ui/Balance/balance.css @@ -44,7 +44,7 @@ } .balanceValue { - margin: 0 1em 0 0; + margin: 0 0.5em 0 0; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; diff --git a/js/src/ui/Balance/balance.js b/js/src/ui/Balance/balance.js index 22b9fc1ae..bc98968ea 100644 --- a/js/src/ui/Balance/balance.js +++ b/js/src/ui/Balance/balance.js @@ -44,15 +44,30 @@ class Balance extends Component { .filter((balance) => new BigNumber(balance.value).gt(0)) .map((balance) => { const token = balance.token; - const value = token.format - ? new BigNumber(balance.value).div(new BigNumber(token.format)).toFormat(3) - : api.util.fromWei(balance.value).toFormat(3); + + let value; + if (token.format) { + const bnf = new BigNumber(token.format); + + let decimals = 0; + if (bnf.gte(1000)) { + decimals = 3; + } else if (bnf.gte(100)) { + decimals = 2; + } else if (bnf.gte(10)) { + decimals = 1; + } + + value = new BigNumber(balance.value).div(bnf).toFormat(decimals); + } else { + value = api.util.fromWei(balance.value).toFormat(3); + } + let imagesrc = token.image; if (!imagesrc) { - imagesrc = - images[token.address] - ? `${api.dappsUrl}${images[token.address]}` - : unknownImage; + imagesrc = images[token.address] + ? `${api.dappsUrl}${images[token.address]}` + : unknownImage; } return ( From 85ade4a2ec60c94c804cb8b6bee46d041eb8ea4b Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Wed, 9 Nov 2016 13:17:40 +0100 Subject: [PATCH 06/55] Fixed uncle details (#3299) --- ethcore/src/client/client.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index b1a70454d..ec59e01cf 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1224,8 +1224,7 @@ impl BlockChainClient for Client { fn uncle_extra_info(&self, id: UncleID) -> Option> { self.uncle(id) - .map(|block| BlockView::new(&block).header()) - .map(|header| self.engine.extra_info(&header)) + .map(|header| self.engine.extra_info(&decode(&header))) } } From 8ecbec8d6cb1e1589cc7f75ac258e10ca0de9fcc Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Wed, 9 Nov 2016 13:17:57 +0100 Subject: [PATCH 07/55] Fixes for open UI after installation (#3300) --- mac/post-install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mac/post-install.sh b/mac/post-install.sh index a0651cf96..24eb7d5f9 100755 --- a/mac/post-install.sh +++ b/mac/post-install.sh @@ -31,7 +31,7 @@ echo -n '{"fat_db":false,"mode":"passive","mode.alarm":3600,"mode.timeout":300," chown -R $USER $HOME/.parity $HOME/Library/LaunchAgents $HOME/Library/LaunchAgents/io.parity.ethereum.plist su $USER -c "launchctl load $HOME/Library/LaunchAgents/io.parity.ethereum.plist" -sleep 1 +sleep 5 -open http://127.0.0.1:8080/ +su $USER -c "open http://127.0.0.1:8080/" From f568730aa8198521cd0d2a12453c00abfa079acc Mon Sep 17 00:00:00 2001 From: arkpar Date: Wed, 9 Nov 2016 13:44:48 +0100 Subject: [PATCH 08/55] Fixed MD5 generation --- .gitlab-ci.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9d82928cf..591807e24 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,12 +23,12 @@ linux-stable: script: - cargo build --release $CARGOFLAGS - strip target/release/parity - - md5sum target/release/parity >> parity.md5 + - md5sum target/release/parity > parity.md5 - sh scripts/deb-build.sh amd64 - cp target/release/parity deb/usr/bin/parity - export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n") - dpkg-deb -b deb "parity_"$VER"_amd64.deb" - - md5sum "parity_"$VER"_amd64.deb" >> "parity_"$VER"_amd64.deb.md5" + - md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5" - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity --body target/release/parity @@ -93,7 +93,7 @@ linux-centos: - export CC="gcc" - cargo build --release $CARGOFLAGS - strip target/release/parity - - md5sum target/release/parity >> parity.md5 + - md5sum target/release/parity > parity.md5 - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity --body target/release/parity @@ -118,12 +118,12 @@ linux-i686: - export HOST_CXX=g++ - cargo build --target i686-unknown-linux-gnu --release $CARGOFLAGS - strip target/i686-unknown-linux-gnu/release/parity - - md5sum target/i686-unknown-linux-gnu/release/parity >> parity.md5 + - md5sum target/i686-unknown-linux-gnu/release/parity > parity.md5 - sh scripts/deb-build.sh i386 - cp target/i686-unknown-linux-gnu/release/parity deb/usr/bin/parity - export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n") - dpkg-deb -b deb "parity_"$VER"_i386.deb" - - md5sum "parity_"$VER"_i386.deb" >> "parity_"$VER"_i386.deb.md5" + - md5sum "parity_"$VER"_i386.deb" > "parity_"$VER"_i386.deb.md5" - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/parity --body target/i686-unknown-linux-gnu/release/parity @@ -158,12 +158,12 @@ linux-armv7: - cat .cargo/config - cargo build --target armv7-unknown-linux-gnueabihf --release $CARGOFLAGS - arm-linux-gnueabihf-strip target/armv7-unknown-linux-gnueabihf/release/parity - - md5sum target/armv7-unknown-linux-gnueabihf/release/parity >> parity.md5 + - md5sum target/armv7-unknown-linux-gnueabihf/release/parity > parity.md5 - sh scripts/deb-build.sh armhf - cp target/armv7-unknown-linux-gnueabihf/release/parity deb/usr/bin/parity - export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n") - dpkg-deb -b deb "parity_"$VER"_armhf.deb" - - md5sum "parity_"$VER"_armhf.deb" >> "parity_"$VER"_armhf.deb.md5" + - md5sum "parity_"$VER"_armhf.deb" > "parity_"$VER"_armhf.deb.md5" - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/parity --body target/armv7-unknown-linux-gnueabihf/release/parity @@ -198,12 +198,12 @@ linux-arm: - cat .cargo/config - cargo build --target arm-unknown-linux-gnueabihf --release $CARGOFLAGS - arm-linux-gnueabihf-strip target/arm-unknown-linux-gnueabihf/release/parity - - md5sum target/arm-unknown-linux-gnueabihf/release/parity >> parity.md5 + - md5sum target/arm-unknown-linux-gnueabihf/release/parity > parity.md5 - sh scripts/deb-build.sh armhf - cp target/arm-unknown-linux-gnueabihf/release/parity deb/usr/bin/parity - export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n") - dpkg-deb -b deb "parity_"$VER"_armhf.deb" - - md5sum "parity_"$VER"_armhf.deb" >> "parity_"$VER"_armhf.deb.md5" + - md5sum "parity_"$VER"_armhf.deb" > "parity_"$VER"_armhf.deb.md5" - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/parity --body target/arm-unknown-linux-gnueabihf/release/parity @@ -238,7 +238,7 @@ linux-armv6: - cat .cargo/config - cargo build --target arm-unknown-linux-gnueabi --release $CARGOFLAGS - arm-linux-gnueabi-strip target/arm-unknown-linux-gnueabi/release/parity - - md5sum target/arm-unknown-linux-gnueabi/release/parity >> parity.md5 + - md5sum target/arm-unknown-linux-gnueabi/release/parity > parity.md5 - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabi/parity --body target/arm-unknown-linux-gnueabi/release/parity @@ -271,12 +271,12 @@ linux-aarch64: - cat .cargo/config - cargo build --target aarch64-unknown-linux-gnu --release $CARGOFLAGS - aarch64-linux-gnu-strip target/aarch64-unknown-linux-gnu/release/parity - - md5sum target/aarch64-unknown-linux-gnu/release/parity >> parity.md5 + - md5sum target/aarch64-unknown-linux-gnu/release/parity > parity.md5 - sh scripts/deb-build.sh arm64 - cp target/aarch64-unknown-linux-gnu/release/parity deb/usr/bin/parity - export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n") - dpkg-deb -b deb "parity_"$VER"_arm64.deb" - - md5sum "parity_"$VER"_arm64.deb" >> "parity_"$VER"_arm64.deb.md5" + - md5sum "parity_"$VER"_arm64.deb" > "parity_"$VER"_arm64.deb.md5" - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/parity --body target/aarch64-unknown-linux-gnu/release/parity @@ -301,7 +301,7 @@ darwin: script: - cargo build --release $CARGOFLAGS - rm -rf parity.md5 - - md5sum target/release/parity >> parity.md5 + - md5sum target/release/parity > parity.md5 - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/parity --body target/release/parity From 95e03f87fae7ac8af5171819fdad5284fbb3cbbf Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 9 Nov 2016 14:01:59 +0100 Subject: [PATCH 09/55] Remove 127.0.0.1 references (#3303) --- js/src/secureApi.js | 2 +- js/src/views/Dapps/Summary/summary.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/js/src/secureApi.js b/js/src/secureApi.js index abc893d78..62454e7ac 100644 --- a/js/src/secureApi.js +++ b/js/src/secureApi.js @@ -122,7 +122,7 @@ export default class SecureApi extends Api { } get dappsUrl () { - return `http://127.0.0.1:${this._dappsPort}`; + return `http://${window.location.hostname}:${this._dappsPort}`; } get signerPort () { diff --git a/js/src/views/Dapps/Summary/summary.js b/js/src/views/Dapps/Summary/summary.js index a1c9f48b6..912f1495e 100644 --- a/js/src/views/Dapps/Summary/summary.js +++ b/js/src/views/Dapps/Summary/summary.js @@ -32,7 +32,7 @@ export default class Summary extends Component { } render () { - const { dappsPort } = this.context.api; + const { dappsUrl } = this.context.api; const { app } = this.props; if (!app) { @@ -41,9 +41,9 @@ export default class Summary extends Component { let image =
 
; if (app.type === 'local') { - image = ; + image = ; } else { - image = ; + image = ; } return ( From 599f214ad9cd963160257b8cee0256627041f620 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Wed, 9 Nov 2016 13:25:39 +0000 Subject: [PATCH 10/55] [ci skip] js-precompiled 20161109-132407 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34afa6a13..925535d8a 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#2508e1385a551abea3e884ce958b82a7a8bc4955" +source = "git+https://github.com/ethcore/js-precompiled.git#90bee2d692c71301ad7266d2d9667cba1a93e9f6" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index ee179359f..0eae50582 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.22", + "version": "0.2.23", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 22faee2099df5d896903161bd4a87088be297f8d Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 9 Nov 2016 15:48:45 +0100 Subject: [PATCH 11/55] Update account recovery phrase hint --- js/src/modals/CreateAccount/AccountDetails/accountDetails.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/modals/CreateAccount/AccountDetails/accountDetails.js b/js/src/modals/CreateAccount/AccountDetails/accountDetails.js index 646c7c180..14c858c06 100644 --- a/js/src/modals/CreateAccount/AccountDetails/accountDetails.js +++ b/js/src/modals/CreateAccount/AccountDetails/accountDetails.js @@ -58,7 +58,7 @@ export default class AccountDetails extends Component { readOnly allowCopy hint='the account recovery phrase' - label='account recovery phrase (keep safe)' + label='owner recovery phrase (keep private and secure, it allows full and unlimited access to the account)' value={ phrase } /> ); } From 4712b882e5dd28a41b1b900b702c603c577d45a3 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 9 Nov 2016 18:02:24 +0100 Subject: [PATCH 12/55] New transaction tests (#3313) * Add new transaction tests. * Add new test. --- ethcore/res/ethereum/tests | 2 +- ethcore/src/json_tests/transaction.rs | 27 +++++++++++++++------------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/ethcore/res/ethereum/tests b/ethcore/res/ethereum/tests index 853333e7d..9028c4801 160000 --- a/ethcore/res/ethereum/tests +++ b/ethcore/res/ethereum/tests @@ -1 +1 @@ -Subproject commit 853333e7da312775fb8f32f2c2771b8578cd0d79 +Subproject commit 9028c4801fd39fbb71a9796979182549a24e81c8 diff --git a/ethcore/src/json_tests/transaction.rs b/ethcore/src/json_tests/transaction.rs index dd5a4ef14..438852124 100644 --- a/ethcore/src/json_tests/transaction.rs +++ b/ethcore/src/json_tests/transaction.rs @@ -26,8 +26,7 @@ fn do_json_test(json_data: &[u8]) -> Vec { let old_schedule = evm::Schedule::new_frontier(); let new_schedule = evm::Schedule::new_homestead(); for (name, test) in tests.into_iter() { - let mut fail = false; - let mut fail_unless = |cond: bool| if !cond && !fail { failed.push(name.clone()); println!("Transaction failed: {:?}", name); fail = true }; + let mut fail_unless = |cond: bool, title: &str| if !cond { failed.push(name.clone()); println!("Transaction failed: {:?}: {:?}", name, title); }; let number: Option = test.block_number.map(Into::into); let schedule = match number { @@ -35,7 +34,7 @@ fn do_json_test(json_data: &[u8]) -> Vec { Some(x) if x < 1_150_000 => &old_schedule, Some(_) => &new_schedule }; - let allow_network_id_of_one = number.map_or(false, |n| n > 2600000); + let allow_network_id_of_one = number.map_or(false, |n| n >= 3_500_000); let rlp: Vec = test.rlp.into(); let res = UntrustedRlp::new(&rlp) @@ -43,26 +42,26 @@ fn do_json_test(json_data: &[u8]) -> Vec { .map_err(From::from) .and_then(|t: SignedTransaction| t.validate(schedule, schedule.have_delegate_call, allow_network_id_of_one)); - fail_unless(test.transaction.is_none() == res.is_err()); + fail_unless(test.transaction.is_none() == res.is_err(), "Validity different"); if let (Some(tx), Some(sender)) = (test.transaction, test.sender) { let t = res.unwrap(); - fail_unless(t.sender().unwrap() == sender.into()); + fail_unless(t.sender().unwrap() == sender.into(), "sender mismatch"); let is_acceptable_network_id = match t.network_id() { None => true, Some(1) if allow_network_id_of_one => true, _ => false, }; - fail_unless(is_acceptable_network_id); + fail_unless(is_acceptable_network_id, "Network ID unacceptable"); let data: Vec = tx.data.into(); - fail_unless(t.data == data); - fail_unless(t.gas_price == tx.gas_price.into()); - fail_unless(t.nonce == tx.nonce.into()); - fail_unless(t.value == tx.value.into()); + fail_unless(t.data == data, "data mismatch"); + fail_unless(t.gas_price == tx.gas_price.into(), "gas_price mismatch"); + fail_unless(t.nonce == tx.nonce.into(), "nonce mismatch"); + fail_unless(t.value == tx.value.into(), "value mismatch"); let to: Option = tx.to.into(); let to: Option
= to.map(Into::into); match t.action { - Action::Call(dest) => fail_unless(Some(dest) == to), - Action::Create => fail_unless(None == to), + Action::Call(dest) => fail_unless(Some(dest) == to, "call/destination mismatch"), + Action::Create => fail_unless(None == to, "create mismatch"), } } } @@ -80,3 +79,7 @@ declare_test!{TransactionTests_Homestead_ttTransactionTest, "TransactionTests/Ho declare_test!{heavy => TransactionTests_Homestead_tt10mbDataField, "TransactionTests/Homestead/tt10mbDataField"} declare_test!{TransactionTests_Homestead_ttWrongRLPTransaction, "TransactionTests/Homestead/ttWrongRLPTransaction"} declare_test!{TransactionTests_RandomTests_tr201506052141PYTHON, "TransactionTests/RandomTests/tr201506052141PYTHON"} +declare_test!{TransactionTests_Homestead_ttTransactionTestEip155VitaliksTests, "TransactionTests/Homestead/ttTransactionTestEip155VitaliksTests"} +declare_test!{TransactionTests_EIP155_ttTransactionTest, "TransactionTests/EIP155/ttTransactionTest"} +declare_test!{TransactionTests_EIP155_ttTransactionTestEip155VitaliksTests, "TransactionTests/EIP155/ttTransactionTestEip155VitaliksTests"} +declare_test!{TransactionTests_EIP155_ttTransactionTestVRule, "TransactionTests/EIP155/ttTransactionTestVRule"} From 29aecc2cff401ae6af9d1e4a098159d54346c170 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Wed, 9 Nov 2016 17:19:59 +0000 Subject: [PATCH 13/55] [ci skip] js-precompiled 20161109-171832 --- Cargo.lock | 2 +- ethcore/res/ethereum/tests | 2 +- js/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 925535d8a..c3dd51492 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#90bee2d692c71301ad7266d2d9667cba1a93e9f6" +source = "git+https://github.com/ethcore/js-precompiled.git#9054ef95a5d79cbd8fefe4869ec3b4de07e9a72d" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/ethcore/res/ethereum/tests b/ethcore/res/ethereum/tests index 9028c4801..853333e7d 160000 --- a/ethcore/res/ethereum/tests +++ b/ethcore/res/ethereum/tests @@ -1 +1 @@ -Subproject commit 9028c4801fd39fbb71a9796979182549a24e81c8 +Subproject commit 853333e7da312775fb8f32f2c2771b8578cd0d79 diff --git a/js/package.json b/js/package.json index 0eae50582..df9e37811 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.23", + "version": "0.2.24", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 877cfe9b530e7a577b914a2de7c529d8d2cb5573 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 9 Nov 2016 18:26:10 +0100 Subject: [PATCH 14/55] Fix signer token updates (#3302) * Manual bump of package.json (recovery) * Debug * Simplify status connections --- js/src/redux/providers/status.js | 12 +++--------- js/src/secureApi.js | 4 ++-- js/src/views/Connection/connection.js | 2 +- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/js/src/redux/providers/status.js b/js/src/redux/providers/status.js index 0e2bf6866..9f47517f5 100644 --- a/js/src/redux/providers/status.js +++ b/js/src/redux/providers/status.js @@ -85,11 +85,6 @@ export default class Status { setTimeout(this._pollStatus, timeout); }; - const wasConnected = this._store.getState().nodeStatus.isConnected; - if (isConnected !== wasConnected) { - this._fetchEnode(); - } - this._store.dispatch(statusCollection({ isConnected, isConnecting, needsToken, secureToken })); if (!isConnected) { @@ -111,8 +106,7 @@ export default class Status { this._api.parity.netPort(), this._api.parity.nodeName(), this._api.parity.rpcSettings(), - this._api.eth.syncing(), - this._pollTraceMode() + this._api.eth.syncing() ]) .then(([clientVersion, coinbase, defaultExtraData, extraData, gasFloorTarget, hashrate, minGasPrice, netChain, netPeers, netPort, nodeName, rpcSettings, syncing, traceMode]) => { const isTest = netChain === 'morden' || netChain === 'testnet'; @@ -134,12 +128,12 @@ export default class Status { isTest, traceMode })); - nextTimeout(); }) .catch((error) => { console.error('_pollStatus', error); - nextTimeout(); }); + + nextTimeout(); } _pollLogs = () => { diff --git a/js/src/secureApi.js b/js/src/secureApi.js index 62454e7ac..6bb9d38ea 100644 --- a/js/src/secureApi.js +++ b/js/src/secureApi.js @@ -23,7 +23,7 @@ export default class SecureApi extends Api { super(new Api.Transport.Ws(url, sysuiToken)); this._isConnecting = true; - this._connectState = 0; + this._connectState = sysuiToken === 'initial' ? 1 : 0; this._needsToken = false; this._dappsPort = 8080; this._signerPort = 8180; @@ -110,7 +110,7 @@ export default class SecureApi extends Api { console.log('SecureApi:connectSuccess', this._transport.token); } - updateToken (token, connectState) { + updateToken (token, connectState = 0) { this._connectState = connectState; this._transport.updateToken(token.replace(/[^a-zA-Z0-9]/g, '')); this._followConnection(); diff --git a/js/src/views/Connection/connection.js b/js/src/views/Connection/connection.js index 07fc8f18a..2929b38b8 100644 --- a/js/src/views/Connection/connection.js +++ b/js/src/views/Connection/connection.js @@ -128,7 +128,7 @@ class Connection extends Component { const { api } = this.context; const { token } = this.state; - api.updateToken(token); + api.updateToken(token, 0); this.setState({ token: '', validToken: false }); } } From 915766c7bfb72e420f6702fe1b2267399d48ede8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 9 Nov 2016 18:26:35 +0100 Subject: [PATCH 15/55] Fix spurious signer tests failures (#3312) * Increasing sleep time for signer tests * Attempt re-connections instead of delaying tests execution --- devtools/src/http_client.rs | 23 ++++++++++++++++++++++- signer/src/tests/mod.rs | 3 +-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/devtools/src/http_client.rs b/devtools/src/http_client.rs index acba2989e..2440a7cda 100644 --- a/devtools/src/http_client.rs +++ b/devtools/src/http_client.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::thread; use std::time::Duration; use std::io::{Read, Write}; use std::str::{self, Lines}; @@ -42,8 +43,28 @@ pub fn read_block(lines: &mut Lines, all: bool) -> String { block } +fn connect(address: &SocketAddr) -> TcpStream { + let mut retries = 0; + let mut last_error = None; + while retries < 10 { + retries += 1; + + let res = TcpStream::connect(address); + match res { + Ok(stream) => { + return stream; + }, + Err(e) => { + last_error = Some(e); + thread::sleep(Duration::from_millis(retries * 10)); + } + } + } + panic!("Unable to connect to the server. Last error: {:?}", last_error); +} + pub fn request(address: &SocketAddr, request: &str) -> Response { - let mut req = TcpStream::connect(address).unwrap(); + let mut req = connect(address); req.set_read_timeout(Some(Duration::from_secs(1))).unwrap(); req.write_all(request.as_bytes()).unwrap(); diff --git a/signer/src/tests/mod.rs b/signer/src/tests/mod.rs index 9b85f1554..d442a7e0d 100644 --- a/signer/src/tests/mod.rs +++ b/signer/src/tests/mod.rs @@ -16,7 +16,7 @@ use std::ops::{Deref, DerefMut}; use std::thread; -use std::time::{self, Duration}; +use std::time; use std::sync::Arc; use devtools::{http_client, RandomTempPath}; use rpc::ConfirmationsQueue; @@ -50,7 +50,6 @@ pub fn serve() -> (Server, usize, GuardedAuthCodes) { let builder = ServerBuilder::new(queue, path.to_path_buf()); let port = 35000 + rand::random::() % 10000; let res = builder.start(format!("127.0.0.1:{}", port).parse().unwrap()).unwrap(); - thread::sleep(Duration::from_millis(25)); (res, port, GuardedAuthCodes { authcodes: AuthCodes::from_file(&path).unwrap(), From eba0dd5023a8284b99eeaf7d3c0d8c5562b3ed0c Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Wed, 9 Nov 2016 19:40:36 +0100 Subject: [PATCH 16/55] Additional snapshot sync checks (#3318) * Additional snapshot sync checks * Proper checks * Proper highset block check --- sync/src/chain.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 063d60e6b..2f810e754 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -457,12 +457,19 @@ impl ChainSync { if self.state != SyncState::WaitingPeers { return; } - let best_block = io.chain().chain_info().best_block_number; + // Make sure the snapshot block is not too far away from best block and network best block and + // that it is higher than fork detection block + let our_best_block = io.chain().chain_info().best_block_number; + let fork_block = self.fork_block.as_ref().map(|&(n, _)| n).unwrap_or(0); let (best_hash, max_peers, snapshot_peers) = { //collect snapshot infos from peers let snapshots = self.peers.iter() - .filter(|&(_, p)| p.is_allowed() && p.snapshot_number.map_or(false, |sn| best_block < sn && (sn - best_block) > SNAPSHOT_RESTORE_THRESHOLD)) + .filter(|&(_, p)| p.is_allowed() && p.snapshot_number.map_or(false, |sn| + our_best_block < sn && (sn - our_best_block) > SNAPSHOT_RESTORE_THRESHOLD && + sn > fork_block && + self.highest_block.map_or(true, |highest| highest >= sn && (highest - sn) <= SNAPSHOT_RESTORE_THRESHOLD) + )) .filter_map(|(p, peer)| peer.snapshot_hash.map(|hash| (p, hash.clone()))); let mut snapshot_peers = HashMap::new(); From 88c9cea04d84272405d97b29649a3fe50aa06103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 9 Nov 2016 19:41:47 +0100 Subject: [PATCH 17/55] Dapps interface RPC (#3311) * Dapps Interface RPC * Adding JS apis * Support for signer interface in proxypac and embeds * Fixing tests * fixing tests again --- dapps/src/apps/fetcher.rs | 26 ++++++++++----------- dapps/src/apps/fs.rs | 4 ++-- dapps/src/apps/mod.rs | 12 +++++----- dapps/src/handlers/content.rs | 16 ++++++------- dapps/src/handlers/fetch.rs | 16 ++++++------- dapps/src/handlers/mod.rs | 8 +++---- dapps/src/lib.rs | 36 ++++++++++++++--------------- dapps/src/page/builtin.rs | 14 +++++------ dapps/src/page/handler.rs | 12 +++++----- dapps/src/page/local.rs | 12 +++++----- dapps/src/proxypac.rs | 12 +++++----- dapps/src/router/mod.rs | 16 ++++++------- dapps/src/tests/helpers.rs | 4 ++-- js/src/api/rpc/parity/parity.js | 5 ++++ js/src/jsonrpc/interfaces/parity.js | 9 ++++++++ parity/configuration.rs | 6 ++--- parity/dapps.rs | 10 ++++---- parity/rpc_apis.rs | 4 +++- parity/run.rs | 8 +++++-- rpc/src/v1/helpers/signer.rs | 18 +++++++-------- rpc/src/v1/impls/parity.rs | 13 ++++++++++- rpc/src/v1/tests/mocked/parity.rs | 25 ++++++++++++++++++-- rpc/src/v1/traits/parity.rs | 4 ++++ 23 files changed, 173 insertions(+), 117 deletions(-) diff --git a/dapps/src/apps/fetcher.rs b/dapps/src/apps/fetcher.rs index 041064121..2c1414201 100644 --- a/dapps/src/apps/fetcher.rs +++ b/dapps/src/apps/fetcher.rs @@ -47,7 +47,7 @@ pub struct ContentFetcher { resolver: R, cache: Arc>, sync: Arc, - embeddable_at: Option, + embeddable_on: Option<(String, u16)>, } impl Drop for ContentFetcher { @@ -59,7 +59,7 @@ impl Drop for ContentFetcher { impl ContentFetcher { - pub fn new(resolver: R, sync_status: Arc, embeddable_at: Option) -> Self { + pub fn new(resolver: R, sync_status: Arc, embeddable_on: Option<(String, u16)>) -> Self { let mut dapps_path = env::temp_dir(); dapps_path.push(random_filename()); @@ -68,17 +68,17 @@ impl ContentFetcher { resolver: resolver, sync: sync_status, cache: Arc::new(Mutex::new(ContentCache::default())), - embeddable_at: embeddable_at, + embeddable_on: embeddable_on, } } - fn still_syncing(port: Option) -> Box { + fn still_syncing(address: Option<(String, u16)>) -> Box { Box::new(ContentHandler::error( StatusCode::ServiceUnavailable, "Sync In Progress", "Your node is still syncing. We cannot resolve any content before it's fully synced.", Some("Refresh"), - port, + address, )) } @@ -145,7 +145,7 @@ impl ContentFetcher { match content { // Don't serve dapps if we are still syncing (but serve content) Some(URLHintResult::Dapp(_)) if self.sync.is_major_importing() => { - (None, Self::still_syncing(self.embeddable_at)) + (None, Self::still_syncing(self.embeddable_on.clone())) }, Some(URLHintResult::Dapp(dapp)) => { let (handler, fetch_control) = ContentFetcherHandler::new( @@ -155,9 +155,9 @@ impl ContentFetcher { id: content_id.clone(), dapps_path: self.dapps_path.clone(), on_done: Box::new(on_done), - embeddable_at: self.embeddable_at, + embeddable_on: self.embeddable_on.clone(), }, - self.embeddable_at, + self.embeddable_on.clone(), ); (Some(ContentStatus::Fetching(fetch_control)), Box::new(handler) as Box) @@ -172,13 +172,13 @@ impl ContentFetcher { content_path: self.dapps_path.clone(), on_done: Box::new(on_done), }, - self.embeddable_at, + self.embeddable_on.clone(), ); (Some(ContentStatus::Fetching(fetch_control)), Box::new(handler) as Box) }, None if self.sync.is_major_importing() => { - (None, Self::still_syncing(self.embeddable_at)) + (None, Self::still_syncing(self.embeddable_on.clone())) }, None => { // This may happen when sync status changes in between @@ -188,7 +188,7 @@ impl ContentFetcher { "Resource Not Found", "Requested resource was not found.", None, - self.embeddable_at, + self.embeddable_on.clone(), )) as Box) }, } @@ -293,7 +293,7 @@ struct DappInstaller { id: String, dapps_path: PathBuf, on_done: Box) + Send>, - embeddable_at: Option, + embeddable_on: Option<(String, u16)>, } impl DappInstaller { @@ -386,7 +386,7 @@ impl ContentValidator for DappInstaller { try!(manifest_file.write_all(manifest_str.as_bytes())); // Create endpoint - let app = LocalPageEndpoint::new(target, manifest.clone().into(), PageCache::Enabled, self.embeddable_at); + let app = LocalPageEndpoint::new(target, manifest.clone().into(), PageCache::Enabled, self.embeddable_on.clone()); // Return modified app manifest Ok((manifest.id.clone(), app)) diff --git a/dapps/src/apps/fs.rs b/dapps/src/apps/fs.rs index 104b33035..f0b4ddfa8 100644 --- a/dapps/src/apps/fs.rs +++ b/dapps/src/apps/fs.rs @@ -97,12 +97,12 @@ fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo { }) } -pub fn local_endpoints(dapps_path: String, signer_port: Option) -> Endpoints { +pub fn local_endpoints(dapps_path: String, signer_address: Option<(String, u16)>) -> Endpoints { let mut pages = Endpoints::new(); for dapp in local_dapps(dapps_path) { pages.insert( dapp.id, - Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, signer_port)) + Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, signer_address.clone())) ); } pages diff --git a/dapps/src/apps/mod.rs b/dapps/src/apps/mod.rs index 11919d6d2..3cb0d8256 100644 --- a/dapps/src/apps/mod.rs +++ b/dapps/src/apps/mod.rs @@ -37,26 +37,26 @@ pub fn utils() -> Box { Box::new(PageEndpoint::with_prefix(parity_ui::App::default(), UTILS_PATH.to_owned())) } -pub fn all_endpoints(dapps_path: String, signer_port: Option) -> Endpoints { +pub fn all_endpoints(dapps_path: String, signer_address: Option<(String, u16)>) -> Endpoints { // fetch fs dapps at first to avoid overwriting builtins - let mut pages = fs::local_endpoints(dapps_path, signer_port); + let mut pages = fs::local_endpoints(dapps_path, signer_address.clone()); // NOTE [ToDr] Dapps will be currently embeded on 8180 - insert::(&mut pages, "ui", Embeddable::Yes(signer_port)); - pages.insert("proxy".into(), ProxyPac::boxed(signer_port)); + insert::(&mut pages, "ui", Embeddable::Yes(signer_address.clone())); + pages.insert("proxy".into(), ProxyPac::boxed(signer_address)); pages } fn insert(pages: &mut Endpoints, id: &str, embed_at: Embeddable) { pages.insert(id.to_owned(), Box::new(match embed_at { - Embeddable::Yes(port) => PageEndpoint::new_safe_to_embed(T::default(), port), + Embeddable::Yes(address) => PageEndpoint::new_safe_to_embed(T::default(), address), Embeddable::No => PageEndpoint::new(T::default()), })); } enum Embeddable { - Yes(Option), + Yes(Option<(String, u16)>), #[allow(dead_code)] No, } diff --git a/dapps/src/handlers/content.rs b/dapps/src/handlers/content.rs index a67fbcd0b..738a9a890 100644 --- a/dapps/src/handlers/content.rs +++ b/dapps/src/handlers/content.rs @@ -32,7 +32,7 @@ pub struct ContentHandler { content: String, mimetype: Mime, write_pos: usize, - safe_to_embed_at_port: Option, + safe_to_embed_on: Option<(String, u16)>, } impl ContentHandler { @@ -44,31 +44,31 @@ impl ContentHandler { Self::new(StatusCode::NotFound, content, mimetype) } - pub fn html(code: StatusCode, content: String, embeddable_at: Option) -> Self { - Self::new_embeddable(code, content, mime!(Text/Html), embeddable_at) + pub fn html(code: StatusCode, content: String, embeddable_on: Option<(String, u16)>) -> Self { + Self::new_embeddable(code, content, mime!(Text/Html), embeddable_on) } - pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>, embeddable_at: Option) -> Self { + pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>, embeddable_on: Option<(String, u16)>) -> Self { Self::html(code, format!( include_str!("../error_tpl.html"), title=title, message=message, details=details.unwrap_or_else(|| ""), version=version(), - ), embeddable_at) + ), embeddable_on) } pub fn new(code: StatusCode, content: String, mimetype: Mime) -> Self { Self::new_embeddable(code, content, mimetype, None) } - pub fn new_embeddable(code: StatusCode, content: String, mimetype: Mime, embeddable_at: Option) -> Self { + pub fn new_embeddable(code: StatusCode, content: String, mimetype: Mime, embeddable_on: Option<(String, u16)>) -> Self { ContentHandler { code: code, content: content, mimetype: mimetype, write_pos: 0, - safe_to_embed_at_port: embeddable_at, + safe_to_embed_on: embeddable_on, } } } @@ -85,7 +85,7 @@ impl server::Handler for ContentHandler { fn on_response(&mut self, res: &mut server::Response) -> Next { res.set_status(self.code); res.headers_mut().set(header::ContentType(self.mimetype.clone())); - add_security_headers(&mut res.headers_mut(), self.safe_to_embed_at_port.clone()); + add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone()); Next::write() } diff --git a/dapps/src/handlers/fetch.rs b/dapps/src/handlers/fetch.rs index 1a0221bff..a34b58fa7 100644 --- a/dapps/src/handlers/fetch.rs +++ b/dapps/src/handlers/fetch.rs @@ -138,7 +138,7 @@ pub struct ContentFetcherHandler { client: Option, installer: H, request_url: Option, - embeddable_at: Option, + embeddable_on: Option<(String, u16)>, } impl Drop for ContentFetcherHandler { @@ -157,7 +157,7 @@ impl ContentFetcherHandler { url: String, control: Control, handler: H, - embeddable_at: Option, + embeddable_on: Option<(String, u16)>, ) -> (Self, Arc) { let fetch_control = Arc::new(FetchControl::default()); @@ -169,7 +169,7 @@ impl ContentFetcherHandler { status: FetchState::NotStarted(url), installer: handler, request_url: None, - embeddable_at: embeddable_at, + embeddable_on: embeddable_on, }; (handler, fetch_control) @@ -208,7 +208,7 @@ impl server::Handler for ContentFetcherHandler< "Unable To Start Dapp Download", "Could not initialize download of the dapp. It might be a problem with the remote server.", Some(&format!("{}", e)), - self.embeddable_at, + self.embeddable_on.clone(), )), } }, @@ -218,7 +218,7 @@ impl server::Handler for ContentFetcherHandler< "Method Not Allowed", "Only GET requests are allowed.", None, - self.embeddable_at, + self.embeddable_on.clone(), )), }) } else { None }; @@ -241,7 +241,7 @@ impl server::Handler for ContentFetcherHandler< "Download Timeout", &format!("Could not fetch content within {} seconds.", FETCH_TIMEOUT), None, - self.embeddable_at, + self.embeddable_on.clone(), ); Self::close_client(&mut self.client); (Some(FetchState::Error(timeout)), Next::write()) @@ -263,7 +263,7 @@ impl server::Handler for ContentFetcherHandler< "Invalid Dapp", "Downloaded bundle does not contain a valid content.", Some(&format!("{:?}", e)), - self.embeddable_at, + self.embeddable_on.clone(), )) }, Ok((id, result)) => { @@ -284,7 +284,7 @@ impl server::Handler for ContentFetcherHandler< "Download Error", "There was an error when fetching the content.", Some(&format!("{:?}", e)), - self.embeddable_at, + self.embeddable_on.clone(), ); (Some(FetchState::Error(error)), Next::write()) }, diff --git a/dapps/src/handlers/mod.rs b/dapps/src/handlers/mod.rs index 3d96e8a40..b575509a5 100644 --- a/dapps/src/handlers/mod.rs +++ b/dapps/src/handlers/mod.rs @@ -30,18 +30,18 @@ pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl}; use url::Url; use hyper::{server, header, net, uri}; -use signer_address; +use address; /// Adds security-related headers to the Response. -pub fn add_security_headers(headers: &mut header::Headers, embeddable_at: Option) { +pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Option<(String, u16)>) { headers.set_raw("X-XSS-Protection", vec![b"1; mode=block".to_vec()]); headers.set_raw("X-Content-Type-Options", vec![b"nosniff".to_vec()]); // Embedding header: - if let Some(port) = embeddable_at { + if let Some(embeddable_on) = embeddable_on { headers.set_raw( "X-Frame-Options", - vec![format!("ALLOW-FROM http://{}", signer_address(port)).into_bytes()] + vec![format!("ALLOW-FROM http://{}", address(embeddable_on)).into_bytes()] ); } else { // TODO [ToDr] Should we be more strict here (DENY?)? diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 4394d1183..2c9fa33d1 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -112,7 +112,7 @@ pub struct ServerBuilder { handler: Arc, registrar: Arc, sync_status: Arc, - signer_port: Option, + signer_address: Option<(String, u16)>, } impl Extendable for ServerBuilder { @@ -129,7 +129,7 @@ impl ServerBuilder { handler: Arc::new(IoHandler::new()), registrar: registrar, sync_status: Arc::new(|| false), - signer_port: None, + signer_address: None, } } @@ -139,8 +139,8 @@ impl ServerBuilder { } /// Change default signer port. - pub fn with_signer_port(&mut self, signer_port: Option) { - self.signer_port = signer_port; + pub fn with_signer_address(&mut self, signer_address: Option<(String, u16)>) { + self.signer_address = signer_address; } /// Asynchronously start server with no authentication, @@ -152,7 +152,7 @@ impl ServerBuilder { NoAuth, self.handler.clone(), self.dapps_path.clone(), - self.signer_port.clone(), + self.signer_address.clone(), self.registrar.clone(), self.sync_status.clone(), ) @@ -167,7 +167,7 @@ impl ServerBuilder { HttpBasicAuth::single_user(username, password), self.handler.clone(), self.dapps_path.clone(), - self.signer_port.clone(), + self.signer_address.clone(), self.registrar.clone(), self.sync_status.clone(), ) @@ -197,11 +197,11 @@ impl Server { } /// Returns a list of CORS domains for API endpoint. - fn cors_domains(signer_port: Option) -> Vec { - match signer_port { - Some(port) => vec![ + fn cors_domains(signer_address: Option<(String, u16)>) -> Vec { + match signer_address { + Some(signer_address) => vec![ format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN), - format!("http://{}", signer_address(port)), + format!("http://{}", address(signer_address)), ], None => vec![], } @@ -213,15 +213,15 @@ impl Server { authorization: A, handler: Arc, dapps_path: String, - signer_port: Option, + signer_address: Option<(String, u16)>, registrar: Arc, sync_status: Arc, ) -> Result { let panic_handler = Arc::new(Mutex::new(None)); let authorization = Arc::new(authorization); - let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(apps::urlhint::URLHintContract::new(registrar), sync_status, signer_port)); - let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_port.clone())); - let cors_domains = Self::cors_domains(signer_port); + let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(apps::urlhint::URLHintContract::new(registrar), sync_status, signer_address.clone())); + let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_address.clone())); + let cors_domains = Self::cors_domains(signer_address.clone()); let special = Arc::new({ let mut special = HashMap::new(); @@ -238,7 +238,7 @@ impl Server { try!(hyper::Server::http(addr)) .handle(move |ctrl| router::Router::new( ctrl, - signer_port.clone(), + signer_address.clone(), content_fetcher.clone(), endpoints.clone(), special.clone(), @@ -302,8 +302,8 @@ pub fn random_filename() -> String { rng.gen_ascii_chars().take(12).collect() } -fn signer_address(port: u16) -> String { - format!("127.0.0.1:{}", port) +fn address(address: (String, u16)) -> String { + format!("{}:{}", address.0, address.1) } #[cfg(test)] @@ -332,7 +332,7 @@ mod util_tests { // when let none = Server::cors_domains(None); - let some = Server::cors_domains(Some(18180)); + let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180))); // then assert_eq!(none, Vec::::new()); diff --git a/dapps/src/page/builtin.rs b/dapps/src/page/builtin.rs index bf5bf088f..40c0e23a6 100644 --- a/dapps/src/page/builtin.rs +++ b/dapps/src/page/builtin.rs @@ -25,7 +25,7 @@ pub struct PageEndpoint { /// Prefix to strip from the path (when `None` deducted from `app_id`) pub prefix: Option, /// Safe to be loaded in frame by other origin. (use wisely!) - safe_to_embed_at_port: Option, + safe_to_embed_on: Option<(String, u16)>, info: EndpointInfo, } @@ -36,7 +36,7 @@ impl PageEndpoint { PageEndpoint { app: Arc::new(app), prefix: None, - safe_to_embed_at_port: None, + safe_to_embed_on: None, info: EndpointInfo::from(info), } } @@ -49,7 +49,7 @@ impl PageEndpoint { PageEndpoint { app: Arc::new(app), prefix: Some(prefix), - safe_to_embed_at_port: None, + safe_to_embed_on: None, info: EndpointInfo::from(info), } } @@ -57,12 +57,12 @@ impl PageEndpoint { /// Creates new `PageEndpoint` which can be safely used in iframe /// even from different origin. It might be dangerous (clickjacking). /// Use wisely! - pub fn new_safe_to_embed(app: T, port: Option) -> Self { + pub fn new_safe_to_embed(app: T, address: Option<(String, u16)>) -> Self { let info = app.info(); PageEndpoint { app: Arc::new(app), prefix: None, - safe_to_embed_at_port: port, + safe_to_embed_on: address, info: EndpointInfo::from(info), } } @@ -79,9 +79,9 @@ impl Endpoint for PageEndpoint { app: BuiltinDapp::new(self.app.clone()), prefix: self.prefix.clone(), path: path, - file: handler::ServedFile::new(self.safe_to_embed_at_port.clone()), + file: handler::ServedFile::new(self.safe_to_embed_on.clone()), cache: PageCache::Disabled, - safe_to_embed_at_port: self.safe_to_embed_at_port.clone(), + safe_to_embed_on: self.safe_to_embed_on.clone(), }) } } diff --git a/dapps/src/page/handler.rs b/dapps/src/page/handler.rs index f908a69c5..74eabf917 100644 --- a/dapps/src/page/handler.rs +++ b/dapps/src/page/handler.rs @@ -60,13 +60,13 @@ pub enum ServedFile { } impl ServedFile { - pub fn new(embeddable_at: Option) -> Self { + pub fn new(embeddable_on: Option<(String, u16)>) -> Self { ServedFile::Error(ContentHandler::error( StatusCode::NotFound, "404 Not Found", "Requested dapp resource was not found.", None, - embeddable_at, + embeddable_on, )) } } @@ -97,7 +97,7 @@ pub struct PageHandler { /// Requested path. pub path: EndpointPath, /// Flag indicating if the file can be safely embeded (put in iframe). - pub safe_to_embed_at_port: Option, + pub safe_to_embed_on: Option<(String, u16)>, /// Cache settings for this page. pub cache: PageCache, } @@ -133,7 +133,7 @@ impl server::Handler for PageHandler { self.app.file(&self.extract_path(url.path())) }, _ => None, - }.map_or_else(|| ServedFile::new(self.safe_to_embed_at_port.clone()), |f| ServedFile::File(f)); + }.map_or_else(|| ServedFile::new(self.safe_to_embed_on.clone()), |f| ServedFile::File(f)); Next::write() } @@ -162,7 +162,7 @@ impl server::Handler for PageHandler { } // Security headers: - add_security_headers(&mut res.headers_mut(), self.safe_to_embed_at_port); + add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone()); Next::write() }, ServedFile::Error(ref mut handler) => { @@ -246,7 +246,7 @@ fn should_extract_path_with_appid() { }, file: ServedFile::new(None), cache: Default::default(), - safe_to_embed_at_port: None, + safe_to_embed_on: None, }; // when diff --git a/dapps/src/page/local.rs b/dapps/src/page/local.rs index aa98a68cd..ec24cac36 100644 --- a/dapps/src/page/local.rs +++ b/dapps/src/page/local.rs @@ -27,17 +27,17 @@ pub struct LocalPageEndpoint { mime: Option, info: Option, cache: PageCache, - embeddable_at: Option, + embeddable_on: Option<(String, u16)>, } impl LocalPageEndpoint { - pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_at: Option) -> Self { + pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Option<(String, u16)>) -> Self { LocalPageEndpoint { path: path, mime: None, info: Some(info), cache: cache, - embeddable_at: embeddable_at, + embeddable_on: embeddable_on, } } @@ -47,7 +47,7 @@ impl LocalPageEndpoint { mime: Some(mime), info: None, cache: cache, - embeddable_at: None, + embeddable_on: None, } } @@ -68,7 +68,7 @@ impl Endpoint for LocalPageEndpoint { prefix: None, path: path, file: handler::ServedFile::new(None), - safe_to_embed_at_port: self.embeddable_at, + safe_to_embed_on: self.embeddable_on.clone(), cache: self.cache, }) } else { @@ -77,7 +77,7 @@ impl Endpoint for LocalPageEndpoint { prefix: None, path: path, file: handler::ServedFile::new(None), - safe_to_embed_at_port: self.embeddable_at, + safe_to_embed_on: self.embeddable_on.clone(), cache: self.cache, }) } diff --git a/dapps/src/proxypac.rs b/dapps/src/proxypac.rs index 2d7c4e3ce..88ecb6ab3 100644 --- a/dapps/src/proxypac.rs +++ b/dapps/src/proxypac.rs @@ -19,24 +19,24 @@ use endpoint::{Endpoint, Handler, EndpointPath}; use handlers::ContentHandler; use apps::{HOME_PAGE, DAPPS_DOMAIN}; -use signer_address; +use address; pub struct ProxyPac { - signer_port: Option, + signer_address: Option<(String, u16)>, } impl ProxyPac { - pub fn boxed(signer_port: Option) -> Box { + pub fn boxed(signer_address: Option<(String, u16)>) -> Box { Box::new(ProxyPac { - signer_port: signer_port + signer_address: signer_address }) } } impl Endpoint for ProxyPac { fn to_handler(&self, path: EndpointPath) -> Box { - let signer = self.signer_port - .map(signer_address) + let signer = self.signer_address.clone() + .map(address) .unwrap_or_else(|| format!("{}:{}", path.host, path.port)); let content = format!( diff --git a/dapps/src/router/mod.rs b/dapps/src/router/mod.rs index fe8061ef0..cb9133886 100644 --- a/dapps/src/router/mod.rs +++ b/dapps/src/router/mod.rs @@ -20,7 +20,7 @@ pub mod auth; mod host_validation; -use signer_address; +use address; use std::sync::Arc; use std::collections::HashMap; use url::{Url, Host}; @@ -43,7 +43,7 @@ pub enum SpecialEndpoint { pub struct Router { control: Option, - signer_port: Option, + signer_address: Option<(String, u16)>, endpoints: Arc, fetch: Arc, special: Arc>>, @@ -117,14 +117,14 @@ impl server::Handler for Router { "404 Not Found", "Requested content was not found.", None, - self.signer_port, + self.signer_address.clone(), )) }, // Redirect any other GET request to signer. _ if *req.method() == hyper::Method::Get => { - if let Some(port) = self.signer_port { + if let Some(signer_address) = self.signer_address.clone() { trace!(target: "dapps", "Redirecting to signer interface."); - Redirection::boxed(&format!("http://{}", signer_address(port))) + Redirection::boxed(&format!("http://{}", address(signer_address))) } else { trace!(target: "dapps", "Signer disabled, returning 404."); Box::new(ContentHandler::error( @@ -132,7 +132,7 @@ impl server::Handler for Router { "404 Not Found", "Your homepage is not available when Trusted Signer is disabled.", Some("You can still access dapps by writing a correct address, though. Re-enabled Signer to get your homepage back."), - self.signer_port, + self.signer_address.clone(), )) } }, @@ -168,7 +168,7 @@ impl server::Handler for Router { impl Router { pub fn new( control: Control, - signer_port: Option, + signer_address: Option<(String, u16)>, content_fetcher: Arc, endpoints: Arc, special: Arc>>, @@ -181,7 +181,7 @@ impl Router { .to_handler(EndpointPath::default()); Router { control: Some(control), - signer_port: signer_port, + signer_address: signer_address, endpoints: endpoints, fetch: content_fetcher, special: special, diff --git a/dapps/src/tests/helpers.rs b/dapps/src/tests/helpers.rs index 1fa2e777a..f7c9e8ba6 100644 --- a/dapps/src/tests/helpers.rs +++ b/dapps/src/tests/helpers.rs @@ -76,7 +76,7 @@ pub fn init_server(hosts: Option>, is_syncing: bool) -> (Server, Arc dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading"); let mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar.clone()); builder.with_sync_status(Arc::new(move || is_syncing)); - builder.with_signer_port(Some(SIGNER_PORT)); + builder.with_signer_address(Some(("127.0.0.1".into(), SIGNER_PORT))); ( builder.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), hosts).unwrap(), registrar, @@ -89,7 +89,7 @@ pub fn serve_with_auth(user: &str, pass: &str) -> Server { let mut dapps_path = env::temp_dir(); dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading"); let mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar); - builder.with_signer_port(Some(SIGNER_PORT)); + builder.with_signer_address(Some(("127.0.0.1".into(), SIGNER_PORT))); builder.start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), None, user, pass).unwrap() } diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index f1739f848..5999c9d67 100644 --- a/js/src/api/rpc/parity/parity.js +++ b/js/src/api/rpc/parity/parity.js @@ -60,6 +60,11 @@ export default class Parity { .then(outNumber); } + dappsInterface () { + return this._transport + .execute('parity_dappsInterface'); + } + defaultExtraData () { return this._transport .execute('parity_defaultExtraData'); diff --git a/js/src/jsonrpc/interfaces/parity.js b/js/src/jsonrpc/interfaces/parity.js index 66a8ea962..883ad9675 100644 --- a/js/src/jsonrpc/interfaces/parity.js +++ b/js/src/jsonrpc/interfaces/parity.js @@ -109,6 +109,15 @@ export default { } }, + dappsInterface: { + desc: 'Returns the interface the dapps are running on, error if not enabled', + params: [], + returns: { + type: String, + desc: 'The interface' + } + }, + defaultExtraData: { desc: 'Returns the default extra data', params: [], diff --git a/parity/configuration.rs b/parity/configuration.rs index 1d46dda18..75d319272 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -95,7 +95,7 @@ impl Configuration { let wal = !self.args.flag_fast_and_loose; let warp_sync = self.args.flag_warp; let geth_compatibility = self.args.flag_geth; - let ui_port = self.ui_port(); + let ui_address = self.ui_port().map(|port| (self.ui_interface(), port)); let dapps_conf = self.dapps_config(); let signer_conf = self.signer_config(); let format = try!(self.format()); @@ -243,7 +243,7 @@ impl Configuration { vm_type: vm_type, warp_sync: warp_sync, geth_compatibility: geth_compatibility, - ui_port: ui_port, + ui_address: ui_address, net_settings: self.network_settings(), dapps_conf: dapps_conf, signer_conf: signer_conf, @@ -859,7 +859,7 @@ mod tests { wal: true, vm_type: Default::default(), geth_compatibility: false, - ui_port: Some(8180), + ui_address: Some(("127.0.0.1".into(), 8180)), net_settings: Default::default(), dapps_conf: Default::default(), signer_conf: Default::default(), diff --git a/parity/dapps.rs b/parity/dapps.rs index 6ef64c2fd..80f2f7060 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -58,7 +58,7 @@ pub fn new(configuration: Configuration, deps: Dependencies) -> Result Result>, _auth: Option<(String, String)>, - _signer_port: Option, + _signer_address: Option<(String, u16)>, ) -> Result { Err("Your Parity version has been compiled without WebApps support.".into()) } @@ -120,7 +120,7 @@ mod server { url: &SocketAddr, allowed_hosts: Option>, auth: Option<(String, String)>, - signer_port: Option, + signer_address: Option<(String, u16)>, ) -> Result { use ethcore_dapps as dapps; @@ -131,7 +131,7 @@ mod server { let sync = deps.sync.clone(); let client = deps.client.clone(); server.with_sync_status(Arc::new(move || is_major_importing(Some(sync.status().state), client.queue_info()))); - server.with_signer_port(signer_port); + server.with_signer_address(signer_address); let server = rpc_apis::setup_rpc(server, deps.apis.clone(), rpc_apis::ApiSet::UnsafeContext); let start_result = match auth { diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index 9ffd8e0dd..2d375fdde 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -37,7 +37,7 @@ pub enum Api { Net, /// Eth (Safe) Eth, - /// Geth-compatible "personal" API (DEPRECATED; only used in `--geth` mode.) + /// Geth-compatible "personal" API (DEPRECATED; only used in `--geth` mode.) Personal, /// Signer - Confirm transactions in Signer (UNSAFE: Passwords, List of transactions) Signer, @@ -119,6 +119,7 @@ pub struct Dependencies { pub settings: Arc, pub net_service: Arc, pub geth_compatibility: bool, + pub dapps_interface: Option, pub dapps_port: Option, } @@ -228,6 +229,7 @@ pub fn setup_rpc(server: T, deps: Arc, apis: ApiSet deps.logger.clone(), deps.settings.clone(), signer, + deps.dapps_interface.clone(), deps.dapps_port, ).to_delegate()); diff --git a/parity/run.rs b/parity/run.rs index 2af0d35ca..56ff92c25 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -82,7 +82,7 @@ pub struct RunCmd { pub wal: bool, pub vm_type: VMType, pub geth_compatibility: bool, - pub ui_port: Option, + pub ui_address: Option<(String, u16)>, pub net_settings: NetworkSettings, pub dapps_conf: dapps::Configuration, pub signer_conf: signer::Configuration, @@ -262,7 +262,7 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { let deps_for_rpc_apis = Arc::new(rpc_apis::Dependencies { signer_service: Arc::new(rpc_apis::SignerService::new(move || { signer::generate_new_token(signer_path.clone()).map_err(|e| format!("{:?}", e)) - }, cmd.ui_port)), + }, cmd.ui_address)), snapshot: snapshot_service.clone(), client: client.clone(), sync: sync_provider.clone(), @@ -274,6 +274,10 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { settings: Arc::new(cmd.net_settings.clone()), net_service: manage_network.clone(), geth_compatibility: cmd.geth_compatibility, + dapps_interface: match cmd.dapps_conf.enabled { + true => Some(cmd.dapps_conf.interface.clone()), + false => None, + }, dapps_port: match cmd.dapps_conf.enabled { true => Some(cmd.dapps_conf.port), false => None, diff --git a/rpc/src/v1/helpers/signer.rs b/rpc/src/v1/helpers/signer.rs index d4a5af273..11f8e3376 100644 --- a/rpc/src/v1/helpers/signer.rs +++ b/rpc/src/v1/helpers/signer.rs @@ -22,18 +22,18 @@ use v1::helpers::signing_queue::{ConfirmationsQueue}; pub struct SignerService { queue: Arc, generate_new_token: Box Result + Send + Sync + 'static>, - port: Option, + address: Option<(String, u16)>, } impl SignerService { /// Creates new Signer Service given function to generate new tokens. - pub fn new(new_token: F, port: Option) -> Self + pub fn new(new_token: F, address: Option<(String, u16)>) -> Self where F: Fn() -> Result + Send + Sync + 'static { SignerService { queue: Arc::new(ConfirmationsQueue::default()), generate_new_token: Box::new(new_token), - port: port, + address: address, } } @@ -47,20 +47,20 @@ impl SignerService { self.queue.clone() } - /// Returns signer port (if signer enabled) or `None` otherwise - pub fn port(&self) -> Option { - self.port + /// Returns signer address (if signer enabled) or `None` otherwise + pub fn address(&self) -> Option<(String, u16)> { + self.address.clone() } /// Returns true if Signer is enabled. pub fn is_enabled(&self) -> bool { - self.port.is_some() + self.address.is_some() } #[cfg(test)] /// Creates new Signer Service for tests. - pub fn new_test(port: Option) -> Self { - SignerService::new(|| Ok("new_token".into()), port) + pub fn new_test(address: Option<(String, u16)>) -> Self { + SignerService::new(|| Ok("new_token".into()), address) } } diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index b9c19f667..1b8ee9695 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -52,6 +52,7 @@ pub struct ParityClient where logger: Arc, settings: Arc, signer: Option>, + dapps_interface: Option, dapps_port: Option, } @@ -70,6 +71,7 @@ impl ParityClient where logger: Arc, settings: Arc, signer: Option>, + dapps_interface: Option, dapps_port: Option, ) -> Self { ParityClient { @@ -81,6 +83,7 @@ impl ParityClient where logger: logger, settings: settings, signer: signer, + dapps_interface: dapps_interface, dapps_port: dapps_port, } } @@ -261,7 +264,8 @@ impl Parity for ParityClient where self.signer .clone() - .and_then(|signer| signer.port()) + .and_then(|signer| signer.address()) + .map(|address| address.1) .ok_or_else(|| errors::signer_disabled()) } @@ -272,6 +276,13 @@ impl Parity for ParityClient where .ok_or_else(|| errors::dapps_disabled()) } + fn dapps_interface(&self) -> Result { + try!(self.active()); + + self.dapps_interface.clone() + .ok_or_else(|| errors::dapps_disabled()) + } + fn next_nonce(&self, address: H160) -> Result { try!(self.active()); let address: Address = address.into(); diff --git a/rpc/src/v1/tests/mocked/parity.rs b/rpc/src/v1/tests/mocked/parity.rs index 8baecbd90..b5c8187c7 100644 --- a/rpc/src/v1/tests/mocked/parity.rs +++ b/rpc/src/v1/tests/mocked/parity.rs @@ -38,6 +38,7 @@ pub struct Dependencies { pub settings: Arc, pub network: Arc, pub accounts: Arc, + pub dapps_interface: Option, pub dapps_port: Option, } @@ -61,6 +62,7 @@ impl Dependencies { }), network: Arc::new(TestManageNetwork), accounts: Arc::new(AccountProvider::transient_provider()), + dapps_interface: Some("127.0.0.1".into()), dapps_port: Some(18080), } } @@ -75,6 +77,7 @@ impl Dependencies { self.logger.clone(), self.settings.clone(), signer, + self.dapps_interface.clone(), self.dapps_port, ) } @@ -238,7 +241,7 @@ fn rpc_parity_node_name() { #[test] fn rpc_parity_unsigned_transactions_count() { let deps = Dependencies::new(); - let io = deps.with_signer(SignerService::new_test(Some(18180))); + let io = deps.with_signer(SignerService::new_test(Some(("127.0.0.1".into(), 18180)))); let request = r#"{"jsonrpc": "2.0", "method": "parity_unsignedTransactionsCount", "params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":0,"id":1}"#; @@ -282,7 +285,7 @@ fn rpc_parity_encrypt() { fn rpc_parity_signer_port() { // given let deps = Dependencies::new(); - let io1 = deps.with_signer(SignerService::new_test(Some(18180))); + let io1 = deps.with_signer(SignerService::new_test(Some(("127.0.0.1".into(), 18180)))); let io2 = deps.default_client(); // when @@ -313,6 +316,24 @@ fn rpc_parity_dapps_port() { assert_eq!(io2.handle_request_sync(request), Some(response2.to_owned())); } +#[test] +fn rpc_parity_dapps_interface() { + // given + let mut deps = Dependencies::new(); + let io1 = deps.default_client(); + deps.dapps_interface = None; + let io2 = deps.default_client(); + + // when + let request = r#"{"jsonrpc": "2.0", "method": "parity_dappsInterface", "params": [], "id": 1}"#; + let response1 = r#"{"jsonrpc":"2.0","result":"127.0.0.1","id":1}"#; + let response2 = r#"{"jsonrpc":"2.0","error":{"code":-32031,"message":"Dapps Server is disabled. This API is not available.","data":null},"id":1}"#; + + // then + assert_eq!(io1.handle_request_sync(request), Some(response1.to_owned())); + assert_eq!(io2.handle_request_sync(request), Some(response2.to_owned())); +} + #[test] fn rpc_parity_next_nonce() { let deps = Dependencies::new(); diff --git a/rpc/src/v1/traits/parity.rs b/rpc/src/v1/traits/parity.rs index 23cf50ed3..f8c219a89 100644 --- a/rpc/src/v1/traits/parity.rs +++ b/rpc/src/v1/traits/parity.rs @@ -123,6 +123,10 @@ build_rpc_trait! { #[rpc(name = "parity_dappsPort")] fn dapps_port(&self) -> Result; + /// Returns current Dapps Server interface address or an error if dapps server is disabled. + #[rpc(name = "parity_dappsInterface")] + fn dapps_interface(&self) -> Result; + /// Returns next nonce for particular sender. Should include all transactions in the queue. #[rpc(name = "parity_nextNonce")] fn next_nonce(&self, H160) -> Result; From 90ff810e367c438d3c2943e60eaa849307af9ce3 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 9 Nov 2016 20:05:08 +0100 Subject: [PATCH 18/55] Handle redirects from /api/content on manifest.json gracefully (#3315) * Add redirect follow mode to manifest.json * Remove (now) unused count parameter * autoRewrite: true for dev mode proxy redirects --- js/src/views/Dapps/dappsStore.js | 23 +++++++---------------- js/webpack.config.js | 3 ++- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/js/src/views/Dapps/dappsStore.js b/js/src/views/Dapps/dappsStore.js index 01877c38b..6a00ff059 100644 --- a/js/src/views/Dapps/dappsStore.js +++ b/js/src/views/Dapps/dappsStore.js @@ -197,24 +197,15 @@ export default class DappsStore { }); } - _fetchManifest (manifestHash, count = 0) { - return fetch(`${this._getHost()}/api/content/${manifestHash}/`) + _fetchManifest (manifestHash) { + return fetch(`${this._getHost()}/api/content/${manifestHash}/`, { redirect: 'follow', mode: 'cors' }) .then((response) => { - if (response.ok) { - return response.json(); - } - - if (count < 1) { - return this._fetchManifest(manifestHash, count + 1); - } - - return null; + return response.ok + ? response.json() + : null; }) - .catch(() => { - if (count < 1) { - return this._fetchManifest(manifestHash, count + 1); - } - + .catch((error) => { + console.warn('DappsStore:fetchManifest', error); return null; }); } diff --git a/js/webpack.config.js b/js/webpack.config.js index a85fefa9f..4413299fa 100644 --- a/js/webpack.config.js +++ b/js/webpack.config.js @@ -198,7 +198,8 @@ module.exports = { proxy: { '/api/*': { target: 'http://127.0.0.1:8080', - changeOrigin: true + changeOrigin: true, + autoRewrite: true }, '/app/*': { target: 'http://127.0.0.1:8080', From 2f98169539bb7645a5d21b0180bfe00784b20f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 10 Nov 2016 11:27:05 +0100 Subject: [PATCH 19/55] In-browser signing support (#3231) * Signer RAW confirmations * Returning address book as eth_accounts * UI support for in-browser signing * Post review fixes * Adding new methods to jsonrpc * Fixing eth_accounts * Deterministic accounts ordering --- ethcore/src/account_provider.rs | 12 ++- js/package.json | 2 + js/src/api/format/input.js | 4 + js/src/api/rpc/parity/parity.js | 6 ++ js/src/api/rpc/signer/signer.js | 7 +- js/src/jsonrpc/interfaces/parity.js | 14 +++ js/src/jsonrpc/interfaces/signer.js | 20 +++- js/src/redux/providers/signerMiddleware.js | 66 ++++++++++--- js/src/util/wallet.js | 82 +++++++++++++++ .../RequestPendingWeb3/RequestPendingWeb3.js | 13 ++- .../TransactionPending/TransactionPending.css | 2 +- .../TransactionPending/TransactionPending.js | 6 +- .../TransactionPendingFormConfirm.css | 4 + .../TransactionPendingFormConfirm.js | 56 +++++++++-- js/webpack.vendor.js | 1 + rpc/src/v1/helpers/errors.rs | 6 +- rpc/src/v1/impls/eth.rs | 6 +- rpc/src/v1/impls/signer.rs | 69 +++++++++++-- rpc/src/v1/tests/mocked/eth.rs | 9 +- rpc/src/v1/tests/mocked/signer.rs | 99 ++++++++++++++++++- rpc/src/v1/traits/signer.rs | 6 +- 21 files changed, 447 insertions(+), 43 deletions(-) create mode 100644 js/src/util/wallet.js diff --git a/ethcore/src/account_provider.rs b/ethcore/src/account_provider.rs index 064c3e935..e906aefe9 100644 --- a/ethcore/src/account_provider.rs +++ b/ethcore/src/account_provider.rs @@ -95,6 +95,7 @@ impl KeyDirectory for NullDir { struct AddressBook { path: PathBuf, cache: HashMap, + transient: bool, } impl AddressBook { @@ -106,11 +107,18 @@ impl AddressBook { let mut r = AddressBook { path: path, cache: HashMap::new(), + transient: false, }; r.revert(); r } + pub fn transient() -> Self { + let mut book = AddressBook::new(Default::default()); + book.transient = true; + book + } + pub fn get(&self) -> HashMap { self.cache.clone() } @@ -134,6 +142,7 @@ impl AddressBook { } fn revert(&mut self) { + if self.transient { return; } trace!(target: "addressbook", "revert"); let _ = fs::File::open(self.path.clone()) .map_err(|e| trace!(target: "addressbook", "Couldn't open address book: {}", e)) @@ -144,6 +153,7 @@ impl AddressBook { } fn save(&mut self) { + if self.transient { return; } trace!(target: "addressbook", "save"); let _ = fs::File::create(self.path.clone()) .map_err(|e| warn!(target: "addressbook", "Couldn't open address book for writing: {}", e)) @@ -175,7 +185,7 @@ impl AccountProvider { pub fn transient_provider() -> Self { AccountProvider { unlocked: Mutex::new(HashMap::new()), - address_book: Mutex::new(AddressBook::new(Default::default())), + address_book: Mutex::new(AddressBook::transient()), sstore: Box::new(EthStore::open(Box::new(NullDir::default())) .expect("NullDir load always succeeds; qed")) } diff --git a/js/package.json b/js/package.json index df9e37811..eb0926c4b 100644 --- a/js/package.json +++ b/js/package.json @@ -118,6 +118,7 @@ "bytes": "^2.4.0", "chart.js": "^2.3.0", "es6-promise": "^3.2.1", + "ethereumjs-tx": "^1.1.2", "file-saver": "^1.3.3", "format-json": "^1.0.3", "format-number": "^2.0.1", @@ -147,6 +148,7 @@ "redux-actions": "^0.10.1", "redux-thunk": "^2.1.0", "rlp": "^2.0.0", + "scryptsy": "^2.0.0", "store": "^1.3.20", "utf8": "^2.1.1", "validator": "^5.7.0", diff --git a/js/src/api/format/input.js b/js/src/api/format/input.js index b46148cdc..830ca0e21 100644 --- a/js/src/api/format/input.js +++ b/js/src/api/format/input.js @@ -93,6 +93,10 @@ export function inFilter (options) { } export function inHex (str) { + if (str && str.toString) { + str = str.toString(16); + } + if (str && str.substr(0, 2) === '0x') { return str.toLowerCase(); } diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index 5999c9d67..a33828b80 100644 --- a/js/src/api/rpc/parity/parity.js +++ b/js/src/api/rpc/parity/parity.js @@ -181,6 +181,12 @@ export default class Parity { .then(outAddress); } + nextNonce (account) { + return this._transport + .execute('parity_nextNonce', inAddress(account)) + .then(outNumber); + } + nodeName () { return this._transport .execute('parity_nodeName'); diff --git a/js/src/api/rpc/signer/signer.js b/js/src/api/rpc/signer/signer.js index 7f905cf50..126ce651a 100644 --- a/js/src/api/rpc/signer/signer.js +++ b/js/src/api/rpc/signer/signer.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { inNumber16 } from '../../format/input'; +import { inNumber16, inData } from '../../format/input'; import { outSignerRequest } from '../../format/output'; export default class Signer { @@ -27,6 +27,11 @@ export default class Signer { .execute('signer_confirmRequest', inNumber16(requestId), options, password); } + confirmRequestRaw (requestId, data) { + return this._transport + .execute('signer_confirmRequestRaw', inNumber16(requestId), inData(data)); + } + generateAuthorizationToken () { return this._transport .execute('signer_generateAuthorizationToken'); diff --git a/js/src/jsonrpc/interfaces/parity.js b/js/src/jsonrpc/interfaces/parity.js index 883ad9675..5dd313e00 100644 --- a/js/src/jsonrpc/interfaces/parity.js +++ b/js/src/jsonrpc/interfaces/parity.js @@ -356,6 +356,20 @@ export default { } }, + nextNonce: { + desc: 'Returns next available nonce for transaction from given account. Includes pending block and transaction queue.', + params: [ + { + type: Address, + desc: 'Account' + } + ], + returns: { + type: Quantity, + desc: 'Next valid nonce' + } + }, + nodeName: { desc: 'Returns node name (identity)', params: [], diff --git a/js/src/jsonrpc/interfaces/signer.js b/js/src/jsonrpc/interfaces/signer.js index f394dbb61..f50bb1115 100644 --- a/js/src/jsonrpc/interfaces/signer.js +++ b/js/src/jsonrpc/interfaces/signer.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { Quantity } from '../types'; +import { Quantity, Data } from '../types'; export default { generateAuthorizationToken: { @@ -57,6 +57,24 @@ export default { } }, + confirmRequestRaw: { + desc: 'Confirm a request in the signer queue providing signed request.', + params: [ + { + type: Quantity, + desc: 'The request id' + }, + { + type: Data, + desc: 'Signed request (transaction RLP)' + } + ], + returns: { + type: Boolean, + desc: 'The status of the confirmation' + } + }, + rejectRequest: { desc: 'Rejects a request in the signer queue', params: [ diff --git a/js/src/redux/providers/signerMiddleware.js b/js/src/redux/providers/signerMiddleware.js index 6d09eeb4e..4cc877ced 100644 --- a/js/src/redux/providers/signerMiddleware.js +++ b/js/src/redux/providers/signerMiddleware.js @@ -16,6 +16,9 @@ import * as actions from './signerActions'; +import { inHex } from '../../api/format/input'; +import { Wallet } from '../../util/wallet'; + export default class SignerMiddleware { constructor (api) { this._api = api; @@ -49,23 +52,58 @@ export default class SignerMiddleware { } onConfirmStart = (store, action) => { - const { id, password } = action.payload; + const { id, password, wallet, payload } = action.payload; - this._api.signer - .confirmRequest(id, {}, password) - .then((txHash) => { - console.log('confirmRequest', id, txHash); - if (!txHash) { - store.dispatch(actions.errorConfirmRequest({ id, err: 'Unable to confirm.' })); - return; + const handlePromise = promise => { + promise + .then((txHash) => { + console.log('confirmRequest', id, txHash); + if (!txHash) { + store.dispatch(actions.errorConfirmRequest({ id, err: 'Unable to confirm.' })); + return; + } + + store.dispatch(actions.successConfirmRequest({ id, txHash })); + }) + .catch((error) => { + console.error('confirmRequest', id, error); + store.dispatch(actions.errorConfirmRequest({ id, err: error.message })); + }); + }; + + // Sign request in-browser + if (wallet && payload.transaction) { + const { transaction } = payload; + + (transaction.nonce.isZero() + ? this._api.parity.nextNonce(transaction.from) + : Promise.resolve(transaction.nonce) + ).then(nonce => { + let txData = { + to: inHex(transaction.to), + nonce: inHex(transaction.nonce.isZero() ? nonce : transaction.nonce), + gasPrice: inHex(transaction.gasPrice), + gasLimit: inHex(transaction.gas), + value: inHex(transaction.value), + data: inHex(transaction.data) + }; + + try { + // NOTE: Derving the key takes significant amount of time, + // make sure to display some kind of "in-progress" state. + const signer = Wallet.fromJson(wallet, password); + const rawTx = signer.signTransaction(txData); + + handlePromise(this._api.signer.confirmRequestRaw(id, rawTx)); + } catch (error) { + console.error(error); + store.dispatch(actions.errorConfirmRequest({ id, err: error.message })); } - - store.dispatch(actions.successConfirmRequest({ id, txHash })); - }) - .catch((error) => { - console.error('confirmRequest', id, error); - store.dispatch(actions.errorConfirmRequest({ id, err: error.message })); }); + return; + } + + handlePromise(this._api.signer.confirmRequest(id, {}, password)); } onRejectStart = (store, action) => { diff --git a/js/src/util/wallet.js b/js/src/util/wallet.js new file mode 100644 index 000000000..14c3a6016 --- /dev/null +++ b/js/src/util/wallet.js @@ -0,0 +1,82 @@ +// 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 scrypt from 'scryptsy'; +import Transaction from 'ethereumjs-tx'; +import { pbkdf2Sync } from 'crypto'; +import { createDecipheriv } from 'browserify-aes'; + +import { inHex } from '../api/format/input'; +import { sha3 } from '../api/util/sha3'; + +// Adapted from https://github.com/kvhnuke/etherwallet/blob/mercury/app/scripts/myetherwallet.js + +export class Wallet { + + static fromJson (json, password) { + if (json.version !== 3) { + throw new Error('Only V3 wallets are supported'); + } + + const { kdf } = json.crypto; + const kdfparams = json.crypto.kdfparams || {}; + const pwd = Buffer.from(password); + const salt = Buffer.from(kdfparams.salt, 'hex'); + let derivedKey; + + if (kdf === 'scrypt') { + derivedKey = scrypt(pwd, salt, kdfparams.n, kdfparams.r, kdfparams.p, kdfparams.dklen); + } else if (kdf === 'pbkdf2') { + if (kdfparams.prf !== 'hmac-sha256') { + throw new Error('Unsupported parameters to PBKDF2'); + } + derivedKey = pbkdf2Sync(pwd, salt, kdfparams.c, kdfparams.dklen, 'sha256'); + } else { + throw new Error('Unsupported key derivation scheme'); + } + + const ciphertext = Buffer.from(json.crypto.ciphertext, 'hex'); + let mac = sha3(Buffer.concat([derivedKey.slice(16, 32), ciphertext])); + if (mac !== inHex(json.crypto.mac)) { + throw new Error('Key derivation failed - possibly wrong passphrase'); + } + + const decipher = createDecipheriv( + json.crypto.cipher, + derivedKey.slice(0, 16), + Buffer.from(json.crypto.cipherparams.iv, 'hex') + ); + let seed = Buffer.concat([decipher.update(ciphertext), decipher.final()]); + + while (seed.length < 32) { + const nullBuff = Buffer.from([0x00]); + seed = Buffer.concat([nullBuff, seed]); + } + + return new Wallet(seed); + } + + constructor (seed) { + this.seed = seed; + } + + signTransaction (transaction) { + const tx = new Transaction(transaction); + tx.sign(this.seed); + return inHex(tx.serialize().toString('hex')); + } + +} diff --git a/js/src/views/Signer/components/RequestPendingWeb3/RequestPendingWeb3.js b/js/src/views/Signer/components/RequestPendingWeb3/RequestPendingWeb3.js index 4014f328b..97fa43f69 100644 --- a/js/src/views/Signer/components/RequestPendingWeb3/RequestPendingWeb3.js +++ b/js/src/views/Signer/components/RequestPendingWeb3/RequestPendingWeb3.js @@ -33,15 +33,22 @@ export default class RequestPendingWeb3 extends Component { className: PropTypes.string }; + onConfirm = data => { + const { onConfirm, payload } = this.props; + + data.payload = payload; + onConfirm(data); + }; + render () { - const { payload, id, className, isSending, date, onConfirm, onReject } = this.props; + const { payload, id, className, isSending, date, onReject } = this.props; if (payload.sign) { const { sign } = payload; return ( * { vertical-align: middle; - min-height: 120px; + min-height: 190px; } .inputs { diff --git a/js/src/views/Signer/components/TransactionPending/TransactionPending.js b/js/src/views/Signer/components/TransactionPending/TransactionPending.js index 77d02d0b1..55e4f6405 100644 --- a/js/src/views/Signer/components/TransactionPending/TransactionPending.js +++ b/js/src/views/Signer/components/TransactionPending/TransactionPending.js @@ -116,9 +116,11 @@ export default class TransactionPending extends Component { ); } - onConfirm = password => { + onConfirm = data => { const { id, gasPrice } = this.props; - this.props.onConfirm({ id, password, gasPrice }); + const { password, wallet } = data; + + this.props.onConfirm({ id, password, wallet, gasPrice }); } onReject = () => { diff --git a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/TransactionPendingFormConfirm.css b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/TransactionPendingFormConfirm.css index 673b045d2..d10e634ae 100644 --- a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/TransactionPendingFormConfirm.css +++ b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/TransactionPendingFormConfirm.css @@ -40,3 +40,7 @@ .passwordHint span { opacity: 0.85; } + +.fileInput input { + top: 22px; +} diff --git a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/TransactionPendingFormConfirm.js b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/TransactionPendingFormConfirm.js index 5b54586a4..5765447ee 100644 --- a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/TransactionPendingFormConfirm.js +++ b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/TransactionPendingFormConfirm.js @@ -35,26 +35,33 @@ class TransactionPendingFormConfirm extends Component { id = Math.random(); // for tooltip state = { + walletError: null, + wallet: null, password: '' } render () { const { accounts, address, isSending } = this.props; - const { password } = this.state; + const { password, walletError, wallet } = this.state; const account = accounts[address] || {}; + const isExternal = !account.uuid; const passwordHint = account.meta && account.meta.passwordHint ? (
(hint) { account.meta.passwordHint }
) : null; + const isWalletOk = !isExternal || (walletError === null && wallet !== null); + const keyInput = isExternal ? this.renderKeyInput() : null; + return (
+ { keyInput }
@@ -71,7 +78,7 @@ class TransactionPendingFormConfirm extends Component { className={ styles.confirmButton } fullWidth primary - disabled={ isSending } + disabled={ isSending || !isWalletOk } icon={ } label={ isSending ? 'Confirming...' : 'Confirm Transaction' } /> @@ -82,6 +89,20 @@ class TransactionPendingFormConfirm extends Component { ); } + renderKeyInput () { + const { walletError } = this.state; + + return ( + + ); + } + renderTooltip () { if (this.state.password.length) { return; @@ -94,6 +115,26 @@ class TransactionPendingFormConfirm extends Component { ); } + onKeySelect = evt => { + const fileReader = new FileReader(); + fileReader.onload = e => { + try { + const wallet = JSON.parse(e.target.result); + this.setState({ + walletError: null, + wallet: wallet + }); + } catch (e) { + this.setState({ + walletError: 'Given wallet file is invalid.', + wallet: null + }); + } + }; + + fileReader.readAsText(evt.target.files[0]); + } + onModifyPassword = evt => { const password = evt.target.value; this.setState({ @@ -102,8 +143,11 @@ class TransactionPendingFormConfirm extends Component { } onConfirm = () => { - const { password } = this.state; - this.props.onConfirm(password); + const { password, wallet } = this.state; + + this.props.onConfirm({ + password, wallet + }); } onKeyDown = evt => { diff --git a/js/webpack.vendor.js b/js/webpack.vendor.js index a896cbfd8..74a51dada 100644 --- a/js/webpack.vendor.js +++ b/js/webpack.vendor.js @@ -22,6 +22,7 @@ const DEST = process.env.BUILD_DEST || '.build'; let modules = [ 'babel-polyfill', + 'browserify-aes', 'ethereumjs-tx', 'scryptsy', 'react', 'react-dom', 'react-redux', 'react-router', 'redux', 'redux-thunk', 'react-router-redux', 'lodash', 'material-ui', 'moment', 'blockies' diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index 50c22b187..d36feca4b 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -17,7 +17,7 @@ //! RPC Error codes and error objects macro_rules! rpc_unimplemented { - () => (Err(::v1::helpers::errors::unimplemented())) + () => (Err(::v1::helpers::errors::unimplemented(None))) } use std::fmt; @@ -51,11 +51,11 @@ mod codes { pub const FETCH_ERROR: i64 = -32060; } -pub fn unimplemented() -> Error { +pub fn unimplemented(details: Option) -> Error { Error { code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST), message: "This request is not implemented yet. Please create an issue on Github repo.".into(), - data: None + data: details.map(Value::String), } } diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 6986cf0ed..8207426ba 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -20,6 +20,7 @@ extern crate ethash; use std::io::{Write}; use std::process::{Command, Stdio}; +use std::collections::BTreeSet; use std::thread; use std::time::{Instant, Duration}; use std::sync::{Arc, Weak}; @@ -339,7 +340,10 @@ impl Eth for EthClient where let store = take_weak!(self.accounts); let accounts = try!(store.accounts().map_err(|e| errors::internal("Could not fetch accounts.", e))); - Ok(accounts.into_iter().map(Into::into).collect()) + let addresses = try!(store.addresses_info().map_err(|e| errors::internal("Could not fetch accounts.", e))); + + let set: BTreeSet
= accounts.into_iter().chain(addresses.keys().cloned()).collect(); + Ok(set.into_iter().map(Into::into).collect()) } fn block_number(&self) -> Result { diff --git a/rpc/src/v1/impls/signer.rs b/rpc/src/v1/impls/signer.rs index 0ee06b5c5..66f46ba01 100644 --- a/rpc/src/v1/impls/signer.rs +++ b/rpc/src/v1/impls/signer.rs @@ -18,14 +18,17 @@ use std::sync::{Arc, Weak}; -use jsonrpc_core::*; +use rlp::{UntrustedRlp, View}; use ethcore::account_provider::AccountProvider; use ethcore::client::MiningBlockChainClient; +use ethcore::transaction::SignedTransaction; use ethcore::miner::MinerService; + +use jsonrpc_core::Error; use v1::traits::Signer; -use v1::types::{TransactionModification, ConfirmationRequest, ConfirmationResponse, U256}; +use v1::types::{TransactionModification, ConfirmationRequest, ConfirmationResponse, U256, Bytes}; use v1::helpers::{errors, SignerService, SigningQueue, ConfirmationPayload}; -use v1::helpers::dispatch; +use v1::helpers::dispatch::{self, dispatch_transaction}; /// Transactions confirmation (personal) rpc implementation. pub struct SignerClient where C: MiningBlockChainClient, M: MinerService { @@ -66,9 +69,9 @@ impl Signer for SignerClient where C: MiningBlockC let signer = take_weak!(self.signer); Ok(signer.requests() - .into_iter() - .map(Into::into) - .collect() + .into_iter() + .map(Into::into) + .collect() ) } @@ -101,6 +104,60 @@ impl Signer for SignerClient where C: MiningBlockC }).unwrap_or_else(|| Err(errors::invalid_params("Unknown RequestID", id))) } + fn confirm_request_raw(&self, id: U256, bytes: Bytes) -> Result { + try!(self.active()); + + let id = id.into(); + let signer = take_weak!(self.signer); + let client = take_weak!(self.client); + let miner = take_weak!(self.miner); + + signer.peek(&id).map(|confirmation| { + let result = match confirmation.payload { + ConfirmationPayload::SendTransaction(request) => { + let signed_transaction: SignedTransaction = try!( + UntrustedRlp::new(&bytes.0).as_val().map_err(errors::from_rlp_error) + ); + let sender = try!( + signed_transaction.sender().map_err(|e| errors::invalid_params("Invalid signature.", e)) + ); + + // Verification + let sender_matches = sender == request.from; + let data_matches = signed_transaction.data == request.data; + let value_matches = signed_transaction.value == request.value; + let nonce_matches = match request.nonce { + Some(nonce) => signed_transaction.nonce == nonce, + None => true, + }; + + // Dispatch if everything is ok + if sender_matches && data_matches && value_matches && nonce_matches { + dispatch_transaction(&*client, &*miner, signed_transaction) + .map(Into::into) + .map(ConfirmationResponse::SendTransaction) + } else { + let mut error = Vec::new(); + if !sender_matches { error.push("from") } + if !data_matches { error.push("data") } + if !value_matches { error.push("value") } + if !nonce_matches { error.push("nonce") } + + Err(errors::invalid_params("Sent transaction does not match the request.", error)) + } + }, + // TODO [ToDr]: + // 1. Sign - verify signature + // 2. Decrypt - pass through? + _ => Err(errors::unimplemented(Some("Non-transaction requests does not support RAW signing yet.".into()))), + }; + if let Ok(ref response) = result { + signer.request_confirmed(id, Ok(response.clone())); + } + result + }).unwrap_or_else(|| Err(errors::invalid_params("Unknown RequestID", id))) + } + fn reject_request(&self, id: U256) -> Result { try!(self.active()); let signer = take_weak!(self.signer); diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index 7119de2c1..67e77a6db 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -353,9 +353,16 @@ fn rpc_eth_gas_price() { fn rpc_eth_accounts() { let tester = EthTester::default(); let address = tester.accounts_provider.new_account("").unwrap(); + let address2 = Address::default(); + + tester.accounts_provider.set_address_name(address2, "Test Account".into()).unwrap(); let request = r#"{"jsonrpc": "2.0", "method": "eth_accounts", "params": [], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","result":[""#.to_owned() + &format!("0x{:?}", address) + r#""],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":[""#.to_owned() + + &format!("0x{:?}", address2) + + r#"",""# + + &format!("0x{:?}", address) + + r#""],"id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); } diff --git a/rpc/src/v1/tests/mocked/signer.rs b/rpc/src/v1/tests/mocked/signer.rs index 8807e2373..e2ba580e0 100644 --- a/rpc/src/v1/tests/mocked/signer.rs +++ b/rpc/src/v1/tests/mocked/signer.rs @@ -16,11 +16,14 @@ use std::sync::Arc; use std::str::FromStr; -use jsonrpc_core::IoHandler; -use util::{U256, Uint, Address}; +use util::{U256, Uint, Address, ToPretty}; + use ethcore::account_provider::AccountProvider; use ethcore::client::TestBlockChainClient; use ethcore::transaction::{Transaction, Action}; +use rlp::encode; + +use jsonrpc_core::IoHandler; use v1::{SignerClient, Signer}; use v1::tests::helpers::TestMinerService; use v1::helpers::{SigningQueue, SignerService, FilledTransactionRequest, ConfirmationPayload}; @@ -206,6 +209,98 @@ fn should_confirm_transaction_and_dispatch() { assert_eq!(tester.miner.imported_transactions.lock().len(), 1); } +#[test] +fn should_confirm_transaction_with_rlp() { + // given + let tester = signer_tester(); + let address = tester.accounts.new_account("test").unwrap(); + let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(); + tester.signer.add_request(ConfirmationPayload::SendTransaction(FilledTransactionRequest { + from: address, + to: Some(recipient), + gas_price: U256::from(10_000), + gas: U256::from(10_000_000), + value: U256::from(1), + data: vec![], + nonce: None, + })).unwrap(); + + let t = Transaction { + nonce: U256::zero(), + gas_price: U256::from(0x1000), + gas: U256::from(10_000_000), + action: Action::Call(recipient), + value: U256::from(0x1), + data: vec![] + }; + tester.accounts.unlock_account_temporarily(address, "test".into()).unwrap(); + let signature = tester.accounts.sign(address, None, t.hash(None)).unwrap(); + let t = t.with_signature(signature, None); + let rlp = encode(&t); + + assert_eq!(tester.signer.requests().len(), 1); + + // when + let request = r#"{ + "jsonrpc":"2.0", + "method":"signer_confirmRequestRaw", + "params":["0x1", "0x"#.to_owned() + &rlp.to_hex() + r#""], + "id":1 + }"#; +println!("{:?}", request); + let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#; + + // then + assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned())); + assert_eq!(tester.signer.requests().len(), 0); + assert_eq!(tester.miner.imported_transactions.lock().len(), 1); +} + +#[test] +fn should_return_error_when_sender_does_not_match() { + // given + let tester = signer_tester(); + let address = tester.accounts.new_account("test").unwrap(); + let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(); + tester.signer.add_request(ConfirmationPayload::SendTransaction(FilledTransactionRequest { + from: Address::default(), + to: Some(recipient), + gas_price: U256::from(10_000), + gas: U256::from(10_000_000), + value: U256::from(1), + data: vec![], + nonce: None, + })).unwrap(); + + let t = Transaction { + nonce: U256::zero(), + gas_price: U256::from(0x1000), + gas: U256::from(10_000_000), + action: Action::Call(recipient), + value: U256::from(0x1), + data: vec![] + }; + tester.accounts.unlock_account_temporarily(address, "test".into()).unwrap(); + let signature = tester.accounts.sign(address, None, t.hash(None)).unwrap(); + let t = t.with_signature(signature, None); + let rlp = encode(&t); + + assert_eq!(tester.signer.requests().len(), 1); + + // when + let request = r#"{ + "jsonrpc":"2.0", + "method":"signer_confirmRequestRaw", + "params":["0x1", "0x"#.to_owned() + &rlp.to_hex() + r#""], + "id":1 + }"#; + let response = r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Couldn't parse parameters: Sent transaction does not match the request.","data":"[\"from\"]"},"id":1}"#; + + // then + assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned())); + assert_eq!(tester.signer.requests().len(), 1); +} + #[test] fn should_generate_new_token() { // given diff --git a/rpc/src/v1/traits/signer.rs b/rpc/src/v1/traits/signer.rs index 26d6899cb..eafa520d4 100644 --- a/rpc/src/v1/traits/signer.rs +++ b/rpc/src/v1/traits/signer.rs @@ -18,7 +18,7 @@ use jsonrpc_core::Error; use v1::helpers::auto_args::Wrap; -use v1::types::{U256, TransactionModification, ConfirmationRequest, ConfirmationResponse}; +use v1::types::{U256, Bytes, TransactionModification, ConfirmationRequest, ConfirmationResponse}; build_rpc_trait! { @@ -33,6 +33,10 @@ build_rpc_trait! { #[rpc(name = "signer_confirmRequest")] fn confirm_request(&self, U256, TransactionModification, String) -> Result; + /// Confirm specific request with already signed data. + #[rpc(name = "signer_confirmRequestRaw")] + fn confirm_request_raw(&self, U256, Bytes) -> Result; + /// Reject the confirmation request. #[rpc(name = "signer_rejectRequest")] fn reject_request(&self, U256) -> Result; From 67ac05ef39828f258bdce3b5387c2952d8f0766d Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Thu, 10 Nov 2016 11:27:35 +0100 Subject: [PATCH 20/55] Default contract type on UI (#3310) * Added Token and Wallet ABI in Watch Contract #3126 * Improved ABI Validator #3281 * Select contract type on first screen #3126 * Added types decsription * Add ABI type to Contract metadata // Custom as default type #3310 --- js/src/api/contract/contract.js | 33 ++--- js/src/contracts/abi/index.js | 4 +- js/src/contracts/abi/wallet.json | 1 + js/src/modals/AddContract/addContract.css | 32 +++++ js/src/modals/AddContract/addContract.js | 153 ++++++++++++++++++++-- js/src/util/validation.js | 37 +++++- 6 files changed, 227 insertions(+), 33 deletions(-) create mode 100644 js/src/contracts/abi/wallet.json create mode 100644 js/src/modals/AddContract/addContract.css diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index bb6c15d8d..44e66c925 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -136,27 +136,30 @@ export default class Contract { } parseEventLogs (logs) { - return logs.map((log) => { - const signature = log.topics[0].substr(2); - const event = this.events.find((evt) => evt.signature === signature); + return logs + .map((log) => { + const signature = log.topics[0].substr(2); + const event = this.events.find((evt) => evt.signature === signature); - if (!event) { - throw new Error(`Unable to find event matching signature ${signature}`); - } + if (!event) { + console.warn(`Unable to find event matching signature ${signature}`); + return null; + } - const decoded = event.decodeLog(log.topics, log.data); + const decoded = event.decodeLog(log.topics, log.data); - log.params = {}; - log.event = event.name; + log.params = {}; + log.event = event.name; - decoded.params.forEach((param) => { - const { type, value } = param.token; + decoded.params.forEach((param) => { + const { type, value } = param.token; - log.params[param.name] = { type, value }; - }); + log.params[param.name] = { type, value }; + }); - return log; - }); + return log; + }) + .filter((log) => log); } parseTransactionEvents (receipt) { diff --git a/js/src/contracts/abi/index.js b/js/src/contracts/abi/index.js index 599f8a13b..80f49dc5b 100644 --- a/js/src/contracts/abi/index.js +++ b/js/src/contracts/abi/index.js @@ -24,6 +24,7 @@ import owned from './owned.json'; import registry from './registry.json'; import signaturereg from './signaturereg.json'; import tokenreg from './tokenreg.json'; +import wallet from './wallet.json'; export { basiccoin, @@ -35,5 +36,6 @@ export { owned, registry, signaturereg, - tokenreg + tokenreg, + wallet }; diff --git a/js/src/contracts/abi/wallet.json b/js/src/contracts/abi/wallet.json new file mode 100644 index 000000000..8048d239c --- /dev/null +++ b/js/src/contracts/abi/wallet.json @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"removeOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"}],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_numOwners","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_lastDay","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[],"name":"resetSpentToday","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_spentToday","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"addOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_required","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_h","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_newLimit","type":"uint256"}],"name":"setDailyLimit","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"execute","outputs":[{"name":"_r","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"_operation","type":"bytes32"}],"name":"revoke","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_newRequired","type":"uint256"}],"name":"changeRequirement","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_operation","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"hasConfirmed","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"ownerIndex","type":"uint256"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"}],"name":"kill","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"}],"name":"changeOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_dailyLimit","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"inputs":[{"name":"_owners","type":"address[]"},{"name":"_required","type":"uint256"},{"name":"_daylimit","type":"uint256"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Confirmation","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Revoke","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"},{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"}],"name":"OwnerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newRequirement","type":"uint256"}],"name":"RequirementChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"SingleTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"MultiTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"initiator","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"ConfirmationNeeded","type":"event"}] diff --git a/js/src/modals/AddContract/addContract.css b/js/src/modals/AddContract/addContract.css new file mode 100644 index 000000000..ed92a86d5 --- /dev/null +++ b/js/src/modals/AddContract/addContract.css @@ -0,0 +1,32 @@ +/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* This file is part of Parity. +/* +/* Parity is free software: you can redistribute it and/or modify +/* it under the terms of the GNU General Public License as published by +/* the Free Software Foundation, either version 3 of the License, or +/* (at your option) any later version. +/* +/* Parity is distributed in the hope that it will be useful, +/* but WITHOUT ANY WARRANTY; without even the implied warranty of +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/* GNU General Public License for more details. +/* +/* You should have received a copy of the GNU General Public License +/* along with Parity. If not, see . +*/ + +.spaced { + margin: 0.25em 0; +} + +.typeContainer { + display: flex; + flex-direction: column; + + .desc { + font-size: 0.8em; + margin-bottom: 0.5em; + color: #ccc; + z-index: 2; + } +} diff --git a/js/src/modals/AddContract/addContract.js b/js/src/modals/AddContract/addContract.js index 418378136..57773d2dc 100644 --- a/js/src/modals/AddContract/addContract.js +++ b/js/src/modals/AddContract/addContract.js @@ -17,10 +17,38 @@ import React, { Component, PropTypes } from 'react'; import ContentAdd from 'material-ui/svg-icons/content/add'; import ContentClear from 'material-ui/svg-icons/content/clear'; +import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; +import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; + +import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; import { Button, Modal, Form, Input, InputAddress } from '../../ui'; import { ERRORS, validateAbi, validateAddress, validateName } from '../../util/validation'; +import { eip20, wallet } from '../../contracts/abi'; +import styles from './addContract.css'; + +const ABI_TYPES = [ + { + label: 'Token', readOnly: true, value: JSON.stringify(eip20), + type: 'token', + description: (A standard ERC 20 token) + }, + { + label: 'Multisig Wallet', readOnly: true, + type: 'multisig', + value: JSON.stringify(wallet), + description: (Official Multisig contract: see contract code) + }, + { + label: 'Custom Contract', value: '', + type: 'custom', + description: 'Contract created from custom ABI' + } +]; + +const STEPS = [ 'choose a contract type', 'enter contract details' ]; + export default class AddContract extends Component { static contextTypes = { api: PropTypes.object.isRequired @@ -34,44 +62,101 @@ export default class AddContract extends Component { state = { abi: '', abiError: ERRORS.invalidAbi, + abiType: ABI_TYPES[2], + abiTypeIndex: 2, abiParsed: null, address: '', addressError: ERRORS.invalidAddress, name: '', nameError: ERRORS.invalidName, - description: '' + description: '', + step: 0 }; + componentDidMount () { + this.onChangeABIType(null, this.state.abiTypeIndex); + } + render () { + const { step } = this.state; + return ( - { this.renderFields() } + steps={ STEPS } + current={ step } + > + { this.renderStep(step) } ); } + renderStep (step) { + switch (step) { + case 0: + return this.renderContractTypeSelector(); + default: + return this.renderFields(); + } + } + + renderContractTypeSelector () { + const { abiTypeIndex } = this.state; + + return ( + + { this.renderAbiTypes() } + + ); + } + renderDialogActions () { - const { addressError, nameError } = this.state; + const { addressError, nameError, step } = this.state; const hasError = !!(addressError || nameError); - return ([ + const cancelBtn = (
+ ) } + key={ index } + /> + )); + } - this.setState(validateAbi(abi, api)); + onNext = () => { + this.setState({ step: this.state.step + 1 }); + } + + onPrev = () => { + this.setState({ step: this.state.step - 1 }); + } + + onChangeABIType = (event, index) => { + const abiType = ABI_TYPES[index]; + this.setState({ abiTypeIndex: index, abiType }); + this.onEditAbi(abiType.value); + } + + onEditAbi = (abiIn) => { + const { api } = this.context; + const { abi, abiError, abiParsed } = validateAbi(abiIn, api); + this.setState({ abi, abiError, abiParsed }); + } + + onChangeAddress = (event, value) => { + this.onEditAddress(value); } onEditAddress = (_address) => { @@ -138,7 +262,7 @@ export default class AddContract extends Component { onAdd = () => { const { api } = this.context; - const { abiParsed, address, name, description } = this.state; + const { abiParsed, address, name, description, abiType } = this.state; Promise.all([ api.parity.setAccountName(address, name), @@ -147,6 +271,7 @@ export default class AddContract extends Component { deleted: false, timestamp: Date.now(), abi: abiParsed, + type: abiType.type, description }) ]).catch((error) => { diff --git a/js/src/util/validation.js b/js/src/util/validation.js index dd6e22911..5fad30563 100644 --- a/js/src/util/validation.js +++ b/js/src/util/validation.js @@ -38,10 +38,22 @@ export function validateAbi (abi, api) { abiParsed = JSON.parse(abi); if (!api.util.isArray(abiParsed) || !abiParsed.length) { - abiError = ERRORS.inavlidAbi; - } else { - abi = JSON.stringify(abiParsed); + abiError = ERRORS.invalidAbi; + return { abi, abiError, abiParsed }; } + + // Validate each elements of the Array + const invalidIndex = abiParsed + .map((o) => isValidAbiEvent(o, api) || isValidAbiFunction(o, api)) + .findIndex((valid) => !valid); + + if (invalidIndex !== -1) { + const invalid = abiParsed[invalidIndex]; + abiError = `${ERRORS.invalidAbi} (#${invalidIndex}: ${invalid.name || invalid.type})`; + return { abi, abiError, abiParsed }; + } + + abi = JSON.stringify(abiParsed); } catch (error) { abiError = ERRORS.invalidAbi; } @@ -53,6 +65,25 @@ export function validateAbi (abi, api) { }; } +function isValidAbiFunction (object, api) { + if (!object) { + return false; + } + + return ((object.type === 'function' && object.name) || object.type === 'constructor') && + (object.inputs && api.util.isArray(object.inputs)); +} + +function isValidAbiEvent (object, api) { + if (!object) { + return false; + } + + return (object.type === 'event') && + (object.name) && + (object.inputs && api.util.isArray(object.inputs)); +} + export function validateAddress (address) { let addressError = null; From 6ffaab15a399be463afa379f868a434badb09e95 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Thu, 10 Nov 2016 11:28:27 +0100 Subject: [PATCH 21/55] Disarm the HF and add more bootnodes (#3323) * Disarm the HF * More bootnodes * Updated tests --- ethcore/res/ethereum/frontier.json | 16 +++++++++++----- ethcore/res/ethereum/tests | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/ethcore/res/ethereum/frontier.json b/ethcore/res/ethereum/frontier.json index ecaefa4c3..be473237c 100644 --- a/ethcore/res/ethereum/frontier.json +++ b/ethcore/res/ethereum/frontier.json @@ -131,10 +131,10 @@ "0x807640a13483f8ac783c557fcdf27be11ea4ac7a" ], "eip150Transition": "0x259518", - "eip155Transition": 2642462, - "eip160Transition": 2642462, - "eip161abcTransition": 2642462, - "eip161dTransition": 2642462 + "eip155Transition": "0x7fffffffffffffff", + "eip160Transition": "0x7fffffffffffffff", + "eip161abcTransition": "0x7fffffffffffffff", + "eip161dTransition": "0x7fffffffffffffff" } } }, @@ -176,7 +176,13 @@ "enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303", "enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303", "enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123:30303", - "enode://4cd540b2c3292e17cff39922e864094bf8b0741fcc8c5dcea14957e389d7944c70278d872902e3d0345927f621547efa659013c400865485ab4bfa0c6596936f@138.201.144.135:30303" + "enode://4cd540b2c3292e17cff39922e864094bf8b0741fcc8c5dcea14957e389d7944c70278d872902e3d0345927f621547efa659013c400865485ab4bfa0c6596936f@138.201.144.135:30303", + + "enode://89d5dc2a81e574c19d0465f497c1af96732d1b61a41de89c2a37f35707689ac416529fae1038809852b235c2d30fd325abdc57c122feeefbeaaf802cc7e9580d@45.55.33.62:30303", + "enode://605e04a43b1156966b3a3b66b980c87b7f18522f7f712035f84576016be909a2798a438b2b17b1a8c58db314d88539a77419ca4be36148c086900fba487c9d39@188.166.255.12:30303", + "enode://016b20125f447a3b203a3cae953b2ede8ffe51290c071e7599294be84317635730c397b8ff74404d6be412d539ee5bb5c3c700618723d3b53958c92bd33eaa82@159.203.210.80:30303", + "enode://01f76fa0561eca2b9a7e224378dd854278735f1449793c46ad0c4e79e8775d080c21dcc455be391e90a98153c3b05dcc8935c8440de7b56fe6d67251e33f4e3c@10.6.6.117:30303", + "enode://fe11ef89fc5ac9da358fc160857855f25bbf9e332c79b9ca7089330c02b728b2349988c6062f10982041702110745e203d26975a6b34bcc97144f9fe439034e8@10.1.72.117:30303" ], "accounts": { "0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, diff --git a/ethcore/res/ethereum/tests b/ethcore/res/ethereum/tests index 853333e7d..9028c4801 160000 --- a/ethcore/res/ethereum/tests +++ b/ethcore/res/ethereum/tests @@ -1 +1 @@ -Subproject commit 853333e7da312775fb8f32f2c2771b8578cd0d79 +Subproject commit 9028c4801fd39fbb71a9796979182549a24e81c8 From 1deeb0d9016293105a329949def64ba40b752ac9 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Thu, 10 Nov 2016 11:42:00 +0100 Subject: [PATCH 22/55] Git pre-push checks for UI (#3072) * Added eslint cached option (#2291) * Added pre-push script running linting (#2291) * Modifies pre-push hook to run if eslint installed // auto install hook * Update pre-push script comment * Added husky for git hooks (#3072) --- js/.gitignore | 1 + js/package.json | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/js/.gitignore b/js/.gitignore index acb73a82a..c1e496d91 100644 --- a/js/.gitignore +++ b/js/.gitignore @@ -6,3 +6,4 @@ build .dist .happypack .npmjs +.eslintcache diff --git a/js/package.json b/js/package.json index eb0926c4b..e7ba88e74 100644 --- a/js/package.json +++ b/js/package.json @@ -39,9 +39,11 @@ "clean": "rm -rf ./build ./coverage", "coveralls": "npm run testCoverage && coveralls < coverage/lcov.info", "lint": "eslint --ignore-path .gitignore ./src/", + "lint:cached": "eslint --cache --ignore-path .gitignore ./src/", "test": "mocha 'src/**/*.spec.js'", "test:coverage": "istanbul cover _mocha -- 'src/**/*.spec.js'", - "test:e2e": "mocha 'src/**/*.e2e.js'" + "test:e2e": "mocha 'src/**/*.e2e.js'", + "prepush": "npm run lint:cached" }, "devDependencies": { "babel-cli": "^6.10.1", @@ -84,6 +86,7 @@ "happypack": "^2.2.1", "history": "^2.0.0", "html-loader": "^0.4.4", + "husky": "^0.11.9", "ignore-styles": "2.0.0", "image-webpack-loader": "^1.8.0", "istanbul": "^1.0.0-alpha.2", From 6ad909e7b36b69582840020c47dadc29d8ae468d Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Thu, 10 Nov 2016 14:26:06 +0100 Subject: [PATCH 23/55] Removed unnecessary test (#3342) --- js/src/api/contract/contract.spec.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/js/src/api/contract/contract.spec.js b/js/src/api/contract/contract.spec.js index 9065b4fad..96929cc11 100644 --- a/js/src/api/contract/contract.spec.js +++ b/js/src/api/contract/contract.spec.js @@ -119,19 +119,6 @@ describe('api/contract/Contract', () => { }); describe('parseTransactionEvents', () => { - it('checks for unmatched signatures', () => { - const contract = new Contract(eth, [{ anonymous: false, name: 'Message', type: 'event' }]); - expect(() => contract.parseTransactionEvents({ - logs: [{ - data: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063cf90d3f0410092fc0fca41846f5962239791950000000000000000000000000000000000000000000000000000000056e6c85f0000000000000000000000000000000000000000000000000001000000004fcd00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000d706f7374286d6573736167652900000000000000000000000000000000000000', - topics: [ - '0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5', - '0x0000000000000000000000000000000000000000000000000001000000004fe0' - ] - }] - })).to.throw(/event matching signature/); - }); - it('parses a transaction log into the data', () => { const contract = new Contract(eth, [ { From bb5da2379ba4940beca921ae9d4652a107f2ea8d Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Thu, 10 Nov 2016 14:49:16 +0100 Subject: [PATCH 24/55] Windows app and installer fixes (#3338) * Windows app and installer fixes * Sorted out comments --- nsis/installer.nsi | 5 ++++- windows/ptray/ptray.cpp | 42 +++++++++++++++++++++++++++++++++-------- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/nsis/installer.nsi b/nsis/installer.nsi index 9c928b63b..685b4a936 100644 --- a/nsis/installer.nsi +++ b/nsis/installer.nsi @@ -12,7 +12,7 @@ !define VERSIONMINOR 5 !define VERSIONBUILD 0 !define ARGS "--warp" -!define FIRST_START_ARGS "--warp --mode=passive" +!define FIRST_START_ARGS "ui --warp --mode=passive" !addplugindir .\ @@ -160,6 +160,9 @@ section "uninstall" !insertmacro TerminateApp # Remove Start Menu launcher delete "$SMPROGRAMS\${COMPANYNAME}\${APPNAME}.lnk" + delete "$SMPROGRAMS\${COMPANYNAME}\${APPNAME} Ethereum.lnk" + delete "$DESKTOP\${APPNAME} Ethereum.lnk" + # Try to remove the Start Menu folder - this will only happen if it is empty rmDir "$SMPROGRAMS\${COMPANYNAME}" diff --git a/windows/ptray/ptray.cpp b/windows/ptray/ptray.cpp index cb33ad68a..8c50df2c1 100644 --- a/windows/ptray/ptray.cpp +++ b/windows/ptray/ptray.cpp @@ -41,6 +41,7 @@ DWORD parityProcId = 0; NOTIFYICONDATA nidApp; WCHAR szTitle[MAX_LOADSTRING]; WCHAR szWindowClass[MAX_LOADSTRING]; +LPCWCHAR commandLineFiltered = L""; LPCWSTR cParityExe = _T("parity.exe"); @@ -85,7 +86,6 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, OpenUI(); return 0; } - LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadStringW(hInstance, IDC_PTRAY, szWindowClass, MAX_LOADSTRING); @@ -133,6 +133,24 @@ ATOM MyRegisterClass(HINSTANCE hInstance) bool InitInstance(HINSTANCE hInstance, int nCmdShow, LPWSTR cmdLine) { + if (lstrlen(cmdLine) > 0) + { + int commandLineArgs = 0; + LPWSTR* commandLine = CommandLineToArgvW(cmdLine, &commandLineArgs); + LPWSTR filteredArgs = new WCHAR[lstrlen(cmdLine) + 2]; + filteredArgs[0] = '\0'; + for (int i = 0; i < commandLineArgs; i++) + { + // Remove "ui" from command line + if (lstrcmp(commandLine[i], L"ui") != 0) + { + lstrcat(filteredArgs, commandLine[i]); + lstrcat(filteredArgs, L" "); + } + } + commandLineFiltered = filteredArgs; + } + // Check if already running PROCESSENTRY32 entry; entry.dwSize = sizeof(PROCESSENTRY32); @@ -190,7 +208,9 @@ bool InitInstance(HINSTANCE hInstance, int nCmdShow, LPWSTR cmdLine) nidApp.uCallbackMessage = WM_USER_SHELLICON; LoadString(hInstance, IDS_CONTROL_PARITY, nidApp.szTip, MAX_LOADSTRING); Shell_NotifyIcon(NIM_ADD, &nidApp); - return TRUE; + + SetTimer(hWnd, 0, 1000, nullptr); + return true; } @@ -294,8 +314,10 @@ void OpenUI() PROCESS_INFORMATION procInfo = { 0 }; STARTUPINFO startupInfo = { sizeof(STARTUPINFO) }; - LPWSTR cmd = _T("parity.exe ui"); - CreateProcess(path, cmd, nullptr, nullptr, false, CREATE_NO_WINDOW, nullptr, nullptr, &startupInfo, &procInfo); + LPWSTR args = new WCHAR[lstrlen(commandLineFiltered) + MAX_PATH + 2]; + lstrcpy(args, L"parity.exe ui "); + lstrcat(args, commandLineFiltered); + CreateProcess(path, args, nullptr, nullptr, false, CREATE_NO_WINDOW, nullptr, nullptr, &startupInfo, &procInfo); } bool AutostartEnabled() { @@ -321,10 +343,14 @@ void EnableAutostart(bool enable) { if (enable) { - TCHAR path[MAX_PATH] = { 0 }; - if (!GetTrayExePath(path, MAX_PATH)) - return; - RegSetValueEx(hKey, L"Parity", 0, REG_SZ, (LPBYTE)path, MAX_PATH); + LPWSTR args = new WCHAR[lstrlen(commandLineFiltered) + MAX_PATH + 2]; + if (GetTrayExePath(args, MAX_PATH)) + { + lstrcat(args, L" "); + lstrcat(args, commandLineFiltered); + RegSetValueEx(hKey, L"Parity", 0, REG_SZ, (LPBYTE)args, MAX_PATH); + } + delete[] args; } else { From 561f008c915c07a00a89cd8859e6b590a729f722 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 10 Nov 2016 15:01:07 +0100 Subject: [PATCH 25/55] Manual bump of package.json (#3345) --- js/package.json | 2 +- js/scripts/release.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/package.json b/js/package.json index e7ba88e74..b8bfedc71 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.24", + "version": "0.2.26", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", diff --git a/js/scripts/release.sh b/js/scripts/release.sh index fd95e00b8..5e631cf98 100755 --- a/js/scripts/release.sh +++ b/js/scripts/release.sh @@ -63,7 +63,7 @@ if [ "$BRANCH" == "master" ]; then echo "*** Publishing $PACKAGE to npmjs" cd .npmjs - npm publish --access public + npm publish --access public || true cd ../.. fi From aa0f05a18636b9a22554460c7ba66527436fff28 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 10 Nov 2016 14:07:52 +0000 Subject: [PATCH 26/55] [ci skip] js-precompiled 20161110-140631 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3dd51492..3808bfd61 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#9054ef95a5d79cbd8fefe4869ec3b4de07e9a72d" +source = "git+https://github.com/ethcore/js-precompiled.git#d92b25d4c79259850cb3ae95b5c6cd4649056c3f" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index b8bfedc71..9da83e7e1 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.26", + "version": "0.2.27", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 0ed811472636882549288bb8f7e961daaf282280 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 10 Nov 2016 14:20:08 +0000 Subject: [PATCH 27/55] [ci skip] js-precompiled 20161110-141839 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3808bfd61..f6d5fa213 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#d92b25d4c79259850cb3ae95b5c6cd4649056c3f" +source = "git+https://github.com/ethcore/js-precompiled.git#29c6f1d527db5c6a29c6b6afdb9bde32c6809079" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 9da83e7e1..ca633fd29 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.27", + "version": "0.2.28", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From a97e68a030adc664cfed51e5d55149e9976b1e9f Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 10 Nov 2016 16:38:47 +0100 Subject: [PATCH 28/55] Make transactions load (#3348) --- js/src/views/Account/Transactions/transactions.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/js/src/views/Account/Transactions/transactions.js b/js/src/views/Account/Transactions/transactions.js index fcbb2ce41..3e14dd923 100644 --- a/js/src/views/Account/Transactions/transactions.js +++ b/js/src/views/Account/Transactions/transactions.js @@ -48,9 +48,7 @@ class Transactions extends Component { } componentDidMount () { - if (this.props.traceMode !== undefined) { - this.getTransactions(this.props); - } + this.getTransactions(this.props); } componentWillReceiveProps (newProps) { From 529633e9b24ec103b65b78a6d21dc4f223a29128 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 10 Nov 2016 16:33:24 +0000 Subject: [PATCH 29/55] [ci skip] js-precompiled 20161110-163203 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f6d5fa213..a2c441d6e 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#29c6f1d527db5c6a29c6b6afdb9bde32c6809079" +source = "git+https://github.com/ethcore/js-precompiled.git#c6691c86121806988c73d1847b3a0b77ee5b546e" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index ca633fd29..7b57385a9 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.28", + "version": "0.2.29", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 3854b8a689d6c6e7634491f60e72a7d244de0cc0 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 10 Nov 2016 18:30:17 +0100 Subject: [PATCH 30/55] LES Part 1 (#3322) * stub implementations of light client trait * Light provider trait * light client sync stubs * LES boilerplate * stub implementation of provider for client * skeleton and request traits * request definitions * new_list -> begin_list * handle unknown packet * revise light implementation strategy * make verification module public * Move all light client work to own crate * experiment with answering requests * buffer flow scaffolding * remove LESv2 requests * buffer flow basics, implement cost table * begin status module * implement handshake parsing and creation * implement announcement serialization * errors, punishment, and handshake * handle announcements * making announcements, clean up warnings * allow dead code temporarily --- ethcore/light/Cargo.toml | 16 + ethcore/light/src/client.rs | 115 +++++ ethcore/light/src/lib.rs | 47 ++ ethcore/light/src/net/buffer_flow.rs | 264 ++++++++++ ethcore/light/src/net/error.rs | 94 ++++ ethcore/light/src/net/mod.rs | 506 +++++++++++++++++++ ethcore/light/src/net/status.rs | 539 +++++++++++++++++++++ ethcore/light/src/provider.rs | 71 +++ ethcore/light/src/request.rs | 145 ++++++ ethcore/src/lib.rs | 2 +- ethcore/src/types/mod.rs.in | 2 +- ethcore/src/verification/canon_verifier.rs | 3 + ethcore/src/verification/mod.rs | 3 + ethcore/src/verification/noop_verifier.rs | 3 + ethcore/src/verification/verification.rs | 12 +- ethcore/src/verification/verifier.rs | 4 + sync/src/sync_io.rs | 4 +- util/fetch/src/lib.rs | 2 +- 18 files changed, 1821 insertions(+), 11 deletions(-) create mode 100644 ethcore/light/Cargo.toml create mode 100644 ethcore/light/src/client.rs create mode 100644 ethcore/light/src/lib.rs create mode 100644 ethcore/light/src/net/buffer_flow.rs create mode 100644 ethcore/light/src/net/error.rs create mode 100644 ethcore/light/src/net/mod.rs create mode 100644 ethcore/light/src/net/status.rs create mode 100644 ethcore/light/src/provider.rs create mode 100644 ethcore/light/src/request.rs diff --git a/ethcore/light/Cargo.toml b/ethcore/light/Cargo.toml new file mode 100644 index 000000000..daf141de7 --- /dev/null +++ b/ethcore/light/Cargo.toml @@ -0,0 +1,16 @@ +[package] +description = "Parity LES primitives" +homepage = "https://ethcore.io" +license = "GPL-3.0" +name = "ethcore-light" +version = "1.5.0" +authors = ["Ethcore "] + +[dependencies] +log = "0.3" +ethcore = { path = ".." } +ethcore-util = { path = "../../util" } +ethcore-network = { path = "../../util/network" } +ethcore-io = { path = "../../util/io" } +rlp = { path = "../../util/rlp" } +time = "0.1" \ No newline at end of file diff --git a/ethcore/light/src/client.rs b/ethcore/light/src/client.rs new file mode 100644 index 000000000..e3b5745b2 --- /dev/null +++ b/ethcore/light/src/client.rs @@ -0,0 +1,115 @@ +// 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 . + +//! Light client implementation. Used for raw data queries as well as the header +//! sync. + +use std::sync::Arc; + +use ethcore::engines::Engine; +use ethcore::ids::BlockID; +use ethcore::service::ClientIoMessage; +use ethcore::block_import_error::BlockImportError; +use ethcore::block_status::BlockStatus; +use ethcore::verification::queue::{HeaderQueue, QueueInfo}; +use ethcore::transaction::SignedTransaction; +use ethcore::blockchain_info::BlockChainInfo; + +use io::IoChannel; +use util::hash::H256; +use util::{Bytes, Mutex}; + +use provider::Provider; +use request; + +/// Light client implementation. +pub struct Client { + engine: Arc, + header_queue: HeaderQueue, + message_channel: Mutex>, +} + +impl Client { + /// Import a header as rlp-encoded bytes. + pub fn import_header(&self, bytes: Bytes) -> Result { + let header = ::rlp::decode(&bytes); + + self.header_queue.import(header).map_err(Into::into) + } + + /// Whether the block is already known (but not necessarily part of the canonical chain) + pub fn is_known(&self, _id: BlockID) -> bool { + false + } + + /// Fetch a vector of all pending transactions. + pub fn pending_transactions(&self) -> Vec { + vec![] + } + + /// Inquire about the status of a given block. + pub fn status(&self, _id: BlockID) -> BlockStatus { + BlockStatus::Unknown + } + + /// Get the header queue info. + pub fn queue_info(&self) -> QueueInfo { + self.header_queue.queue_info() + } +} + +// dummy implementation -- may draw from canonical cache further on. +impl Provider for Client { + fn chain_info(&self) -> BlockChainInfo { + unimplemented!() + } + + fn reorg_depth(&self, _a: &H256, _b: &H256) -> Option { + None + } + + fn earliest_state(&self) -> Option { + None + } + + fn block_headers(&self, _req: request::Headers) -> Vec { + Vec::new() + } + + fn block_bodies(&self, _req: request::Bodies) -> Vec { + Vec::new() + } + + fn receipts(&self, _req: request::Receipts) -> Vec { + Vec::new() + } + + fn proofs(&self, _req: request::StateProofs) -> Vec { + Vec::new() + } + + fn code(&self, _req: request::ContractCodes) -> Vec { + Vec::new() + } + + fn header_proofs(&self, _req: request::HeaderProofs) -> Vec { + Vec::new() + } + + fn pending_transactions(&self) -> Vec { + Vec::new() + } +} \ No newline at end of file diff --git a/ethcore/light/src/lib.rs b/ethcore/light/src/lib.rs new file mode 100644 index 000000000..07e6833a7 --- /dev/null +++ b/ethcore/light/src/lib.rs @@ -0,0 +1,47 @@ +// 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 . + +//! Light client logic and implementation. +//! +//! A "light" client stores very little chain-related data locally +//! unlike a full node, which stores all blocks, headers, receipts, and more. +//! +//! This enables the client to have a much lower resource footprint in +//! exchange for the cost of having to ask the network for state data +//! while responding to queries. This makes a light client unsuitable for +//! low-latency applications, but perfectly suitable for simple everyday +//! use-cases like sending transactions from a personal account. +//! +//! It starts by performing a header-only sync, verifying random samples +//! of members of the chain to varying degrees. + +// TODO: remove when integrating with parity. +#![allow(dead_code)] + +pub mod client; +pub mod net; +pub mod provider; +pub mod request; + +extern crate ethcore_util as util; +extern crate ethcore_network as network; +extern crate ethcore_io as io; +extern crate ethcore; +extern crate rlp; +extern crate time; + +#[macro_use] +extern crate log; \ No newline at end of file diff --git a/ethcore/light/src/net/buffer_flow.rs b/ethcore/light/src/net/buffer_flow.rs new file mode 100644 index 000000000..b7bd30f82 --- /dev/null +++ b/ethcore/light/src/net/buffer_flow.rs @@ -0,0 +1,264 @@ +// 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 . + +//! LES buffer flow management. +//! +//! Every request in the LES protocol leads to a reduction +//! of the requester's buffer value as a rate-limiting mechanism. +//! This buffer value will recharge at a set rate. +//! +//! This module provides an interface for configuration of buffer +//! flow costs and recharge rates. + +use request; +use super::packet; +use super::error::Error; + +use rlp::*; +use util::U256; +use time::{Duration, SteadyTime}; + +/// A request cost specification. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Cost(pub U256, pub U256); + +/// Buffer value. +/// +/// Produced and recharged using `FlowParams`. +/// Definitive updates can be made as well -- these will reset the recharge +/// point to the time of the update. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Buffer { + estimate: U256, + recharge_point: SteadyTime, +} + +impl Buffer { + /// Get the current buffer value. + pub fn current(&self) -> U256 { self.estimate.clone() } + + /// Make a definitive update. + /// This will be the value obtained after receiving + /// a response to a request. + pub fn update_to(&mut self, value: U256) { + self.estimate = value; + self.recharge_point = SteadyTime::now(); + } + + /// Attempt to apply the given cost to the buffer. + /// + /// If successful, the cost will be deducted successfully. + /// + /// If unsuccessful, the structure will be unaltered an an + /// error will be produced. + pub fn deduct_cost(&mut self, cost: U256) -> Result<(), Error> { + match cost > self.estimate { + true => Err(Error::BufferEmpty), + false => { + self.estimate = self.estimate - cost; + Ok(()) + } + } + } +} + +/// A cost table, mapping requests to base and per-request costs. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CostTable { + headers: Cost, + bodies: Cost, + receipts: Cost, + state_proofs: Cost, + contract_codes: Cost, + header_proofs: Cost, +} + +impl Default for CostTable { + fn default() -> Self { + // arbitrarily chosen constants. + CostTable { + headers: Cost(100000.into(), 10000.into()), + bodies: Cost(150000.into(), 15000.into()), + receipts: Cost(50000.into(), 5000.into()), + state_proofs: Cost(250000.into(), 25000.into()), + contract_codes: Cost(200000.into(), 20000.into()), + header_proofs: Cost(150000.into(), 15000.into()), + } + } +} + +impl RlpEncodable for CostTable { + fn rlp_append(&self, s: &mut RlpStream) { + fn append_cost(s: &mut RlpStream, msg_id: u8, cost: &Cost) { + s.begin_list(3) + .append(&msg_id) + .append(&cost.0) + .append(&cost.1); + } + + s.begin_list(6); + + append_cost(s, packet::GET_BLOCK_HEADERS, &self.headers); + append_cost(s, packet::GET_BLOCK_BODIES, &self.bodies); + append_cost(s, packet::GET_RECEIPTS, &self.receipts); + append_cost(s, packet::GET_PROOFS, &self.state_proofs); + append_cost(s, packet::GET_CONTRACT_CODES, &self.contract_codes); + append_cost(s, packet::GET_HEADER_PROOFS, &self.header_proofs); + } +} + +impl RlpDecodable for CostTable { + fn decode(decoder: &D) -> Result where D: Decoder { + let rlp = decoder.as_rlp(); + + let mut headers = None; + let mut bodies = None; + let mut receipts = None; + let mut state_proofs = None; + let mut contract_codes = None; + let mut header_proofs = None; + + for row in rlp.iter() { + let msg_id: u8 = try!(row.val_at(0)); + let cost = { + let base = try!(row.val_at(1)); + let per = try!(row.val_at(2)); + + Cost(base, per) + }; + + match msg_id { + packet::GET_BLOCK_HEADERS => headers = Some(cost), + packet::GET_BLOCK_BODIES => bodies = Some(cost), + packet::GET_RECEIPTS => receipts = Some(cost), + packet::GET_PROOFS => state_proofs = Some(cost), + packet::GET_CONTRACT_CODES => contract_codes = Some(cost), + packet::GET_HEADER_PROOFS => header_proofs = Some(cost), + _ => return Err(DecoderError::Custom("Unrecognized message in cost table")), + } + } + + Ok(CostTable { + headers: try!(headers.ok_or(DecoderError::Custom("No headers cost specified"))), + bodies: try!(bodies.ok_or(DecoderError::Custom("No bodies cost specified"))), + receipts: try!(receipts.ok_or(DecoderError::Custom("No receipts cost specified"))), + state_proofs: try!(state_proofs.ok_or(DecoderError::Custom("No proofs cost specified"))), + contract_codes: try!(contract_codes.ok_or(DecoderError::Custom("No contract codes specified"))), + header_proofs: try!(header_proofs.ok_or(DecoderError::Custom("No header proofs cost specified"))), + }) + } +} + +/// A buffer-flow manager handles costs, recharge, limits +#[derive(Debug, Clone, PartialEq)] +pub struct FlowParams { + costs: CostTable, + limit: U256, + recharge: U256, +} + +impl FlowParams { + /// Create new flow parameters from a request cost table, + /// buffer limit, and (minimum) rate of recharge. + pub fn new(limit: U256, costs: CostTable, recharge: U256) -> Self { + FlowParams { + costs: costs, + limit: limit, + recharge: recharge, + } + } + + /// Get a reference to the buffer limit. + pub fn limit(&self) -> &U256 { &self.limit } + + /// Get a reference to the cost table. + pub fn cost_table(&self) -> &CostTable { &self.costs } + + /// Get a reference to the recharge rate. + pub fn recharge_rate(&self) -> &U256 { &self.recharge } + + /// Compute the actual cost of a request, given the kind of request + /// and number of requests made. + pub fn compute_cost(&self, kind: request::Kind, amount: usize) -> U256 { + let cost = match kind { + request::Kind::Headers => &self.costs.headers, + request::Kind::Bodies => &self.costs.bodies, + request::Kind::Receipts => &self.costs.receipts, + request::Kind::StateProofs => &self.costs.state_proofs, + request::Kind::Codes => &self.costs.contract_codes, + request::Kind::HeaderProofs => &self.costs.header_proofs, + }; + + let amount: U256 = amount.into(); + cost.0 + (amount * cost.1) + } + + /// Create initial buffer parameter. + pub fn create_buffer(&self) -> Buffer { + Buffer { + estimate: self.limit, + recharge_point: SteadyTime::now(), + } + } + + /// Recharge the buffer based on time passed since last + /// update. + pub fn recharge(&self, buf: &mut Buffer) { + let now = SteadyTime::now(); + + // recompute and update only in terms of full seconds elapsed + // in order to keep the estimate as an underestimate. + let elapsed = (now - buf.recharge_point).num_seconds(); + buf.recharge_point = buf.recharge_point + Duration::seconds(elapsed); + + let elapsed: U256 = elapsed.into(); + + buf.estimate = ::std::cmp::min(self.limit, buf.estimate + (elapsed * self.recharge)); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_serialize_cost_table() { + let costs = CostTable::default(); + let serialized = ::rlp::encode(&costs); + + let new_costs: CostTable = ::rlp::decode(&*serialized); + + assert_eq!(costs, new_costs); + } + + #[test] + fn buffer_mechanism() { + use std::thread; + use std::time::Duration; + + let flow_params = FlowParams::new(100.into(), Default::default(), 20.into()); + let mut buffer = flow_params.create_buffer(); + + assert!(buffer.deduct_cost(101.into()).is_err()); + assert!(buffer.deduct_cost(10.into()).is_ok()); + + thread::sleep(Duration::from_secs(1)); + + flow_params.recharge(&mut buffer); + + assert_eq!(buffer.estimate, 100.into()); + } +} \ No newline at end of file diff --git a/ethcore/light/src/net/error.rs b/ethcore/light/src/net/error.rs new file mode 100644 index 000000000..e15bd50d3 --- /dev/null +++ b/ethcore/light/src/net/error.rs @@ -0,0 +1,94 @@ +// 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 . + +//! Defines error types and levels of punishment to use upon +//! encountering. + +use rlp::DecoderError; +use network::NetworkError; + +use std::fmt; + +/// Levels of punishment. +/// +/// Currently just encompasses two different kinds of disconnect and +/// no punishment, but this is where reputation systems might come into play. +// In ascending order +#[derive(Debug, PartialEq, Eq)] +pub enum Punishment { + /// Perform no punishment. + None, + /// Disconnect the peer, but don't prevent them from reconnecting. + Disconnect, + /// Disconnect the peer and prevent them from reconnecting. + Disable, +} + +/// Kinds of errors which can be encountered in the course of LES. +#[derive(Debug)] +pub enum Error { + /// An RLP decoding error. + Rlp(DecoderError), + /// A network error. + Network(NetworkError), + /// Out of buffer. + BufferEmpty, + /// Unrecognized packet code. + UnrecognizedPacket(u8), + /// Unexpected handshake. + UnexpectedHandshake, + /// Peer on wrong network (wrong NetworkId or genesis hash) + WrongNetwork, +} + +impl Error { + /// What level of punishment does this error warrant? + pub fn punishment(&self) -> Punishment { + match *self { + Error::Rlp(_) => Punishment::Disable, + Error::Network(_) => Punishment::None, + Error::BufferEmpty => Punishment::Disable, + Error::UnrecognizedPacket(_) => Punishment::Disconnect, + Error::UnexpectedHandshake => Punishment::Disconnect, + Error::WrongNetwork => Punishment::Disable, + } + } +} + +impl From for Error { + fn from(err: DecoderError) -> Self { + Error::Rlp(err) + } +} + +impl From for Error { + fn from(err: NetworkError) -> Self { + Error::Network(err) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Error::Rlp(ref err) => err.fmt(f), + Error::Network(ref err) => err.fmt(f), + Error::BufferEmpty => write!(f, "Out of buffer"), + Error::UnrecognizedPacket(code) => write!(f, "Unrecognized packet: 0x{:x}", code), + Error::UnexpectedHandshake => write!(f, "Unexpected handshake"), + Error::WrongNetwork => write!(f, "Wrong network"), + } + } +} \ No newline at end of file diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs new file mode 100644 index 000000000..e72ce4bb2 --- /dev/null +++ b/ethcore/light/src/net/mod.rs @@ -0,0 +1,506 @@ +// 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 . + +//! LES Protocol Version 1 implementation. +//! +//! This uses a "Provider" to answer requests. +//! See https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) + +use io::TimerToken; +use network::{NetworkProtocolHandler, NetworkContext, NetworkError, PeerId}; +use rlp::{RlpStream, Stream, UntrustedRlp, View}; +use util::hash::H256; +use util::RwLock; + +use std::collections::{HashMap, HashSet}; +use std::sync::atomic::AtomicUsize; + +use provider::Provider; +use request::{self, Request}; + +use self::buffer_flow::{Buffer, FlowParams}; +use self::error::{Error, Punishment}; +use self::status::{Status, Capabilities}; + +mod buffer_flow; +mod error; +mod status; + +pub use self::status::Announcement; + +const TIMEOUT: TimerToken = 0; +const TIMEOUT_INTERVAL_MS: u64 = 1000; + +// LPV1 +const PROTOCOL_VERSION: u32 = 1; + +// TODO [rob] make configurable. +const PROTOCOL_ID: [u8; 3] = *b"les"; + +// packet ID definitions. +mod packet { + // the status packet. + pub const STATUS: u8 = 0x00; + + // announcement of new block hashes or capabilities. + pub const ANNOUNCE: u8 = 0x01; + + // request and response for block headers + pub const GET_BLOCK_HEADERS: u8 = 0x02; + pub const BLOCK_HEADERS: u8 = 0x03; + + // request and response for block bodies + pub const GET_BLOCK_BODIES: u8 = 0x04; + pub const BLOCK_BODIES: u8 = 0x05; + + // request and response for transaction receipts. + pub const GET_RECEIPTS: u8 = 0x06; + pub const RECEIPTS: u8 = 0x07; + + // request and response for merkle proofs. + pub const GET_PROOFS: u8 = 0x08; + pub const PROOFS: u8 = 0x09; + + // request and response for contract code. + pub const GET_CONTRACT_CODES: u8 = 0x0a; + pub const CONTRACT_CODES: u8 = 0x0b; + + // relay transactions to peers. + pub const SEND_TRANSACTIONS: u8 = 0x0c; + + // request and response for header proofs in a CHT. + pub const GET_HEADER_PROOFS: u8 = 0x0d; + pub const HEADER_PROOFS: u8 = 0x0e; +} + +// A pending peer: one we've sent our status to but +// may not have received one for. +struct PendingPeer { + sent_head: H256, +} + +// data about each peer. +struct Peer { + local_buffer: Buffer, // their buffer relative to us + remote_buffer: Buffer, // our buffer relative to them + current_asking: HashSet, // pending request ids. + status: Status, + capabilities: Capabilities, + remote_flow: FlowParams, + sent_head: H256, // last head we've given them. +} + +/// This is an implementation of the light ethereum network protocol, abstracted +/// over a `Provider` of data and a p2p network. +/// +/// This is simply designed for request-response purposes. Higher level uses +/// of the protocol, such as synchronization, will function as wrappers around +/// this system. +pub struct LightProtocol { + provider: Box, + genesis_hash: H256, + network_id: status::NetworkId, + pending_peers: RwLock>, + peers: RwLock>, + pending_requests: RwLock>, + capabilities: RwLock, + flow_params: FlowParams, // assumed static and same for every peer. + req_id: AtomicUsize, +} + +impl LightProtocol { + /// Make an announcement of new chain head and capabilities to all peers. + /// The announcement is expected to be valid. + pub fn make_announcement(&self, mut announcement: Announcement, io: &NetworkContext) { + let mut reorgs_map = HashMap::new(); + + // calculate reorg info and send packets + for (peer_id, peer_info) in self.peers.write().iter_mut() { + let reorg_depth = reorgs_map.entry(peer_info.sent_head) + .or_insert_with(|| { + match self.provider.reorg_depth(&announcement.head_hash, &peer_info.sent_head) { + Some(depth) => depth, + None => { + // both values will always originate locally -- this means something + // has gone really wrong + debug!(target: "les", "couldn't compute reorganization depth between {:?} and {:?}", + &announcement.head_hash, &peer_info.sent_head); + 0 + } + } + }); + + peer_info.sent_head = announcement.head_hash; + announcement.reorg_depth = *reorg_depth; + + if let Err(e) = io.send(*peer_id, packet::ANNOUNCE, status::write_announcement(&announcement)) { + debug!(target: "les", "Error sending to peer {}: {}", peer_id, e); + } + } + } +} + +impl LightProtocol { + // called when a peer connects. + fn on_connect(&self, peer: &PeerId, io: &NetworkContext) { + let peer = *peer; + + match self.send_status(peer, io) { + Ok(pending_peer) => { + self.pending_peers.write().insert(peer, pending_peer); + } + Err(e) => { + trace!(target: "les", "Error while sending status: {}", e); + io.disconnect_peer(peer); + } + } + } + + // called when a peer disconnects. + fn on_disconnect(&self, peer: PeerId) { + // TODO: reassign all requests assigned to this peer. + self.pending_peers.write().remove(&peer); + self.peers.write().remove(&peer); + } + + // send status to a peer. + fn send_status(&self, peer: PeerId, io: &NetworkContext) -> Result { + let chain_info = self.provider.chain_info(); + + // TODO: could update capabilities here. + + let status = Status { + head_td: chain_info.total_difficulty, + head_hash: chain_info.best_block_hash, + head_num: chain_info.best_block_number, + genesis_hash: chain_info.genesis_hash, + protocol_version: PROTOCOL_VERSION, + network_id: self.network_id, + last_head: None, + }; + + let capabilities = self.capabilities.read().clone(); + let status_packet = status::write_handshake(&status, &capabilities, &self.flow_params); + + try!(io.send(peer, packet::STATUS, status_packet)); + + Ok(PendingPeer { + sent_head: chain_info.best_block_hash, + }) + } + + // Handle status message from peer. + fn status(&self, peer: &PeerId, data: UntrustedRlp) -> Result<(), Error> { + let pending = match self.pending_peers.write().remove(peer) { + Some(pending) => pending, + None => { + return Err(Error::UnexpectedHandshake); + } + }; + + let (status, capabilities, flow_params) = try!(status::parse_handshake(data)); + + trace!(target: "les", "Connected peer with chain head {:?}", (status.head_hash, status.head_num)); + + if (status.network_id, status.genesis_hash) != (self.network_id, self.genesis_hash) { + return Err(Error::WrongNetwork); + } + + self.peers.write().insert(*peer, Peer { + local_buffer: self.flow_params.create_buffer(), + remote_buffer: flow_params.create_buffer(), + current_asking: HashSet::new(), + status: status, + capabilities: capabilities, + remote_flow: flow_params, + sent_head: pending.sent_head, + }); + + Ok(()) + } + + // Handle an announcement. + fn announcement(&self, peer: &PeerId, data: UntrustedRlp) -> Result<(), Error> { + if !self.peers.read().contains_key(peer) { + debug!(target: "les", "Ignoring announcement from unknown peer"); + return Ok(()) + } + + let announcement = try!(status::parse_announcement(data)); + let mut peers = self.peers.write(); + + let peer_info = match peers.get_mut(peer) { + Some(info) => info, + None => return Ok(()), + }; + + // update status. + { + // TODO: punish peer if they've moved backwards. + let status = &mut peer_info.status; + let last_head = status.head_hash; + status.head_hash = announcement.head_hash; + status.head_td = announcement.head_td; + status.head_num = announcement.head_num; + status.last_head = Some((last_head, announcement.reorg_depth)); + } + + // update capabilities. + { + let caps = &mut peer_info.capabilities; + caps.serve_headers = caps.serve_headers || announcement.serve_headers; + caps.serve_state_since = caps.serve_state_since.or(announcement.serve_state_since); + caps.serve_chain_since = caps.serve_chain_since.or(announcement.serve_chain_since); + caps.tx_relay = caps.tx_relay || announcement.tx_relay; + } + + // TODO: notify listeners if new best block. + + Ok(()) + } + + // Handle a request for block headers. + fn get_block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + const MAX_HEADERS: usize = 512; + + let mut present_buffer = match self.peers.read().get(peer) { + Some(peer) => peer.local_buffer.clone(), + None => { + debug!(target: "les", "Ignoring announcement from unknown peer"); + return Ok(()) + } + }; + + self.flow_params.recharge(&mut present_buffer); + let req_id: u64 = try!(data.val_at(0)); + + let req = request::Headers { + block: { + let rlp = try!(data.at(1)); + (try!(rlp.val_at(0)), try!(rlp.val_at(1))) + }, + max: ::std::cmp::min(MAX_HEADERS, try!(data.val_at(2))), + skip: try!(data.val_at(3)), + reverse: try!(data.val_at(4)), + }; + + let max_cost = self.flow_params.compute_cost(request::Kind::Headers, req.max); + try!(present_buffer.deduct_cost(max_cost)); + + let response = self.provider.block_headers(req); + let actual_cost = self.flow_params.compute_cost(request::Kind::Headers, response.len()); + + let cur_buffer = match self.peers.write().get_mut(peer) { + Some(peer) => { + self.flow_params.recharge(&mut peer.local_buffer); + try!(peer.local_buffer.deduct_cost(actual_cost)); + peer.local_buffer.current() + } + None => { + debug!(target: "les", "peer disconnected during serving of request."); + return Ok(()) + } + }; + + io.respond(packet::BLOCK_HEADERS, { + let mut stream = RlpStream::new_list(response.len() + 2); + stream.append(&req_id).append(&cur_buffer); + + for header in response { + stream.append_raw(&header, 1); + } + + stream.out() + }).map_err(Into::into) + } + + // Receive a response for block headers. + fn block_headers(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { + unimplemented!() + } + + // Handle a request for block bodies. + fn get_block_bodies(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + const MAX_BODIES: usize = 256; + + let mut present_buffer = match self.peers.read().get(peer) { + Some(peer) => peer.local_buffer.clone(), + None => { + debug!(target: "les", "Ignoring announcement from unknown peer"); + return Ok(()) + } + }; + + self.flow_params.recharge(&mut present_buffer); + let req_id: u64 = try!(data.val_at(0)); + + let req = request::Bodies { + block_hashes: try!(data.iter().skip(1).take(MAX_BODIES).map(|x| x.as_val()).collect()) + }; + + let max_cost = self.flow_params.compute_cost(request::Kind::Bodies, req.block_hashes.len()); + try!(present_buffer.deduct_cost(max_cost)); + + let response = self.provider.block_bodies(req); + let response_len = response.iter().filter(|x| &x[..] != &::rlp::EMPTY_LIST_RLP).count(); + let actual_cost = self.flow_params.compute_cost(request::Kind::Bodies, response_len); + + let cur_buffer = match self.peers.write().get_mut(peer) { + Some(peer) => { + self.flow_params.recharge(&mut peer.local_buffer); + try!(peer.local_buffer.deduct_cost(actual_cost)); + peer.local_buffer.current() + } + None => { + debug!(target: "les", "peer disconnected during serving of request."); + return Ok(()) + } + }; + + io.respond(packet::BLOCK_BODIES, { + let mut stream = RlpStream::new_list(response.len() + 2); + stream.append(&req_id).append(&cur_buffer); + + for body in response { + stream.append_raw(&body, 1); + } + + stream.out() + }).map_err(Into::into) + } + + // Receive a response for block bodies. + fn block_bodies(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { + unimplemented!() + } + + // Handle a request for receipts. + fn get_receipts(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { + unimplemented!() + } + + // Receive a response for receipts. + fn receipts(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { + unimplemented!() + } + + // Handle a request for proofs. + fn get_proofs(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { + unimplemented!() + } + + // Receive a response for proofs. + fn proofs(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { + unimplemented!() + } + + // Handle a request for contract code. + fn get_contract_code(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { + unimplemented!() + } + + // Receive a response for contract code. + fn contract_code(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { + unimplemented!() + } + + // Handle a request for header proofs + fn get_header_proofs(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { + unimplemented!() + } + + // Receive a response for header proofs + fn header_proofs(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { + unimplemented!() + } + + // Receive a set of transactions to relay. + fn relay_transactions(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { + unimplemented!() + } +} + +impl NetworkProtocolHandler for LightProtocol { + fn initialize(&self, io: &NetworkContext) { + io.register_timer(TIMEOUT, TIMEOUT_INTERVAL_MS).expect("Error registering sync timer."); + } + + fn read(&self, io: &NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) { + let rlp = UntrustedRlp::new(data); + + // handle the packet + let res = match packet_id { + packet::STATUS => self.status(peer, rlp), + packet::ANNOUNCE => self.announcement(peer, rlp), + + packet::GET_BLOCK_HEADERS => self.get_block_headers(peer, io, rlp), + packet::BLOCK_HEADERS => self.block_headers(peer, io, rlp), + + packet::GET_BLOCK_BODIES => self.get_block_bodies(peer, io, rlp), + packet::BLOCK_BODIES => self.block_bodies(peer, io, rlp), + + packet::GET_RECEIPTS => self.get_receipts(peer, io, rlp), + packet::RECEIPTS => self.receipts(peer, io, rlp), + + packet::GET_PROOFS => self.get_proofs(peer, io, rlp), + packet::PROOFS => self.proofs(peer, io, rlp), + + packet::GET_CONTRACT_CODES => self.get_contract_code(peer, io, rlp), + packet::CONTRACT_CODES => self.contract_code(peer, io, rlp), + + packet::GET_HEADER_PROOFS => self.get_header_proofs(peer, io, rlp), + packet::HEADER_PROOFS => self.header_proofs(peer, io, rlp), + + packet::SEND_TRANSACTIONS => self.relay_transactions(peer, io, rlp), + + other => { + Err(Error::UnrecognizedPacket(other)) + } + }; + + // if something went wrong, figure out how much to punish the peer. + if let Err(e) = res { + match e.punishment() { + Punishment::None => {} + Punishment::Disconnect => { + debug!(target: "les", "Disconnecting peer {}: {}", peer, e); + io.disconnect_peer(*peer) + } + Punishment::Disable => { + debug!(target: "les", "Disabling peer {}: {}", peer, e); + io.disable_peer(*peer) + } + } + } + } + + fn connected(&self, io: &NetworkContext, peer: &PeerId) { + self.on_connect(peer, io); + } + + fn disconnected(&self, _io: &NetworkContext, peer: &PeerId) { + self.on_disconnect(*peer); + } + + fn timeout(&self, _io: &NetworkContext, timer: TimerToken) { + match timer { + TIMEOUT => { + // broadcast transactions to peers. + } + _ => warn!(target: "les", "received timeout on unknown token {}", timer), + } + } +} \ No newline at end of file diff --git a/ethcore/light/src/net/status.rs b/ethcore/light/src/net/status.rs new file mode 100644 index 000000000..5aaea9f3a --- /dev/null +++ b/ethcore/light/src/net/status.rs @@ -0,0 +1,539 @@ +// 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 . + +//! Peer status and capabilities. + +use rlp::{DecoderError, RlpDecodable, RlpEncodable, RlpStream, Stream, UntrustedRlp, View}; +use util::{H256, U256}; + +use super::buffer_flow::FlowParams; + +// recognized handshake/announcement keys. +// unknown keys are to be skipped, known keys have a defined order. +// their string values are defined in the LES spec. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)] +enum Key { + ProtocolVersion, + NetworkId, + HeadTD, + HeadHash, + HeadNum, + GenesisHash, + ServeHeaders, + ServeChainSince, + ServeStateSince, + TxRelay, + BufferLimit, + BufferCostTable, + BufferRechargeRate, +} + +impl Key { + // get the string value of this key. + fn as_str(&self) -> &'static str { + match *self { + Key::ProtocolVersion => "protocolVersion", + Key::NetworkId => "networkId", + Key::HeadTD => "headTd", + Key::HeadHash => "headHash", + Key::HeadNum => "headNum", + Key::GenesisHash => "genesisHash", + Key::ServeHeaders => "serveHeaders", + Key::ServeChainSince => "serveChainSince", + Key::ServeStateSince => "serveStateSince", + Key::TxRelay => "txRelay", + Key::BufferLimit => "flowControl/BL", + Key::BufferCostTable => "flowControl/MRC", + Key::BufferRechargeRate => "flowControl/MRR", + } + } + + // try to parse the key value from a string. + fn from_str(s: &str) -> Option { + match s { + "protocolVersion" => Some(Key::ProtocolVersion), + "networkId" => Some(Key::NetworkId), + "headTd" => Some(Key::HeadTD), + "headHash" => Some(Key::HeadHash), + "headNum" => Some(Key::HeadNum), + "genesisHash" => Some(Key::GenesisHash), + "serveHeaders" => Some(Key::ServeHeaders), + "serveChainSince" => Some(Key::ServeChainSince), + "serveStateSince" => Some(Key::ServeStateSince), + "txRelay" => Some(Key::TxRelay), + "flowControl/BL" => Some(Key::BufferLimit), + "flowControl/MRC" => Some(Key::BufferCostTable), + "flowControl/MRR" => Some(Key::BufferRechargeRate), + _ => None + } + } +} + +/// Network ID structure. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum NetworkId { + /// ID for the mainnet + Mainnet = 1, + /// ID for the testnet + Testnet = 0, +} + +impl NetworkId { + fn from_raw(raw: u32) -> Option { + match raw { + 0 => Some(NetworkId::Testnet), + 1 => Some(NetworkId::Mainnet), + _ => None, + } + } +} + +// helper for decoding key-value pairs in the handshake or an announcement. +struct Parser<'a> { + pos: usize, + rlp: UntrustedRlp<'a>, +} + +impl<'a> Parser<'a> { + // expect a specific next key, and decode the value. + // error on unexpected key or invalid value. + fn expect(&mut self, key: Key) -> Result { + self.expect_raw(key).and_then(|item| item.as_val()) + } + + // expect a specific next key, and get the value's RLP. + // if the key isn't found, the position isn't advanced. + fn expect_raw(&mut self, key: Key) -> Result, DecoderError> { + let pre_pos = self.pos; + if let Some((k, val)) = try!(self.get_next()) { + if k == key { return Ok(val) } + } + + self.pos = pre_pos; + Err(DecoderError::Custom("Missing expected key")) + } + + // get the next key and value RLP. + fn get_next(&mut self) -> Result)>, DecoderError> { + while self.pos < self.rlp.item_count() { + let pair = try!(self.rlp.at(self.pos)); + let k: String = try!(pair.val_at(0)); + + self.pos += 1; + match Key::from_str(&k) { + Some(key) => return Ok(Some((key , try!(pair.at(1))))), + None => continue, + } + } + + Ok(None) + } +} + +// Helper for encoding a key-value pair +fn encode_pair(key: Key, val: &T) -> Vec { + let mut s = RlpStream::new_list(2); + s.append(&key.as_str()).append(val); + s.out() +} + +// Helper for encoding a flag. +fn encode_flag(key: Key) -> Vec { + let mut s = RlpStream::new_list(2); + s.append(&key.as_str()).append_empty_data(); + s.out() +} + +/// A peer status message. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Status { + /// Protocol version. + pub protocol_version: u32, + /// Network id of this peer. + pub network_id: NetworkId, + /// Total difficulty of the head of the chain. + pub head_td: U256, + /// Hash of the best block. + pub head_hash: H256, + /// Number of the best block. + pub head_num: u64, + /// Genesis hash + pub genesis_hash: H256, + /// Last announced chain head and reorg depth to common ancestor. + pub last_head: Option<(H256, u64)>, +} + +/// Peer capabilities. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Capabilities { + /// Whether this peer can serve headers + pub serve_headers: bool, + /// Earliest block number it can serve block/receipt requests for. + pub serve_chain_since: Option, + /// Earliest block number it can serve state requests for. + pub serve_state_since: Option, + /// Whether it can relay transactions to the eth network. + pub tx_relay: bool, +} + +impl Default for Capabilities { + fn default() -> Self { + Capabilities { + serve_headers: true, + serve_chain_since: None, + serve_state_since: None, + tx_relay: false, + } + } +} + +/// Attempt to parse a handshake message into its three parts: +/// - chain status +/// - serving capabilities +/// - buffer flow parameters +pub fn parse_handshake(rlp: UntrustedRlp) -> Result<(Status, Capabilities, FlowParams), DecoderError> { + let mut parser = Parser { + pos: 0, + rlp: rlp, + }; + + let status = Status { + protocol_version: try!(parser.expect(Key::ProtocolVersion)), + network_id: try!(parser.expect(Key::NetworkId) + .and_then(|id: u32| NetworkId::from_raw(id).ok_or(DecoderError::Custom("Invalid network ID")))), + head_td: try!(parser.expect(Key::HeadTD)), + head_hash: try!(parser.expect(Key::HeadHash)), + head_num: try!(parser.expect(Key::HeadNum)), + genesis_hash: try!(parser.expect(Key::GenesisHash)), + last_head: None, + }; + + let capabilities = Capabilities { + serve_headers: parser.expect_raw(Key::ServeHeaders).is_ok(), + serve_chain_since: parser.expect(Key::ServeChainSince).ok(), + serve_state_since: parser.expect(Key::ServeStateSince).ok(), + tx_relay: parser.expect_raw(Key::TxRelay).is_ok(), + }; + + let flow_params = FlowParams::new( + try!(parser.expect(Key::BufferLimit)), + try!(parser.expect(Key::BufferCostTable)), + try!(parser.expect(Key::BufferRechargeRate)), + ); + + Ok((status, capabilities, flow_params)) +} + +/// Write a handshake, given status, capabilities, and flow parameters. +pub fn write_handshake(status: &Status, capabilities: &Capabilities, flow_params: &FlowParams) -> Vec { + let mut pairs = Vec::new(); + pairs.push(encode_pair(Key::ProtocolVersion, &status.protocol_version)); + pairs.push(encode_pair(Key::NetworkId, &(status.network_id as u32))); + pairs.push(encode_pair(Key::HeadTD, &status.head_td)); + pairs.push(encode_pair(Key::HeadHash, &status.head_hash)); + pairs.push(encode_pair(Key::HeadNum, &status.head_num)); + pairs.push(encode_pair(Key::GenesisHash, &status.genesis_hash)); + + if capabilities.serve_headers { + pairs.push(encode_flag(Key::ServeHeaders)); + } + if let Some(ref serve_chain_since) = capabilities.serve_chain_since { + pairs.push(encode_pair(Key::ServeChainSince, serve_chain_since)); + } + if let Some(ref serve_state_since) = capabilities.serve_state_since { + pairs.push(encode_pair(Key::ServeStateSince, serve_state_since)); + } + if capabilities.tx_relay { + pairs.push(encode_flag(Key::TxRelay)); + } + + pairs.push(encode_pair(Key::BufferLimit, flow_params.limit())); + pairs.push(encode_pair(Key::BufferCostTable, flow_params.cost_table())); + pairs.push(encode_pair(Key::BufferRechargeRate, flow_params.recharge_rate())); + + let mut stream = RlpStream::new_list(pairs.len()); + + for pair in pairs { + stream.append_raw(&pair, 1); + } + + stream.out() +} + +/// An announcement of new chain head or capabilities made by a peer. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Announcement { + /// Hash of the best block. + pub head_hash: H256, + /// Number of the best block. + pub head_num: u64, + /// Head total difficulty + pub head_td: U256, + /// reorg depth to common ancestor of last announced head. + pub reorg_depth: u64, + /// optional new header-serving capability. false means "no change" + pub serve_headers: bool, + /// optional new state-serving capability + pub serve_state_since: Option, + /// optional new chain-serving capability + pub serve_chain_since: Option, + /// optional new transaction-relay capability. false means "no change" + pub tx_relay: bool, + // TODO: changes in buffer flow? +} + +/// Parse an announcement. +pub fn parse_announcement(rlp: UntrustedRlp) -> Result { + let mut last_key = None; + + let mut announcement = Announcement { + head_hash: try!(rlp.val_at(0)), + head_num: try!(rlp.val_at(1)), + head_td: try!(rlp.val_at(2)), + reorg_depth: try!(rlp.val_at(3)), + serve_headers: false, + serve_state_since: None, + serve_chain_since: None, + tx_relay: false, + }; + + let mut parser = Parser { + pos: 4, + rlp: rlp, + }; + + while let Some((key, item)) = try!(parser.get_next()) { + if Some(key) <= last_key { return Err(DecoderError::Custom("Invalid announcement key ordering")) } + last_key = Some(key); + + match key { + Key::ServeHeaders => announcement.serve_headers = true, + Key::ServeStateSince => announcement.serve_state_since = Some(try!(item.as_val())), + Key::ServeChainSince => announcement.serve_chain_since = Some(try!(item.as_val())), + Key::TxRelay => announcement.tx_relay = true, + _ => return Err(DecoderError::Custom("Nonsensical key in announcement")), + } + } + + Ok(announcement) +} + +/// Write an announcement out. +pub fn write_announcement(announcement: &Announcement) -> Vec { + let mut pairs = Vec::new(); + if announcement.serve_headers { + pairs.push(encode_flag(Key::ServeHeaders)); + } + if let Some(ref serve_chain_since) = announcement.serve_chain_since { + pairs.push(encode_pair(Key::ServeChainSince, serve_chain_since)); + } + if let Some(ref serve_state_since) = announcement.serve_state_since { + pairs.push(encode_pair(Key::ServeStateSince, serve_state_since)); + } + if announcement.tx_relay { + pairs.push(encode_flag(Key::TxRelay)); + } + + let mut stream = RlpStream::new_list(4 + pairs.len()); + stream + .append(&announcement.head_hash) + .append(&announcement.head_num) + .append(&announcement.head_td) + .append(&announcement.reorg_depth); + + for item in pairs { + stream.append_raw(&item, 1); + } + + stream.out() +} + +#[cfg(test)] +mod tests { + use super::*; + use super::super::buffer_flow::FlowParams; + use util::{U256, H256, FixedHash}; + use rlp::{RlpStream, Stream ,UntrustedRlp, View}; + + #[test] + fn full_handshake() { + let status = Status { + protocol_version: 1, + network_id: NetworkId::Mainnet, + head_td: U256::default(), + head_hash: H256::default(), + head_num: 10, + genesis_hash: H256::zero(), + last_head: None, + }; + + let capabilities = Capabilities { + serve_headers: true, + serve_chain_since: Some(5), + serve_state_since: Some(8), + tx_relay: true, + }; + + let flow_params = FlowParams::new( + 1_000_000.into(), + Default::default(), + 1000.into(), + ); + + let handshake = write_handshake(&status, &capabilities, &flow_params); + + let (read_status, read_capabilities, read_flow) + = parse_handshake(UntrustedRlp::new(&handshake)).unwrap(); + + assert_eq!(read_status, status); + assert_eq!(read_capabilities, capabilities); + assert_eq!(read_flow, flow_params); + } + + #[test] + fn partial_handshake() { + let status = Status { + protocol_version: 1, + network_id: NetworkId::Mainnet, + head_td: U256::default(), + head_hash: H256::default(), + head_num: 10, + genesis_hash: H256::zero(), + last_head: None, + }; + + let capabilities = Capabilities { + serve_headers: false, + serve_chain_since: Some(5), + serve_state_since: None, + tx_relay: true, + }; + + let flow_params = FlowParams::new( + 1_000_000.into(), + Default::default(), + 1000.into(), + ); + + let handshake = write_handshake(&status, &capabilities, &flow_params); + + let (read_status, read_capabilities, read_flow) + = parse_handshake(UntrustedRlp::new(&handshake)).unwrap(); + + assert_eq!(read_status, status); + assert_eq!(read_capabilities, capabilities); + assert_eq!(read_flow, flow_params); + } + + #[test] + fn skip_unknown_keys() { + let status = Status { + protocol_version: 1, + network_id: NetworkId::Mainnet, + head_td: U256::default(), + head_hash: H256::default(), + head_num: 10, + genesis_hash: H256::zero(), + last_head: None, + }; + + let capabilities = Capabilities { + serve_headers: false, + serve_chain_since: Some(5), + serve_state_since: None, + tx_relay: true, + }; + + let flow_params = FlowParams::new( + 1_000_000.into(), + Default::default(), + 1000.into(), + ); + + let handshake = write_handshake(&status, &capabilities, &flow_params); + let interleaved = { + let handshake = UntrustedRlp::new(&handshake); + let mut stream = RlpStream::new_list(handshake.item_count() * 3); + + for item in handshake.iter() { + stream.append_raw(item.as_raw(), 1); + let (mut s1, mut s2) = (RlpStream::new_list(2), RlpStream::new_list(2)); + s1.append(&"foo").append_empty_data(); + s2.append(&"bar").append_empty_data(); + stream.append_raw(&s1.out(), 1); + stream.append_raw(&s2.out(), 1); + } + + stream.out() + }; + + let (read_status, read_capabilities, read_flow) + = parse_handshake(UntrustedRlp::new(&interleaved)).unwrap(); + + assert_eq!(read_status, status); + assert_eq!(read_capabilities, capabilities); + assert_eq!(read_flow, flow_params); + } + + #[test] + fn announcement_roundtrip() { + let announcement = Announcement { + head_hash: H256::random(), + head_num: 100_000, + head_td: 1_000_000.into(), + reorg_depth: 4, + serve_headers: false, + serve_state_since: Some(99_000), + serve_chain_since: Some(1), + tx_relay: true, + }; + + let serialized = write_announcement(&announcement); + let read = parse_announcement(UntrustedRlp::new(&serialized)).unwrap(); + + assert_eq!(read, announcement); + } + + #[test] + fn keys_out_of_order() { + use super::{Key, encode_pair, encode_flag}; + + let mut stream = RlpStream::new_list(6); + stream + .append(&H256::zero()) + .append(&10u64) + .append(&100_000u64) + .append(&2u64) + .append_raw(&encode_pair(Key::ServeStateSince, &44u64), 1) + .append_raw(&encode_flag(Key::ServeHeaders), 1); + + let out = stream.drain(); + assert!(parse_announcement(UntrustedRlp::new(&out)).is_err()); + + let mut stream = RlpStream::new_list(6); + stream + .append(&H256::zero()) + .append(&10u64) + .append(&100_000u64) + .append(&2u64) + .append_raw(&encode_flag(Key::ServeHeaders), 1) + .append_raw(&encode_pair(Key::ServeStateSince, &44u64), 1); + + let out = stream.drain(); + assert!(parse_announcement(UntrustedRlp::new(&out)).is_ok()); + } +} \ No newline at end of file diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs new file mode 100644 index 000000000..b1625f95f --- /dev/null +++ b/ethcore/light/src/provider.rs @@ -0,0 +1,71 @@ +// 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 . + +//! A provider for the LES protocol. This is typically a full node, who can +//! give as much data as necessary to its peers. + +use ethcore::transaction::SignedTransaction; +use ethcore::blockchain_info::BlockChainInfo; +use util::{Bytes, H256}; + +use request; + +/// Defines the operations that a provider for `LES` must fulfill. +/// +/// These are defined at [1], but may be subject to change. +/// Requests which can't be fulfilled should return an empty RLP list. +/// +/// [1]: https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) +pub trait Provider: Send + Sync { + /// Provide current blockchain info. + fn chain_info(&self) -> BlockChainInfo; + + /// Find the depth of a common ancestor between two blocks. + fn reorg_depth(&self, a: &H256, b: &H256) -> Option; + + /// Earliest state. + fn earliest_state(&self) -> Option; + + /// Provide a list of headers starting at the requested block, + /// possibly in reverse and skipping `skip` at a time. + /// + /// The returned vector may have any length in the range [0, `max`], but the + /// results within must adhere to the `skip` and `reverse` parameters. + fn block_headers(&self, req: request::Headers) -> Vec; + + /// Provide as many as possible of the requested blocks (minus the headers) encoded + /// in RLP format. + fn block_bodies(&self, req: request::Bodies) -> Vec; + + /// Provide the receipts as many as possible of the requested blocks. + /// Returns a vector of RLP-encoded lists of receipts. + fn receipts(&self, req: request::Receipts) -> Vec; + + /// Provide a set of merkle proofs, as requested. Each request is a + /// block hash and request parameters. + /// + /// Returns a vector to RLP-encoded lists satisfying the requests. + fn proofs(&self, req: request::StateProofs) -> Vec; + + /// Provide contract code for the specified (block_hash, account_hash) pairs. + fn code(&self, req: request::ContractCodes) -> Vec; + + /// Provide header proofs from the Canonical Hash Tries. + fn header_proofs(&self, req: request::HeaderProofs) -> Vec; + + /// Provide pending transactions. + fn pending_transactions(&self) -> Vec; +} \ No newline at end of file diff --git a/ethcore/light/src/request.rs b/ethcore/light/src/request.rs new file mode 100644 index 000000000..f043f0f25 --- /dev/null +++ b/ethcore/light/src/request.rs @@ -0,0 +1,145 @@ +// 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 . + +//! LES request types. + +// TODO: make IPC compatible. + +use util::H256; + +/// A request for block headers. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Headers { + /// Block information for the request being made. + pub block: (u64, H256), + /// The maximum amount of headers which can be returned. + pub max: usize, + /// The amount of headers to skip between each response entry. + pub skip: usize, + /// Whether the headers should proceed in falling number from the initial block. + pub reverse: bool, +} + +/// A request for specific block bodies. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Bodies { + /// Hashes which bodies are being requested for. + pub block_hashes: Vec +} + +/// A request for transaction receipts. +/// +/// This request is answered with a list of transaction receipts for each block +/// requested. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Receipts { + /// Block hashes to return receipts for. + pub block_hashes: Vec, +} + +/// A request for a state proof +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StateProof { + /// Block hash to query state from. + pub block: H256, + /// Key of the state trie -- corresponds to account hash. + pub key1: H256, + /// Key in that account's storage trie; if empty, then the account RLP should be + /// returned. + pub key2: Option, + /// if greater than zero, trie nodes beyond this level may be omitted. + pub from_level: u32, // could even safely be u8; trie w/ 32-byte key can be at most 64-levels deep. +} + +/// A request for state proofs. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StateProofs { + /// All the proof requests. + pub requests: Vec, +} + +/// A request for contract code. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ContractCodes { + /// Block hash and account key (== sha3(address)) pairs to fetch code for. + pub code_requests: Vec<(H256, H256)>, +} + +/// A request for a header proof from the Canonical Hash Trie. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct HeaderProof { + /// Number of the CHT. + pub cht_number: u64, + /// Block number requested. + pub block_number: u64, + /// If greater than zero, trie nodes beyond this level may be omitted. + pub from_level: u32, +} + +/// A request for header proofs from the CHT. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct HeaderProofs { + /// All the proof requests. + pub requests: Vec, +} + +/// Kinds of requests. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Kind { + /// Requesting headers. + Headers, + /// Requesting block bodies. + Bodies, + /// Requesting transaction receipts. + Receipts, + /// Requesting proofs of state trie nodes. + StateProofs, + /// Requesting contract code by hash. + Codes, + /// Requesting header proofs (from the CHT). + HeaderProofs, +} + +/// Encompasses all possible types of requests in a single structure. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Request { + /// Requesting headers. + Headers(Headers), + /// Requesting block bodies. + Bodies(Bodies), + /// Requesting transaction receipts. + Receipts(Receipts), + /// Requesting state proofs. + StateProofs(StateProofs), + /// Requesting contract codes. + Codes(ContractCodes), + /// Requesting header proofs. + HeaderProofs(HeaderProofs), +} + +impl Request { + /// Get the kind of request this is. + pub fn kind(&self) -> Kind { + match *self { + Request::Headers(_) => Kind::Headers, + Request::Bodies(_) => Kind::Bodies, + Request::Receipts(_) => Kind::Receipts, + Request::StateProofs(_) => Kind::StateProofs, + Request::Codes(_) => Kind::Codes, + Request::HeaderProofs(_) => Kind::HeaderProofs, + } + } +} \ No newline at end of file diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index c7f40418c..bf3e59171 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -137,6 +137,7 @@ pub mod miner; pub mod snapshot; pub mod action_params; pub mod db; +pub mod verification; #[macro_use] pub mod evm; mod cache_manager; @@ -150,7 +151,6 @@ mod account_db; mod builtin; mod executive; mod externalities; -mod verification; mod blockchain; mod types; mod factory; diff --git a/ethcore/src/types/mod.rs.in b/ethcore/src/types/mod.rs.in index 1d2cdb3c0..6ef67009a 100644 --- a/ethcore/src/types/mod.rs.in +++ b/ethcore/src/types/mod.rs.in @@ -33,4 +33,4 @@ pub mod transaction_import; pub mod block_import_error; pub mod restoration_status; pub mod snapshot_manifest; -pub mod mode; \ No newline at end of file +pub mod mode; diff --git a/ethcore/src/verification/canon_verifier.rs b/ethcore/src/verification/canon_verifier.rs index cc6bc448a..b5b01279e 100644 --- a/ethcore/src/verification/canon_verifier.rs +++ b/ethcore/src/verification/canon_verifier.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! Canonical verifier. + use blockchain::BlockProvider; use engines::Engine; use error::Error; @@ -21,6 +23,7 @@ use header::Header; use super::Verifier; use super::verification; +/// A canonial verifier -- this does full verification. pub struct CanonVerifier; impl Verifier for CanonVerifier { diff --git a/ethcore/src/verification/mod.rs b/ethcore/src/verification/mod.rs index 239c88597..55663052b 100644 --- a/ethcore/src/verification/mod.rs +++ b/ethcore/src/verification/mod.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! Block verification utilities. + pub mod verification; pub mod verifier; pub mod queue; @@ -44,6 +46,7 @@ impl Default for VerifierType { } } +/// Create a new verifier based on type. pub fn new(v: VerifierType) -> Box { match v { VerifierType::Canon | VerifierType::CanonNoSeal => Box::new(CanonVerifier), diff --git a/ethcore/src/verification/noop_verifier.rs b/ethcore/src/verification/noop_verifier.rs index fb798be46..7db688a85 100644 --- a/ethcore/src/verification/noop_verifier.rs +++ b/ethcore/src/verification/noop_verifier.rs @@ -14,12 +14,15 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! No-op verifier. + use blockchain::BlockProvider; use engines::Engine; use error::Error; use header::Header; use super::Verifier; +/// A no-op verifier -- this will verify everything it's given immediately. #[allow(dead_code)] pub struct NoopVerifier; diff --git a/ethcore/src/verification/verification.rs b/ethcore/src/verification/verification.rs index bb9f042ae..47b2e16de 100644 --- a/ethcore/src/verification/verification.rs +++ b/ethcore/src/verification/verification.rs @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -/// Block and transaction verification functions -/// -/// Block verification is done in 3 steps -/// 1. Quick verification upon adding to the block queue -/// 2. Signatures verification done in the queue. -/// 3. Final verification against the blockchain done before enactment. +//! Block and transaction verification functions +//! +//! Block verification is done in 3 steps +//! 1. Quick verification upon adding to the block queue +//! 2. Signatures verification done in the queue. +//! 3. Final verification against the blockchain done before enactment. use util::*; use engines::Engine; diff --git a/ethcore/src/verification/verifier.rs b/ethcore/src/verification/verifier.rs index 7f57407f7..05d488f95 100644 --- a/ethcore/src/verification/verifier.rs +++ b/ethcore/src/verification/verifier.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! A generic verifier trait. + use blockchain::BlockProvider; use engines::Engine; use error::Error; @@ -21,6 +23,8 @@ use header::Header; /// Should be used to verify blocks. pub trait Verifier: Send + Sync { + /// Verify a block relative to its parent and uncles. fn verify_block_family(&self, header: &Header, bytes: &[u8], engine: &Engine, bc: &BlockProvider) -> Result<(), Error>; + /// Do a final verification check for an enacted header vs its expected counterpart. fn verify_block_final(&self, expected: &Header, got: &Header) -> Result<(), Error>; } diff --git a/sync/src/sync_io.rs b/sync/src/sync_io.rs index c78074aed..8dc8c65c0 100644 --- a/sync/src/sync_io.rs +++ b/sync/src/sync_io.rs @@ -22,7 +22,7 @@ use ethcore::header::BlockNumber; use ethcore::snapshot::SnapshotService; use parking_lot::RwLock; -/// IO interface for the syning handler. +/// IO interface for the syncing handler. /// Provides peer connection management and an interface to the blockchain client. // TODO: ratings pub trait SyncIo { @@ -70,7 +70,7 @@ pub struct NetSyncIo<'s, 'h> where 'h: 's { impl<'s, 'h> NetSyncIo<'s, 'h> { /// Creates a new instance from the `NetworkContext` and the blockchain client reference. - pub fn new(network: &'s NetworkContext<'h>, + pub fn new(network: &'s NetworkContext<'h>, chain: &'s BlockChainClient, snapshot_service: &'s SnapshotService, chain_overlay: &'s RwLock>) -> NetSyncIo<'s, 'h> { diff --git a/util/fetch/src/lib.rs b/util/fetch/src/lib.rs index 8ec9e0ddd..7ab38604b 100644 --- a/util/fetch/src/lib.rs +++ b/util/fetch/src/lib.rs @@ -26,4 +26,4 @@ extern crate rand; pub mod client; pub mod fetch_file; -pub use self::client::{Client, Fetch, FetchError, FetchResult}; +pub use self::client::{Client, Fetch, FetchError, FetchResult}; \ No newline at end of file From cb84e61d06fc3eab53e94e45c42c0c855cad2098 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Thu, 10 Nov 2016 18:44:11 +0100 Subject: [PATCH 31/55] Fixed Unregister for Contract Owner only #3321 (#3346) --- js/src/dapps/tokenreg/Tokens/Token/token.js | 3 ++- js/src/dapps/tokenreg/Tokens/tokens.js | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/js/src/dapps/tokenreg/Tokens/Token/token.js b/js/src/dapps/tokenreg/Tokens/Token/token.js index 18942b085..34550ef48 100644 --- a/js/src/dapps/tokenreg/Tokens/Token/token.js +++ b/js/src/dapps/tokenreg/Tokens/Token/token.js @@ -57,6 +57,7 @@ export default class Token extends Component { isLoading: PropTypes.bool, isPending: PropTypes.bool, isTokenOwner: PropTypes.bool.isRequired, + isContractOwner: PropTypes.bool.isRequired, fullWidth: PropTypes.bool }; @@ -220,7 +221,7 @@ export default class Token extends Component { } renderUnregister () { - if (!this.props.isTokenOwner) { + if (!this.props.isContractOwner) { return null; } diff --git a/js/src/dapps/tokenreg/Tokens/tokens.js b/js/src/dapps/tokenreg/Tokens/tokens.js index 57c2a2a91..43766a8a8 100644 --- a/js/src/dapps/tokenreg/Tokens/tokens.js +++ b/js/src/dapps/tokenreg/Tokens/tokens.js @@ -45,7 +45,7 @@ export default class Tokens extends Component { } renderTokens (tokens) { - const { accounts } = this.props; + const { accounts, isOwner } = this.props; return tokens.map((token, index) => { if (!token || !token.tla) { @@ -61,7 +61,8 @@ export default class Tokens extends Component { handleMetaLookup={ this.props.handleMetaLookup } handleAddMeta={ this.props.handleAddMeta } key={ index } - isTokenOwner={ isTokenOwner } /> + isTokenOwner={ isTokenOwner } + isContractOwner={ isOwner } /> ); }); } From eb56b82f43ea22ed686f8290c555c64c6e583d4b Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Thu, 10 Nov 2016 18:46:03 +0100 Subject: [PATCH 32/55] Check totalBalance > 0 // Better account selection (#3347) --- .../Accounts/AccountSelector/account-selector.js | 10 ++++++++-- js/src/dapps/tokenreg/Actions/Register/register.js | 10 +++++++++- js/src/dapps/tokenreg/Inputs/validation.js | 2 +- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/js/src/dapps/tokenreg/Accounts/AccountSelector/account-selector.js b/js/src/dapps/tokenreg/Accounts/AccountSelector/account-selector.js index 4c8525d7e..4d29a6692 100644 --- a/js/src/dapps/tokenreg/Accounts/AccountSelector/account-selector.js +++ b/js/src/dapps/tokenreg/Accounts/AccountSelector/account-selector.js @@ -70,7 +70,8 @@ export default class AccountSelector extends Component { static propTypes = { list: PropTypes.array.isRequired, selected: PropTypes.object.isRequired, - handleSetSelected: PropTypes.func.isRequired + handleSetSelected: PropTypes.func.isRequired, + onAccountChange: PropTypes.func }; state = { @@ -85,7 +86,8 @@ export default class AccountSelector extends Component { nestedItems={ nestedAccounts } open={ this.state.open } onSelectAccount={ this.onToggleOpen } - autoGenerateNestedIndicator={ false } /> + autoGenerateNestedIndicator={ false } + nestedListStyle={ { maxHeight: '14em', overflow: 'auto' } } /> ); return ( @@ -110,6 +112,10 @@ export default class AccountSelector extends Component { onToggleOpen = () => { this.setState({ open: !this.state.open }); + + if (typeof this.props.onAccountChange === 'function') { + this.props.onAccountChange(); + } } onSelectAccount = (address) => { diff --git a/js/src/dapps/tokenreg/Actions/Register/register.js b/js/src/dapps/tokenreg/Actions/Register/register.js index 5eb2d9e1b..8ae042494 100644 --- a/js/src/dapps/tokenreg/Actions/Register/register.js +++ b/js/src/dapps/tokenreg/Actions/Register/register.js @@ -81,6 +81,7 @@ export default class RegisterAction extends Component { className={ styles.dialog } onRequestClose={ this.onClose } actions={ this.renderActions() } + ref='dialog' autoScrollBodyContent > { this.renderContent() } @@ -149,7 +150,9 @@ export default class RegisterAction extends Component { renderForm () { return (
- + { this.renderInputs() }
); @@ -175,6 +178,11 @@ export default class RegisterAction extends Component { }); } + onAccountChange = () => { + const { dialog } = this.refs; + dialog.forceUpdate(); + } + onChange (fieldKey, valid, value) { const { fields } = this.state; const field = fields[fieldKey]; diff --git a/js/src/dapps/tokenreg/Inputs/validation.js b/js/src/dapps/tokenreg/Inputs/validation.js index b2e0688a8..38eba5ef1 100644 --- a/js/src/dapps/tokenreg/Inputs/validation.js +++ b/js/src/dapps/tokenreg/Inputs/validation.js @@ -75,7 +75,7 @@ const validateTokenAddress = (address, contract, simple) => { return getTokenTotalSupply(address) .then(balance => { - if (balance === null) { + if (balance === null || balance.equals(0)) { return { error: ERRORS.invalidTokenAddress, valid: false From 0456d4e5d0d2eb35d123321496df99b8b9ff7dfc Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 10 Nov 2016 18:08:55 +0000 Subject: [PATCH 33/55] [ci skip] js-precompiled 20161110-180734 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a2c441d6e..1f4053cea 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#c6691c86121806988c73d1847b3a0b77ee5b546e" +source = "git+https://github.com/ethcore/js-precompiled.git#c2b51f024ff2e97c07a1cf3171400ebaf1ecf9e6" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 7b57385a9..60bb43310 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.29", + "version": "0.2.30", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 0b176e701390fd04d0fe53b57f2eba146e0d33be Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 10 Nov 2016 20:45:03 +0100 Subject: [PATCH 34/55] Cater for home.parity hostname in dappsUrl (#3341) * Cater for home.parity hostname * Cater for 0.0.0.0 & default dappsInterface * Extra check --- js/src/secureApi.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/js/src/secureApi.js b/js/src/secureApi.js index 6bb9d38ea..ed665a5e6 100644 --- a/js/src/secureApi.js +++ b/js/src/secureApi.js @@ -26,6 +26,7 @@ export default class SecureApi extends Api { this._connectState = sysuiToken === 'initial' ? 1 : 0; this._needsToken = false; this._dappsPort = 8080; + this._dappsInterface = null; this._signerPort = 8180; console.log('SecureApi:constructor', sysuiToken); @@ -100,10 +101,12 @@ export default class SecureApi extends Api { Promise .all([ this.parity.dappsPort(), + this.parity.dappsInterface(), this.parity.signerPort() ]) - .then(([dappsPort, signerPort]) => { + .then(([dappsPort, dappsInterface, signerPort]) => { this._dappsPort = dappsPort.toNumber(); + this._dappsInterface = dappsInterface; this._signerPort = signerPort.toNumber(); }); @@ -122,7 +125,17 @@ export default class SecureApi extends Api { } get dappsUrl () { - return `http://${window.location.hostname}:${this._dappsPort}`; + let hostname; + + if (window.location.hostname === 'home.parity') { + hostname = 'dapps.parity'; + } else if (!this._dappsInterface || this._dappsInterface === '0.0.0.0') { + hostname = window.location.hostname; + } else { + hostname = this._dappsInterface; + } + + return `http://${hostname}:${this._dappsPort}`; } get signerPort () { From 7cc88b175f820d48ad78115244887dcfe5a2cd89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chuck=20LeDuc=20D=C3=ADaz?= Date: Fri, 11 Nov 2016 08:52:52 +0100 Subject: [PATCH 35/55] Clarify error message to indicate (#3359) Indicate that Signer *needs* to be re-enabled, not that it *was* re-enabled. --- dapps/src/router/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dapps/src/router/mod.rs b/dapps/src/router/mod.rs index cb9133886..c0a33f2eb 100644 --- a/dapps/src/router/mod.rs +++ b/dapps/src/router/mod.rs @@ -131,7 +131,7 @@ impl server::Handler for Router { StatusCode::NotFound, "404 Not Found", "Your homepage is not available when Trusted Signer is disabled.", - Some("You can still access dapps by writing a correct address, though. Re-enabled Signer to get your homepage back."), + Some("You can still access dapps by writing a correct address, though. Re-enable Signer to get your homepage back."), self.signer_address.clone(), )) } From c7b99cd7e6775ced1b77b35743f89fb5bd72bc58 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Fri, 11 Nov 2016 09:01:28 +0100 Subject: [PATCH 36/55] Determine real-time HTTP connected status (#3335) * Determine real-time HTTP connected status * Update failing tests, no polluting polling --- js/src/api/api.spec.js | 2 +- js/src/api/contract/contract.spec.js | 2 +- js/src/api/rpc/db/db.spec.js | 2 +- js/src/api/rpc/eth/eth.spec.js | 2 +- js/src/api/rpc/net/net.spec.js | 2 +- js/src/api/rpc/parity/parity.spec.js | 2 +- js/src/api/rpc/personal/personal.spec.js | 2 +- js/src/api/rpc/trace/trace.spec.js | 2 +- js/src/api/rpc/web3/web3.spec.js | 2 +- js/src/api/transport/http/http.js | 18 +++++++++++++++++- js/src/api/transport/http/http.spec.js | 2 +- 11 files changed, 27 insertions(+), 11 deletions(-) diff --git a/js/src/api/api.spec.js b/js/src/api/api.spec.js index bbd140d4d..16831ea66 100644 --- a/js/src/api/api.spec.js +++ b/js/src/api/api.spec.js @@ -34,7 +34,7 @@ describe('api/Api', () => { }); describe('interface', () => { - const api = new Api(new Api.Transport.Http(TEST_HTTP_URL)); + const api = new Api(new Api.Transport.Http(TEST_HTTP_URL, -1)); Object.keys(ethereumRpc).sort().forEach((endpoint) => { describe(endpoint, () => { diff --git a/js/src/api/contract/contract.spec.js b/js/src/api/contract/contract.spec.js index 96929cc11..3d57c2afa 100644 --- a/js/src/api/contract/contract.spec.js +++ b/js/src/api/contract/contract.spec.js @@ -25,7 +25,7 @@ import Api from '../api'; import Contract from './contract'; import { isInstanceOf, isFunction } from '../util/types'; -const transport = new Api.Transport.Http(TEST_HTTP_URL); +const transport = new Api.Transport.Http(TEST_HTTP_URL, -1); const eth = new Api(transport); describe('api/contract/Contract', () => { diff --git a/js/src/api/rpc/db/db.spec.js b/js/src/api/rpc/db/db.spec.js index 4379b51c4..4a11fc416 100644 --- a/js/src/api/rpc/db/db.spec.js +++ b/js/src/api/rpc/db/db.spec.js @@ -19,7 +19,7 @@ import { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc'; import Http from '../../transport/http'; import Db from './db'; -const instance = new Db(new Http(TEST_HTTP_URL)); +const instance = new Db(new Http(TEST_HTTP_URL, -1)); describe('api/rpc/Db', () => { let scope; diff --git a/js/src/api/rpc/eth/eth.spec.js b/js/src/api/rpc/eth/eth.spec.js index 65377db50..85d22f4bd 100644 --- a/js/src/api/rpc/eth/eth.spec.js +++ b/js/src/api/rpc/eth/eth.spec.js @@ -20,7 +20,7 @@ import { isBigNumber } from '../../../../test/types'; import Http from '../../transport/http'; import Eth from './eth'; -const instance = new Eth(new Http(TEST_HTTP_URL)); +const instance = new Eth(new Http(TEST_HTTP_URL, -1)); describe('rpc/Eth', () => { const address = '0x63Cf90D3f0410092FC0fca41846f596223979195'; diff --git a/js/src/api/rpc/net/net.spec.js b/js/src/api/rpc/net/net.spec.js index 55029a29e..4903a0cde 100644 --- a/js/src/api/rpc/net/net.spec.js +++ b/js/src/api/rpc/net/net.spec.js @@ -20,7 +20,7 @@ import { isBigNumber } from '../../../../test/types'; import Http from '../../transport/http'; import Net from './net'; -const instance = new Net(new Http(TEST_HTTP_URL)); +const instance = new Net(new Http(TEST_HTTP_URL, -1)); describe('api/rpc/Net', () => { describe('peerCount', () => { diff --git a/js/src/api/rpc/parity/parity.spec.js b/js/src/api/rpc/parity/parity.spec.js index ea0dd8d8c..557314e5c 100644 --- a/js/src/api/rpc/parity/parity.spec.js +++ b/js/src/api/rpc/parity/parity.spec.js @@ -20,7 +20,7 @@ import { isBigNumber } from '../../../../test/types'; import Http from '../../transport/http'; import Parity from './parity'; -const instance = new Parity(new Http(TEST_HTTP_URL)); +const instance = new Parity(new Http(TEST_HTTP_URL, -1)); describe('api/rpc/parity', () => { describe('accountsInfo', () => { diff --git a/js/src/api/rpc/personal/personal.spec.js b/js/src/api/rpc/personal/personal.spec.js index a9bf4f644..70c8cf4c2 100644 --- a/js/src/api/rpc/personal/personal.spec.js +++ b/js/src/api/rpc/personal/personal.spec.js @@ -19,7 +19,7 @@ import { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc'; import Http from '../../transport/http'; import Personal from './personal'; -const instance = new Personal(new Http(TEST_HTTP_URL)); +const instance = new Personal(new Http(TEST_HTTP_URL, -1)); describe('rpc/Personal', () => { const account = '0x63cf90d3f0410092fc0fca41846f596223979195'; diff --git a/js/src/api/rpc/trace/trace.spec.js b/js/src/api/rpc/trace/trace.spec.js index 4a38f7a3f..f36e5537c 100644 --- a/js/src/api/rpc/trace/trace.spec.js +++ b/js/src/api/rpc/trace/trace.spec.js @@ -19,7 +19,7 @@ import { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc'; import Http from '../../transport/http'; import Trace from './trace'; -const instance = new Trace(new Http(TEST_HTTP_URL)); +const instance = new Trace(new Http(TEST_HTTP_URL, -1)); describe('api/rpc/Trace', () => { let scope; diff --git a/js/src/api/rpc/web3/web3.spec.js b/js/src/api/rpc/web3/web3.spec.js index eb4a59cd1..b933e805b 100644 --- a/js/src/api/rpc/web3/web3.spec.js +++ b/js/src/api/rpc/web3/web3.spec.js @@ -19,7 +19,7 @@ import { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc'; import Http from '../../transport/http'; import Web3 from './web3'; -const instance = new Web3(new Http(TEST_HTTP_URL)); +const instance = new Web3(new Http(TEST_HTTP_URL, -1)); describe('api/rpc/Web3', () => { let scope; diff --git a/js/src/api/transport/http/http.js b/js/src/api/transport/http/http.js index 08d9422f8..8ea59f0fb 100644 --- a/js/src/api/transport/http/http.js +++ b/js/src/api/transport/http/http.js @@ -19,11 +19,14 @@ import JsonRpcBase from '../jsonRpcBase'; /* global fetch */ export default class Http extends JsonRpcBase { - constructor (url) { + constructor (url, connectTimeout = 1000) { super(); this._connected = true; this._url = url; + this._connectTimeout = connectTimeout; + + this._pollConnection(); } _encodeOptions (method, params) { @@ -77,4 +80,17 @@ export default class Http extends JsonRpcBase { return response.result; }); } + + _pollConnection = () => { + if (this._connectTimeout <= 0) { + return; + } + + const nextTimeout = () => setTimeout(this._pollConnection, this._connectTimeout); + + this + .execute('net_listening') + .then(nextTimeout) + .catch(nextTimeout); + } } diff --git a/js/src/api/transport/http/http.spec.js b/js/src/api/transport/http/http.spec.js index 94441bc51..718a7e66b 100644 --- a/js/src/api/transport/http/http.spec.js +++ b/js/src/api/transport/http/http.spec.js @@ -17,7 +17,7 @@ import { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc'; import Http from './http'; -const transport = new Http(TEST_HTTP_URL); +const transport = new Http(TEST_HTTP_URL, -1); describe('api/transport/Http', () => { describe('instance', () => { From 5d8f74ed57eaab3d43eb9669a657f4630685d55e Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Fri, 11 Nov 2016 08:22:24 +0000 Subject: [PATCH 37/55] [ci skip] js-precompiled 20161111-082057 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1f4053cea..2a8b2ce09 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#c2b51f024ff2e97c07a1cf3171400ebaf1ecf9e6" +source = "git+https://github.com/ethcore/js-precompiled.git#76e3cbf0c68acb72174e666216d15a5fff5100d6" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 60bb43310..549d55dc8 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.30", + "version": "0.2.31", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 0e4ef539fc441dfc663a36a517bc63cff3196bc2 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Fri, 11 Nov 2016 15:00:04 +0100 Subject: [PATCH 38/55] Solidity Compiler in UI (#3279) * Added new Deploy Contract page // Use Brace in React #2276 * Adding Web Wrokers WIP * Compiling Solidity code // Getting mandatory params #2276 * Working editor and deployment #2276 * WIP : displaying source code * Added Solidity hightling, editor component in UI * Re-adding the standard Deploy Modal #2276 * Using MobX in Contract Edition // Save to Localstorage #2276 * User select Solidity version #2276 * Loading Solidity versions and closing worker properly #2276 * Adds export to solidity editor #2276 * Adding Import to Contract Editor #2276 * Persistent Worker => Don't load twice Solidity Code #2276 * UI Fixes * Editor tweaks * Added Details with ABI in Contract view * Adds Save capabilities to contract editor // WIP on Load #3279 * Working Load and Save contracts... #3231 * Adding loader of Snippets // Export with name #3279 * Added snippets / Importing from files and from URL * Fix wrong ID in saved Contract * Fix lint * Fixed Formal errors as warning #3279 * Fixing lint issues * Use NPM Module for valid URL (fixes linting issue too) * Don't clobber tests. --- js/package.json | 9 +- .../snippets/human-standard-token.sol | 60 ++ js/src/contracts/snippets/standard-token.sol | 55 + js/src/contracts/snippets/token.sol | 47 + js/src/index.js | 3 +- .../DeployContract/DetailsStep/detailsStep.js | 31 +- .../modals/DeployContract/deployContract.js | 43 +- js/src/modals/LoadContract/index.js | 17 + js/src/modals/LoadContract/loadContract.css | 52 + js/src/modals/LoadContract/loadContract.js | 284 +++++ js/src/modals/SaveContract/index.js | 17 + js/src/modals/SaveContract/saveContract.css | 20 + js/src/modals/SaveContract/saveContract.js | 109 ++ js/src/modals/index.js | 6 +- js/src/redux/providers/compilerActions.js | 37 + js/src/redux/providers/compilerReducer.js | 29 + js/src/redux/providers/compilerWorker.js | 177 ++++ js/src/redux/providers/index.js | 1 + js/src/redux/reducers.js | 3 +- js/src/ui/Actionbar/Import/import.css | 45 + js/src/ui/Actionbar/Import/import.js | 198 ++++ js/src/ui/Actionbar/Import/index.js | 17 + js/src/ui/Button/button.js | 13 +- js/src/ui/Editor/editor.js | 103 ++ js/src/ui/Editor/index.js | 17 + js/src/ui/Editor/mode-solidity.js | 994 ++++++++++++++++++ js/src/ui/index.js | 4 + js/src/views/Accounts/List/list.js | 14 +- js/src/views/Application/TabBar/tabBar.js | 3 +- js/src/views/Application/application.css | 2 + js/src/views/Contract/contract.css | 9 + js/src/views/Contract/contract.js | 78 +- js/src/views/Contracts/contracts.js | 11 + js/src/views/WriteContract/index.js | 17 + js/src/views/WriteContract/writeContract.css | 174 +++ js/src/views/WriteContract/writeContract.js | 502 +++++++++ .../views/WriteContract/writeContractStore.js | 373 +++++++ js/src/views/index.js | 2 + 38 files changed, 3555 insertions(+), 21 deletions(-) create mode 100644 js/src/contracts/snippets/human-standard-token.sol create mode 100644 js/src/contracts/snippets/standard-token.sol create mode 100644 js/src/contracts/snippets/token.sol create mode 100644 js/src/modals/LoadContract/index.js create mode 100644 js/src/modals/LoadContract/loadContract.css create mode 100644 js/src/modals/LoadContract/loadContract.js create mode 100644 js/src/modals/SaveContract/index.js create mode 100644 js/src/modals/SaveContract/saveContract.css create mode 100644 js/src/modals/SaveContract/saveContract.js create mode 100644 js/src/redux/providers/compilerActions.js create mode 100644 js/src/redux/providers/compilerReducer.js create mode 100644 js/src/redux/providers/compilerWorker.js create mode 100644 js/src/ui/Actionbar/Import/import.css create mode 100644 js/src/ui/Actionbar/Import/import.js create mode 100644 js/src/ui/Actionbar/Import/index.js create mode 100644 js/src/ui/Editor/editor.js create mode 100644 js/src/ui/Editor/index.js create mode 100644 js/src/ui/Editor/mode-solidity.js create mode 100644 js/src/views/WriteContract/index.js create mode 100644 js/src/views/WriteContract/writeContract.css create mode 100644 js/src/views/WriteContract/writeContract.js create mode 100644 js/src/views/WriteContract/writeContractStore.js diff --git a/js/package.json b/js/package.json index 549d55dc8..573e623d0 100644 --- a/js/package.json +++ b/js/package.json @@ -100,6 +100,7 @@ "postcss-loader": "^0.8.1", "postcss-nested": "^1.0.0", "postcss-simple-vars": "^3.0.0", + "raw-loader": "^0.5.1", "react-addons-test-utils": "^15.3.0", "react-copy-to-clipboard": "^4.2.3", "react-hot-loader": "^1.3.0", @@ -118,6 +119,7 @@ "dependencies": { "bignumber.js": "^2.3.0", "blockies": "0.0.2", + "brace": "^0.9.0", "bytes": "^2.4.0", "chart.js": "^2.3.0", "es6-promise": "^3.2.1", @@ -138,9 +140,11 @@ "moment": "^2.14.1", "qs": "^6.3.0", "react": "^15.2.1", + "react-ace": "^4.0.0", "react-addons-css-transition-group": "^15.2.1", "react-chartjs-2": "^1.5.0", "react-dom": "^15.2.1", + "react-dropzone": "^3.7.3", "react-redux": "^4.4.5", "react-router": "^2.6.1", "react-router-redux": "^4.0.5", @@ -152,10 +156,13 @@ "redux-thunk": "^2.1.0", "rlp": "^2.0.0", "scryptsy": "^2.0.0", + "solc": "ngotchac/solc-js", "store": "^1.3.20", "utf8": "^2.1.1", + "valid-url": "^1.0.9", "validator": "^5.7.0", "web3": "^0.17.0-beta", - "whatwg-fetch": "^1.0.0" + "whatwg-fetch": "^1.0.0", + "worker-loader": "^0.7.1" } } diff --git a/js/src/contracts/snippets/human-standard-token.sol b/js/src/contracts/snippets/human-standard-token.sol new file mode 100644 index 000000000..db05bbc7d --- /dev/null +++ b/js/src/contracts/snippets/human-standard-token.sol @@ -0,0 +1,60 @@ +/* +This Token Contract implements the standard token functionality (https://github.com/ethereum/EIPs/issues/20) as well as the following OPTIONAL extras intended for use by humans. + +In other words. This is intended for deployment in something like a Token Factory or Mist wallet, and then used by humans. +Imagine coins, currencies, shares, voting weight, etc. +Machine-based, rapid creation of many tokens would not necessarily need these extra features or will be minted in other manners. + +1) Initial Finite Supply (upon creation one specifies how much is minted). +2) In the absence of a token registry: Optional Decimal, Symbol & Name. +3) Optional approveAndCall() functionality to notify a contract if an approval() has occurred. + +.*/ + +import "StandardToken.sol"; + +contract HumanStandardToken is StandardToken { + + function () { + //if ether is sent to this address, send it back. + throw; + } + + /* Public variables of the token */ + + /* + NOTE: + The following variables are OPTIONAL vanities. One does not have to include them. + They allow one to customise the token contract & in no way influences the core functionality. + Some wallets/interfaces might not even bother to look at this information. + */ + string public name; //fancy name: eg Simon Bucks + uint8 public decimals; //How many decimals to show. ie. There could 1000 base units with 3 decimals. Meaning 0.980 SBX = 980 base units. It's like comparing 1 wei to 1 ether. + string public symbol; //An identifier: eg SBX + string public version = 'H0.1'; //human 0.1 standard. Just an arbitrary versioning scheme. + + function HumanStandardToken( + uint256 _initialAmount, + string _tokenName, + uint8 _decimalUnits, + string _tokenSymbol + ) { + balances[msg.sender] = _initialAmount; // Give the creator all initial tokens + totalSupply = _initialAmount; // Update total supply + name = _tokenName; // Set the name for display purposes + decimals = _decimalUnits; // Amount of decimals for display purposes + symbol = _tokenSymbol; // Set the symbol for display purposes + } + + /* Approves and then calls the receiving contract */ + function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) { + allowed[msg.sender][_spender] = _value; + Approval(msg.sender, _spender, _value); + + //call the receiveApproval function on the contract you want to be notified. This crafts the function signature manually so one doesn't have to include a contract in here just for this. + //receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData) + //it is assumed that when does this that the call *should* succeed, otherwise one would use vanilla approve instead. + if(!_spender.call(bytes4(bytes32(sha3("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)) { throw; } + return true; + } +} diff --git a/js/src/contracts/snippets/standard-token.sol b/js/src/contracts/snippets/standard-token.sol new file mode 100644 index 000000000..3d91e5510 --- /dev/null +++ b/js/src/contracts/snippets/standard-token.sol @@ -0,0 +1,55 @@ +/* +You should inherit from StandardToken or, for a token like you would want to +deploy in something like Mist, see HumanStandardToken.sol. +(This implements ONLY the standard functions and NOTHING else. +If you deploy this, you won't have anything useful.) + +Implements ERC 20 Token standard: https://github.com/ethereum/EIPs/issues/20 +.*/ + +import "Token.sol"; + +contract StandardToken is Token { + + function transfer(address _to, uint256 _value) returns (bool success) { + //Default assumes totalSupply can't be over max (2^256 - 1). + //If your token leaves out totalSupply and can issue more tokens as time goes on, you need to check if it doesn't wrap. + //Replace the if with this one instead. + //if (balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]) { + if (balances[msg.sender] >= _value && _value > 0) { + balances[msg.sender] -= _value; + balances[_to] += _value; + Transfer(msg.sender, _to, _value); + return true; + } else { return false; } + } + + function transferFrom(address _from, address _to, uint256 _value) returns (bool success) { + //same as above. Replace this line with the following if you want to protect against wrapping uints. + //if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value > balances[_to]) { + if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && _value > 0) { + balances[_to] += _value; + balances[_from] -= _value; + allowed[_from][msg.sender] -= _value; + Transfer(_from, _to, _value); + return true; + } else { return false; } + } + + function balanceOf(address _owner) constant returns (uint256 balance) { + return balances[_owner]; + } + + function approve(address _spender, uint256 _value) returns (bool success) { + allowed[msg.sender][_spender] = _value; + Approval(msg.sender, _spender, _value); + return true; + } + + function allowance(address _owner, address _spender) constant returns (uint256 remaining) { + return allowed[_owner][_spender]; + } + + mapping (address => uint256) balances; + mapping (address => mapping (address => uint256)) allowed; +} diff --git a/js/src/contracts/snippets/token.sol b/js/src/contracts/snippets/token.sol new file mode 100644 index 000000000..d54c5c424 --- /dev/null +++ b/js/src/contracts/snippets/token.sol @@ -0,0 +1,47 @@ +// Abstract contract for the full ERC 20 Token standard +// https://github.com/ethereum/EIPs/issues/20 + +contract Token { + /* This is a slight change to the ERC20 base standard. + function totalSupply() constant returns (uint256 supply); + is replaced with: + uint256 public totalSupply; + This automatically creates a getter function for the totalSupply. + This is moved to the base contract since public getter functions are not + currently recognised as an implementation of the matching abstract + function by the compiler. + */ + /// total amount of tokens + uint256 public totalSupply; + + /// @param _owner The address from which the balance will be retrieved + /// @return The balance + function balanceOf(address _owner) constant returns (uint256 balance); + + /// @notice send `_value` token to `_to` from `msg.sender` + /// @param _to The address of the recipient + /// @param _value The amount of token to be transferred + /// @return Whether the transfer was successful or not + function transfer(address _to, uint256 _value) returns (bool success); + + /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` + /// @param _from The address of the sender + /// @param _to The address of the recipient + /// @param _value The amount of token to be transferred + /// @return Whether the transfer was successful or not + function transferFrom(address _from, address _to, uint256 _value) returns (bool success); + + /// @notice `msg.sender` approves `_addr` to spend `_value` tokens + /// @param _spender The address of the account able to transfer the tokens + /// @param _value The amount of wei to be approved for transfer + /// @return Whether the approval was successful or not + function approve(address _spender, uint256 _value) returns (bool success); + + /// @param _owner The address of the account owning tokens + /// @param _spender The address of the account able to transfer the tokens + /// @return Amount of remaining tokens allowed to spent + function allowance(address _owner, address _spender) constant returns (uint256 remaining); + + event Transfer(address indexed _from, address indexed _to, uint256 _value); + event Approval(address indexed _owner, address indexed _spender, uint256 _value); +} diff --git a/js/src/index.js b/js/src/index.js index 966e2708e..c0f4f94ad 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -31,7 +31,7 @@ import ContractInstances from './contracts'; import { initStore } from './redux'; import { ContextProvider, muiTheme } from './ui'; -import { Accounts, Account, Addresses, Address, Application, Contract, Contracts, Dapp, Dapps, Settings, SettingsBackground, SettingsParity, SettingsProxy, SettingsViews, Signer, Status } from './views'; +import { Accounts, Account, Addresses, Address, Application, Contract, Contracts, WriteContract, Dapp, Dapps, Settings, SettingsBackground, SettingsParity, SettingsProxy, SettingsViews, Signer, Status } from './views'; import { setApi } from './redux/providers/apiActions'; @@ -76,6 +76,7 @@ ReactDOM.render( + diff --git a/js/src/modals/DeployContract/DetailsStep/detailsStep.js b/js/src/modals/DeployContract/DetailsStep/detailsStep.js index e0f02bc70..854715396 100644 --- a/js/src/modals/DeployContract/DetailsStep/detailsStep.js +++ b/js/src/modals/DeployContract/DetailsStep/detailsStep.js @@ -25,7 +25,7 @@ import styles from '../deployContract.css'; export default class DetailsStep extends Component { static contextTypes = { api: PropTypes.object.isRequired - } + }; static propTypes = { accounts: PropTypes.object.isRequired, @@ -46,16 +46,33 @@ export default class DetailsStep extends Component { onFromAddressChange: PropTypes.func.isRequired, onDescriptionChange: PropTypes.func.isRequired, onNameChange: PropTypes.func.isRequired, - onParamsChange: PropTypes.func.isRequired - } + onParamsChange: PropTypes.func.isRequired, + readOnly: PropTypes.bool + }; + + static defaultProps = { + readOnly: false + }; state = { inputs: [] } + componentDidMount () { + const { abi, code } = this.props; + + if (abi) { + this.onAbiChange(abi); + } + + if (code) { + this.onCodeChange(code); + } + } + render () { const { accounts } = this.props; - const { abi, abiError, code, codeError, fromAddress, fromAddressError, name, nameError } = this.props; + const { abi, abiError, code, codeError, fromAddress, fromAddressError, name, nameError, readOnly } = this.props; return (
@@ -77,13 +94,15 @@ export default class DetailsStep extends Component { hint='the abi of the contract to deploy' error={ abiError } value={ abi } - onSubmit={ this.onAbiChange } /> + onSubmit={ this.onAbiChange } + readOnly={ readOnly } /> + onSubmit={ this.onCodeChange } + readOnly={ readOnly } /> { this.renderConstructorInputs() }
); diff --git a/js/src/modals/DeployContract/deployContract.js b/js/src/modals/DeployContract/deployContract.js index 588d16f6a..768723d1f 100644 --- a/js/src/modals/DeployContract/deployContract.js +++ b/js/src/modals/DeployContract/deployContract.js @@ -36,8 +36,17 @@ export default class DeployContract extends Component { static propTypes = { accounts: PropTypes.object.isRequired, - onClose: PropTypes.func.isRequired - } + onClose: PropTypes.func.isRequired, + abi: PropTypes.string, + code: PropTypes.string, + readOnly: PropTypes.bool, + source: PropTypes.string + }; + + static defaultProps = { + readOnly: false, + source: '' + }; state = { abi: '', @@ -57,6 +66,31 @@ export default class DeployContract extends Component { deployError: null } + componentWillMount () { + const { abi, code } = this.props; + + if (abi && code) { + this.setState({ abi, code }); + } + } + + componentWillReceiveProps (nextProps) { + const { abi, code } = nextProps; + const newState = {}; + + if (abi !== this.props.abi) { + newState.abi = abi; + } + + if (code !== this.props.code) { + newState.code = code; + } + + if (Object.keys(newState).length) { + this.setState(newState); + } + } + render () { const { step, deployError } = this.state; @@ -115,7 +149,7 @@ export default class DeployContract extends Component { } renderStep () { - const { accounts } = this.props; + const { accounts, readOnly } = this.props; const { address, deployError, step, deployState, txhash } = this.state; if (deployError) { @@ -129,6 +163,7 @@ export default class DeployContract extends Component { return ( { const { api, store } = this.context; + const { source } = this.props; const { abiParsed, code, description, name, params, fromAddress } = this.state; const options = { data: code, @@ -219,6 +255,7 @@ export default class DeployContract extends Component { contract: true, timestamp: Date.now(), deleted: false, + source, description }) ]) diff --git a/js/src/modals/LoadContract/index.js b/js/src/modals/LoadContract/index.js new file mode 100644 index 000000000..be5e62af5 --- /dev/null +++ b/js/src/modals/LoadContract/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 './loadContract'; diff --git a/js/src/modals/LoadContract/loadContract.css b/js/src/modals/LoadContract/loadContract.css new file mode 100644 index 000000000..f3144eeb8 --- /dev/null +++ b/js/src/modals/LoadContract/loadContract.css @@ -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 . +*/ + +.loadContainer { + display: flex; + flex-direction: row; + + > * { + flex: 50%; + width: 0; + } +} + +.editor { + display: flex; + flex-direction: column; + padding-left: 1em; + + p { + line-height: 48px; + height: 48px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + margin: 0; + font-size: 1.2em; + } +} + +.confirmRemoval { + text-align: center; + + .editor { + text-align: left; + margin-top: 0.5em; + } +} diff --git a/js/src/modals/LoadContract/loadContract.js b/js/src/modals/LoadContract/loadContract.js new file mode 100644 index 000000000..3de55561a --- /dev/null +++ b/js/src/modals/LoadContract/loadContract.js @@ -0,0 +1,284 @@ +// 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 ContentClear from 'material-ui/svg-icons/content/clear'; +import CheckIcon from 'material-ui/svg-icons/navigation/check'; +import DeleteIcon from 'material-ui/svg-icons/action/delete'; + +import { List, ListItem, makeSelectable } from 'material-ui/List'; +import { Subheader, IconButton, Tabs, Tab } from 'material-ui'; +import moment from 'moment'; + +import { Button, Modal, Editor } from '../../ui'; + +import styles from './loadContract.css'; + +const SelectableList = makeSelectable(List); + +const SELECTED_STYLE = { + backgroundColor: 'rgba(255, 255, 255, 0.1)' +}; + +export default class LoadContract extends Component { + + static propTypes = { + onClose: PropTypes.func.isRequired, + onLoad: PropTypes.func.isRequired, + onDelete: PropTypes.func.isRequired, + contracts: PropTypes.object.isRequired, + snippets: PropTypes.object.isRequired + }; + + state = { + selected: -1, + deleteRequest: false, + deleteId: -1 + }; + + render () { + const { deleteRequest } = this.state; + + const title = deleteRequest + ? 'confirm removal' + : 'view contracts'; + + return ( + + { this.renderBody() } + + ); + } + + renderBody () { + if (this.state.deleteRequest) { + return this.renderConfirmRemoval(); + } + + const { contracts, snippets } = this.props; + + const contractsTab = Object.keys(contracts).length === 0 + ? null + : ( + + { this.renderEditor() } + + + Saved Contracts + { this.renderContracts(contracts) } + + + ); + + return ( +
+ + { contractsTab } + + + { this.renderEditor() } + + + Contract Snippets + { this.renderContracts(snippets, false) } + + + +
+ ); + } + + renderConfirmRemoval () { + const { deleteId } = this.state; + const { name, timestamp, sourcecode } = this.props.contracts[deleteId]; + + return ( +
+

+ Are you sure you want to remove the following + contract from your saved contracts? +

+ + +
+ +
+
+ ); + } + + renderEditor () { + const { contracts, snippets } = this.props; + const { selected } = this.state; + + const mergedContracts = Object.assign({}, contracts, snippets); + + if (selected === -1 || !mergedContracts[selected]) { + return null; + } + + const { sourcecode, name } = mergedContracts[selected]; + + return ( +
+

{ name }

+ +
+ ); + } + + renderContracts (contracts, removable = true) { + const { selected } = this.state; + + return Object + .values(contracts) + .map((contract) => { + const { id, name, timestamp, description } = contract; + const onDelete = () => this.onDeleteRequest(id); + + const secondaryText = description || `Saved ${moment(timestamp).fromNow()}`; + const remove = removable + ? ( + + + + ) + : null; + + return ( + + ); + }); + } + + renderDialogActions () { + const { deleteRequest } = this.state; + + if (deleteRequest) { + return [ +
+ ); + } + + renderModal () { + const { title, renderValidation } = this.props; + const { show, step } = this.state; + + if (!show) { + return null; + } + + const hasSteps = typeof renderValidation === 'function'; + + const steps = hasSteps ? [ 'select a file', 'validate' ] : null; + + return ( + + { this.renderBody() } + + ); + } + + renderActions () { + const { validate } = this.state; + + const cancelBtn = ( +
); } + renderDetails (contract) { + const { showDetailsDialog } = this.state; + + if (!showDetailsDialog) { + return null; + } + + const cancelBtn = ( +