From 129ad0bbcbefb60af3e945a220a202cae78ba930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 27 May 2016 20:18:37 +0200 Subject: [PATCH 01/19] Splitting methods requiring signing into separate trait --- parity/dapps.rs | 1 + parity/rpc.rs | 1 + parity/signer.rs | 3 +- rpc/src/v1/impls/eth.rs | 58 +++++++++++++++++++++++++++------- rpc/src/v1/impls/mod.rs | 4 +-- rpc/src/v1/mod.rs | 2 +- rpc/src/v1/tests/mocked/eth.rs | 5 ++- rpc/src/v1/traits/eth.rs | 25 ++++++++++----- rpc/src/v1/traits/mod.rs | 2 +- 9 files changed, 75 insertions(+), 26 deletions(-) diff --git a/parity/dapps.rs b/parity/dapps.rs index 986e3dd07..ebc254604 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -99,6 +99,7 @@ pub fn setup_dapps_server( server.add_delegate(Web3Client::new().to_delegate()); server.add_delegate(NetClient::new(&deps.sync).to_delegate()); server.add_delegate(EthClient::new(&deps.client, &deps.sync, &deps.secret_store, &deps.miner, &deps.external_miner).to_delegate()); + server.add_delegate(EthSigningUnsafeClient::new(&deps.client, &deps.secret_store, &deps.miner).to_delegate()); server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate()); server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner).to_delegate()); server.add_delegate(EthcoreClient::new(&deps.miner, deps.logger.clone(), deps.settings.clone()).to_delegate()); diff --git a/parity/rpc.rs b/parity/rpc.rs index 60766263b..c05a98cb6 100644 --- a/parity/rpc.rs +++ b/parity/rpc.rs @@ -103,6 +103,7 @@ fn setup_rpc_server(apis: Vec<&str>, deps: &Arc) -> Server { modules.insert("eth".to_owned(), "1.0".to_owned()); server.add_delegate(EthClient::new(&deps.client, &deps.sync, &deps.secret_store, &deps.miner, &deps.external_miner).to_delegate()); server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate()); + server.add_delegate(EthSigningUnsafeClient::new(&deps.client, &deps.secret_store, &deps.miner).to_delegate()); }, "personal" => { modules.insert("personal".to_owned(), "1.0".to_owned()); diff --git a/parity/signer.rs b/parity/signer.rs index c4a540721..e5f1b89cf 100644 --- a/parity/signer.rs +++ b/parity/signer.rs @@ -63,8 +63,9 @@ fn do_start(conf: Configuration, deps: Dependencies) -> SignerServer { server.add_delegate(Web3Client::new().to_delegate()); server.add_delegate(NetClient::new(&deps.sync).to_delegate()); server.add_delegate(EthClient::new(&deps.client, &deps.sync, &deps.secret_store, &deps.miner, &deps.external_miner).to_delegate()); + server.add_delegate(EthSigningUnsafeClient::new(&deps.client, &deps.secret_store, &deps.miner).to_delegate()); server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate()); - server.add_delegate(PersonalClient::new(&deps.secret_store).to_delegate()); + server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner).to_delegate()); server.start(addr) }; diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 7627dd61e..f5af2543e 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -35,7 +35,7 @@ use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Act use ethcore::log_entry::LogEntry; use ethcore::filter::Filter as EthcoreFilter; use self::ethash::SeedHashCompute; -use v1::traits::{Eth, EthFilter}; +use v1::traits::{Eth, EthFilter, EthSigning}; use v1::types::{Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, TransactionRequest, CallRequest, OptionalValue, Index, Filter, Log, Receipt}; use v1::helpers::{PollFilter, PollManager}; use v1::impls::{dispatch_transaction, sign_and_dispatch}; @@ -494,17 +494,6 @@ impl Eth for EthClient where }) } - fn send_transaction(&self, params: Params) -> Result { - from_params::<(TransactionRequest, )>(params) - .and_then(|(request, )| { - let accounts = take_weak!(self.accounts); - match accounts.account_secret(&request.from) { - Ok(secret) => sign_and_dispatch(&self.client, &self.miner, request, secret), - Err(_) => to_value(&H256::zero()) - } - }) - } - fn send_raw_transaction(&self, params: Params) -> Result { from_params::<(Bytes, )>(params) .and_then(|(raw_transaction, )| { @@ -726,3 +715,48 @@ impl EthFilter for EthFilterClient where }) } } + + +/// Implementation of functions that require signing when no trusted signer is used. +pub struct EthSigningUnsafeClient where + C: BlockChainClient, + A: AccountProvider, + M: MinerService { + client: Weak, + accounts: Weak, + miner: Weak, +} + +impl EthSigningUnsafeClient where + C: BlockChainClient, + A: AccountProvider, + M: MinerService { + + /// Creates new EthClient. + pub fn new(client: &Arc, accounts: &Arc, miner: &Arc) + -> Self { + EthSigningUnsafeClient { + client: Arc::downgrade(client), + miner: Arc::downgrade(miner), + accounts: Arc::downgrade(accounts), + } + } +} + +impl EthSigning for EthSigningUnsafeClient where + C: BlockChainClient + 'static, + A: AccountProvider + 'static, + M: MinerService + 'static { + + fn send_transaction(&self, params: Params) -> Result { + from_params::<(TransactionRequest, )>(params) + .and_then(|(request, )| { + let accounts = take_weak!(self.accounts); + match accounts.account_secret(&request.from) { + Ok(secret) => sign_and_dispatch(&self.client, &self.miner, request, secret), + Err(_) => to_value(&H256::zero()) + } + }) + } + +} diff --git a/rpc/src/v1/impls/mod.rs b/rpc/src/v1/impls/mod.rs index b79772103..13e4fd512 100644 --- a/rpc/src/v1/impls/mod.rs +++ b/rpc/src/v1/impls/mod.rs @@ -34,7 +34,7 @@ mod traces; mod rpc; pub use self::web3::Web3Client; -pub use self::eth::{EthClient, EthFilterClient}; +pub use self::eth::{EthClient, EthFilterClient, EthSigningUnsafeClient}; pub use self::net::NetClient; pub use self::personal::PersonalClient; pub use self::ethcore::EthcoreClient; @@ -96,4 +96,4 @@ fn sign_and_dispatch(client: &Weak, miner: &Weak, request: Transacti trace!(target: "miner", "send_transaction: dispatching tx: {}", encode(&signed_transaction).to_vec().pretty()); dispatch_transaction(&*client, &*miner, signed_transaction) -} \ No newline at end of file +} diff --git a/rpc/src/v1/mod.rs b/rpc/src/v1/mod.rs index b1ab256c0..49f9e3a38 100644 --- a/rpc/src/v1/mod.rs +++ b/rpc/src/v1/mod.rs @@ -25,5 +25,5 @@ pub mod traits; pub mod tests; pub mod types; -pub use self::traits::{Web3, Eth, EthFilter, Personal, Net, Ethcore, Traces, Rpc}; +pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Personal, Net, Ethcore, Traces, Rpc}; pub use self::impls::*; diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index 32a2cd99a..ce097307c 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -26,7 +26,7 @@ use ethcore::log_entry::{LocalizedLogEntry, LogEntry}; use ethcore::receipt::LocalizedReceipt; use ethcore::transaction::{Transaction, Action}; use ethminer::ExternalMiner; -use v1::{Eth, EthClient}; +use v1::{Eth, EthClient, EthSigning, EthSigningUnsafeClient}; use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService}; fn blockchain_client() -> Arc { @@ -70,8 +70,11 @@ impl Default for EthTester { let hashrates = Arc::new(RwLock::new(HashMap::new())); let external_miner = Arc::new(ExternalMiner::new(hashrates.clone())); let eth = EthClient::new(&client, &sync, &ap, &miner, &external_miner).to_delegate(); + let sign = EthSigningUnsafeClient::new(&client, &ap, &miner).to_delegate(); let io = IoHandler::new(); io.add_delegate(eth); + io.add_delegate(sign); + EthTester { client: client, sync: sync, diff --git a/rpc/src/v1/traits/eth.rs b/rpc/src/v1/traits/eth.rs index a28f72c5c..970bc5dd2 100644 --- a/rpc/src/v1/traits/eth.rs +++ b/rpc/src/v1/traits/eth.rs @@ -74,12 +74,6 @@ pub trait Eth: Sized + Send + Sync + 'static { /// Returns the code at given address at given time (block number). fn code_at(&self, _: Params) -> Result { rpc_unimplemented!() } - /// Signs the data with given address signature. - fn sign(&self, _: Params) -> Result { rpc_unimplemented!() } - - /// Sends transaction. - fn send_transaction(&self, _: Params) -> Result { rpc_unimplemented!() } - /// Sends signed transaction. fn send_raw_transaction(&self, _: Params) -> Result { rpc_unimplemented!() } @@ -150,8 +144,6 @@ pub trait Eth: Sized + Send + Sync + 'static { delegate.add_method("eth_getUncleCountByBlockHash", Eth::block_uncles_count_by_hash); delegate.add_method("eth_getUncleCountByBlockNumber", Eth::block_uncles_count_by_number); delegate.add_method("eth_getCode", Eth::code_at); - delegate.add_method("eth_sign", Eth::sign); - delegate.add_method("eth_sendTransaction", Eth::send_transaction); delegate.add_method("eth_sendRawTransaction", Eth::send_raw_transaction); delegate.add_method("eth_call", Eth::call); delegate.add_method("eth_estimateGas", Eth::estimate_gas); @@ -208,3 +200,20 @@ pub trait EthFilter: Sized + Send + Sync + 'static { delegate } } + +/// 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) -> Result { rpc_unimplemented!() } + + /// Sends transaction. + fn send_transaction(&self, _: Params) -> Result { rpc_unimplemented!() } + + /// 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("eth_sign", EthSigning::sign); + delegate.add_method("eth_sendTransaction", EthSigning::send_transaction); + delegate + } +} diff --git a/rpc/src/v1/traits/mod.rs b/rpc/src/v1/traits/mod.rs index bebf95bb7..f3004a8d4 100644 --- a/rpc/src/v1/traits/mod.rs +++ b/rpc/src/v1/traits/mod.rs @@ -29,7 +29,7 @@ pub mod traces; pub mod rpc; pub use self::web3::Web3; -pub use self::eth::{Eth, EthFilter}; +pub use self::eth::{Eth, EthFilter, EthSigning}; pub use self::net::Net; pub use self::personal::Personal; pub use self::ethcore::Ethcore; From 07399d377fae34008b5420abef13c2e11e5a4161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sat, 28 May 2016 13:42:53 +0200 Subject: [PATCH 02/19] Single place where RPC apis are created. --- dapps/src/lib.rs | 13 ++-- parity/dapps.rs | 26 ++----- parity/main.rs | 23 +++--- parity/rpc.rs | 80 +++++---------------- parity/rpc_apis.rs | 136 ++++++++++++++++++++++++++++++++++++ parity/signer.rs | 20 ++---- rpc/src/lib.rs | 18 +++-- rpc/src/v1/traits/mod.rs | 2 + signer/src/ws_server/mod.rs | 12 ++-- 9 files changed, 201 insertions(+), 129 deletions(-) create mode 100644 parity/rpc_apis.rs diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index e0676881f..ef86f2661 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -52,6 +52,7 @@ extern crate serde_json; extern crate jsonrpc_core; extern crate jsonrpc_http_server; extern crate parity_dapps; +extern crate ethcore_rpc; mod endpoint; mod apps; @@ -66,6 +67,7 @@ use std::net::SocketAddr; use std::collections::HashMap; use jsonrpc_core::{IoHandler, IoDelegate}; use router::auth::{Authorization, NoAuth, HttpBasicAuth}; +use ethcore_rpc::Extendable; static DAPPS_DOMAIN : &'static str = ".parity"; @@ -74,6 +76,12 @@ pub struct ServerBuilder { handler: Arc, } +impl Extendable for ServerBuilder { + fn add_delegate(&self, delegate: IoDelegate) { + self.handler.add_delegate(delegate); + } +} + impl ServerBuilder { /// Construct new dapps server pub fn new() -> Self { @@ -82,11 +90,6 @@ impl ServerBuilder { } } - /// Add io delegate. - pub fn add_delegate(&self, delegate: IoDelegate) where D: Send + Sync + 'static { - self.handler.add_delegate(delegate); - } - /// Asynchronously start server with no authentication, /// returns result with `Server` handle on success or an error. pub fn start_unsecure_http(&self, addr: &SocketAddr) -> Result { diff --git a/parity/dapps.rs b/parity/dapps.rs index ebc254604..4ea74d7f2 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -17,14 +17,9 @@ use std::sync::Arc; use std::str::FromStr; use std::net::SocketAddr; -use ethcore::client::Client; -use ethsync::EthSync; -use ethminer::{Miner, ExternalMiner}; -use util::RotatingLogger; use util::panics::PanicHandler; -use util::keys::store::AccountService; -use util::network_settings::NetworkSettings; use die::*; +use rpc_apis; #[cfg(feature = "dapps")] pub use ethcore_dapps::Server as WebappServer; @@ -41,13 +36,7 @@ pub struct Configuration { pub struct Dependencies { pub panic_handler: Arc, - pub client: Arc, - pub sync: Arc, - pub secret_store: Arc, - pub miner: Arc, - pub external_miner: Arc, - pub logger: Arc, - pub settings: Arc, + pub apis: Arc, } pub fn new(configuration: Configuration, deps: Dependencies) -> Option { @@ -92,18 +81,11 @@ pub fn setup_dapps_server( url: &SocketAddr, auth: Option<(String, String)> ) -> WebappServer { - use ethcore_rpc::v1::*; use ethcore_dapps as dapps; let server = dapps::ServerBuilder::new(); - server.add_delegate(Web3Client::new().to_delegate()); - server.add_delegate(NetClient::new(&deps.sync).to_delegate()); - server.add_delegate(EthClient::new(&deps.client, &deps.sync, &deps.secret_store, &deps.miner, &deps.external_miner).to_delegate()); - server.add_delegate(EthSigningUnsafeClient::new(&deps.client, &deps.secret_store, &deps.miner).to_delegate()); - server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate()); - server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner).to_delegate()); - server.add_delegate(EthcoreClient::new(&deps.miner, deps.logger.clone(), deps.settings.clone()).to_delegate()); - + // TODO [ToDr] Specify apis + let server = rpc_apis::setup_rpc(server, deps.apis.clone(), None); let start_result = match auth { None => { server.start_unsecure_http(url) diff --git a/parity/main.rs b/parity/main.rs index 61aa8ab21..874bb35b5 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -69,6 +69,7 @@ mod cli; mod configuration; mod migration; mod signer; +mod rpc_apis; use std::io::{Write, Read, BufReader, BufRead}; use std::ops::Deref; @@ -197,8 +198,7 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig) // Sync let sync = EthSync::register(service.network(), sync_config, client.clone(), miner.clone()); - let dependencies = Arc::new(rpc::Dependencies { - panic_handler: panic_handler.clone(), + let deps_for_rpc_apis = Arc::new(rpc_apis::Dependencies { client: client.clone(), sync: sync.clone(), secret_store: account_service.clone(), @@ -208,6 +208,11 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig) settings: network_settings.clone(), }); + let dependencies = rpc::Dependencies { + panic_handler: panic_handler.clone(), + apis: deps_for_rpc_apis.clone(), + }; + // Setup http rpc let rpc_server = rpc::new_http(rpc::HttpConfiguration { enabled: network_settings.rpc_enabled, @@ -229,13 +234,7 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig) pass: conf.args.flag_dapps_pass.clone(), }, dapps::Dependencies { panic_handler: panic_handler.clone(), - client: client.clone(), - sync: sync.clone(), - secret_store: account_service.clone(), - miner: miner.clone(), - external_miner: external_miner.clone(), - logger: logger.clone(), - settings: network_settings.clone(), + apis: deps_for_rpc_apis.clone(), }); // Set up a signer @@ -244,11 +243,7 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig) port: conf.args.flag_signer_port, }, signer::Dependencies { panic_handler: panic_handler.clone(), - client: client.clone(), - sync: sync.clone(), - secret_store: account_service.clone(), - miner: miner.clone(), - external_miner: external_miner.clone(), + apis: deps_for_rpc_apis.clone(), }); // Register IO handler diff --git a/parity/rpc.rs b/parity/rpc.rs index c05a98cb6..af998ff97 100644 --- a/parity/rpc.rs +++ b/parity/rpc.rs @@ -15,19 +15,13 @@ // along with Parity. If not, see . -use std::collections::BTreeMap; use std::str::FromStr; use std::sync::Arc; use std::net::SocketAddr; -use ethcore::client::Client; -use ethsync::EthSync; -use ethminer::{Miner, ExternalMiner}; -use util::RotatingLogger; use util::panics::PanicHandler; -use util::keys::store::AccountService; -use util::network_settings::NetworkSettings; use die::*; use jsonipc; +use rpc_apis; #[cfg(feature = "rpc")] pub use ethcore_rpc::Server as RpcServer; @@ -52,16 +46,10 @@ pub struct IpcConfiguration { pub struct Dependencies { pub panic_handler: Arc, - pub client: Arc, - pub sync: Arc, - pub secret_store: Arc, - pub miner: Arc, - pub external_miner: Arc, - pub logger: Arc, - pub settings: Arc, + pub apis: Arc, } -pub fn new_http(conf: HttpConfiguration, deps: &Arc) -> Option { +pub fn new_http(conf: HttpConfiguration, deps: &Dependencies) -> Option { if !conf.enabled { return None; } @@ -78,59 +66,23 @@ pub fn new_http(conf: HttpConfiguration, deps: &Arc) -> Option) -> Option { +pub fn new_ipc(conf: IpcConfiguration, deps: &Dependencies) -> Option { if !conf.enabled { return None; } let apis = conf.apis.split(',').collect(); Some(setup_ipc_rpc_server(deps, &conf.socket_addr, apis)) } -fn setup_rpc_server(apis: Vec<&str>, deps: &Arc) -> Server { - use ethcore_rpc::v1::*; - +fn setup_rpc_server(apis: Vec<&str>, deps: &Dependencies) -> Server { + let apis = rpc_apis::from_str(apis); let server = Server::new(); - let mut modules = BTreeMap::new(); - for api in apis.into_iter() { - match api { - "web3" => { - modules.insert("web3".to_owned(), "1.0".to_owned()); - server.add_delegate(Web3Client::new().to_delegate()); - }, - "net" => { - modules.insert("net".to_owned(), "1.0".to_owned()); - server.add_delegate(NetClient::new(&deps.sync).to_delegate()); - }, - "eth" => { - modules.insert("eth".to_owned(), "1.0".to_owned()); - server.add_delegate(EthClient::new(&deps.client, &deps.sync, &deps.secret_store, &deps.miner, &deps.external_miner).to_delegate()); - server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate()); - server.add_delegate(EthSigningUnsafeClient::new(&deps.client, &deps.secret_store, &deps.miner).to_delegate()); - }, - "personal" => { - modules.insert("personal".to_owned(), "1.0".to_owned()); - server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner).to_delegate()) - }, - "ethcore" => { - modules.insert("ethcore".to_owned(), "1.0".to_owned()); - server.add_delegate(EthcoreClient::new(&deps.miner, deps.logger.clone(), deps.settings.clone()).to_delegate()) - }, - "traces" => { - modules.insert("traces".to_owned(), "1.0".to_owned()); - server.add_delegate(TracesClient::new(&deps.client).to_delegate()) - }, - _ => { - die!("{}: Invalid API name to be enabled.", api); - }, - } - } - server.add_delegate(RpcClient::new(modules).to_delegate()); - server + rpc_apis::setup_rpc(server, deps.apis.clone(), Some(apis)) } #[cfg(not(feature = "rpc"))] pub fn setup_http_rpc_server( - _deps: Dependencies, + _deps: &Dependencies, _url: &SocketAddr, - _cors_domain: Option, + _cors_domain: Vec, _apis: Vec<&str>, ) -> ! { die!("Your Parity version has been compiled without JSON-RPC support.") @@ -138,27 +90,31 @@ pub fn setup_http_rpc_server( #[cfg(feature = "rpc")] pub fn setup_http_rpc_server( - dependencies: &Arc, + dependencies: &Dependencies, url: &SocketAddr, cors_domains: Vec, apis: Vec<&str>, ) -> RpcServer { let server = setup_rpc_server(apis, dependencies); let start_result = server.start_http(url, cors_domains); - let deps = dependencies.clone(); + let ph = dependencies.panic_handler.clone(); match start_result { Err(RpcServerError::IoError(err)) => die_with_io_error("RPC", err), Err(e) => die!("RPC: {:?}", e), Ok(server) => { server.set_panic_handler(move || { - deps.panic_handler.notify_all("Panic in RPC thread.".to_owned()); + ph.notify_all("Panic in RPC thread.".to_owned()); }); server }, } } - -pub fn setup_ipc_rpc_server(dependencies: &Arc, addr: &str, apis: Vec<&str>) -> jsonipc::Server { +#[cfg(not(feature = "rpc"))] +pub fn setup_ipc_rpc_server(_dependencies: &Dependencies, _addr: &str, _apis: Vec<&str>) -> ! { + die!("Your Parity version has been compiled without JSON-RPC support.") +} +#[cfg(feature = "rpc")] +pub fn setup_ipc_rpc_server(dependencies: &Dependencies, addr: &str, apis: Vec<&str>) -> jsonipc::Server { let server = setup_rpc_server(apis, dependencies); match server.start_ipc(addr) { Err(jsonipc::Error::Io(io_error)) => die_with_io_error("RPC", io_error), diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs new file mode 100644 index 000000000..1f1994d9e --- /dev/null +++ b/parity/rpc_apis.rs @@ -0,0 +1,136 @@ +// 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 . + +use std::collections::BTreeMap; +use std::str::FromStr; +use std::sync::Arc; + +use die::*; +use ethsync::EthSync; +use ethminer::{Miner, ExternalMiner}; +use ethcore::client::Client; +use util::RotatingLogger; +use util::keys::store::AccountService; +use util::network_settings::NetworkSettings; + +use ethcore_rpc::Extendable; + +pub enum Api { + Web3, + Net, + Eth, + Personal, + Ethcore, + Traces, + Rpc, +} +pub enum ApiError { + UnknownApi(String) +} + +impl FromStr for Api { + type Err = ApiError; + + fn from_str(s: &str) -> Result { + use self::Api::*; + + match s { + "web3" => Ok(Web3), + "net" => Ok(Net), + "eth" => Ok(Eth), + "personal" => Ok(Personal), + "ethcore" => Ok(Ethcore), + "traces" => Ok(Traces), + "rpc" => Ok(Rpc), + e => Err(ApiError::UnknownApi(e.into())), + } + } +} + +pub struct Dependencies { + pub client: Arc, + pub sync: Arc, + pub secret_store: Arc, + pub miner: Arc, + pub external_miner: Arc, + pub logger: Arc, + pub settings: Arc, +} + +fn to_modules(apis: &[Api]) -> BTreeMap { + let mut modules = BTreeMap::new(); + for api in apis { + let (name, version) = match *api { + Api::Web3 => ("web3", "1.0"), + Api::Net => ("net", "1.0"), + Api::Eth => ("eth", "1.0"), + Api::Personal => ("personal", "1.0"), + Api::Ethcore => ("ethcore", "1.0"), + Api::Traces => ("traces", "1.0"), + Api::Rpc => ("rpc", "1.0"), + }; + modules.insert(name.into(), version.into()); + } + modules +} + +pub fn from_str(apis: Vec<&str>) -> Vec { + apis.into_iter() + .map(Api::from_str) + .collect::, ApiError>>() + .unwrap_or_else(|e| match e { + ApiError::UnknownApi(s) => die!("Unknown RPC API specified: {}", s), + }) +} + +pub fn setup_rpc(server: T, deps: Arc, apis: Option>) -> T { + use ethcore_rpc::v1::*; + + let apis = match apis { + Some(api) => api, + None => vec![Api::Web3, Api::Net, Api::Eth, Api::Personal, Api::Ethcore, Api::Traces, Api::Rpc], + }; + + for api in &apis { + match *api { + Api::Web3 => { + server.add_delegate(Web3Client::new().to_delegate()); + }, + Api::Net => { + server.add_delegate(NetClient::new(&deps.sync).to_delegate()); + }, + Api::Eth => { + server.add_delegate(EthClient::new(&deps.client, &deps.sync, &deps.secret_store, &deps.miner, &deps.external_miner).to_delegate()); + server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate()); + server.add_delegate(EthSigningUnsafeClient::new(&deps.client, &deps.secret_store, &deps.miner).to_delegate()); + }, + Api::Personal => { + server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner).to_delegate()) + }, + Api::Ethcore => { + server.add_delegate(EthcoreClient::new(&deps.miner, deps.logger.clone(), deps.settings.clone()).to_delegate()) + }, + Api::Traces => { + server.add_delegate(TracesClient::new(&deps.client).to_delegate()) + }, + Api::Rpc => { + let modules = to_modules(&apis); + server.add_delegate(RpcClient::new(modules).to_delegate()); + } + } + } + server +} diff --git a/parity/signer.rs b/parity/signer.rs index e5f1b89cf..b2224b106 100644 --- a/parity/signer.rs +++ b/parity/signer.rs @@ -15,12 +15,9 @@ // along with Parity. If not, see . use std::sync::Arc; -use ethcore::client::Client; -use ethsync::EthSync; -use ethminer::{Miner, ExternalMiner}; -use util::keys::store::AccountService; use util::panics::{PanicHandler, ForwardPanic}; use die::*; +use rpc_apis; #[cfg(feature = "ethcore-signer")] use ethcore_signer as signer; @@ -36,11 +33,7 @@ pub struct Configuration { pub struct Dependencies { pub panic_handler: Arc, - pub client: Arc, - pub sync: Arc, - pub secret_store: Arc, - pub miner: Arc, - pub external_miner: Arc, + pub apis: Arc, } pub fn start(conf: Configuration, deps: Dependencies) -> Option { @@ -58,14 +51,9 @@ fn do_start(conf: Configuration, deps: Dependencies) -> SignerServer { }); let start_result = { - use ethcore_rpc::v1::*; let server = signer::ServerBuilder::new(); - server.add_delegate(Web3Client::new().to_delegate()); - server.add_delegate(NetClient::new(&deps.sync).to_delegate()); - server.add_delegate(EthClient::new(&deps.client, &deps.sync, &deps.secret_store, &deps.miner, &deps.external_miner).to_delegate()); - server.add_delegate(EthSigningUnsafeClient::new(&deps.client, &deps.secret_store, &deps.miner).to_delegate()); - server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate()); - server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner).to_delegate()); + // TODO [ToDr] Setup APIS + let server = rpc_apis::setup_rpc(server, deps.apis, None); server.start(addr) }; diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 7d9818615..a82c70af0 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -40,11 +40,24 @@ use self::jsonrpc_core::{IoHandler, IoDelegate}; pub use jsonrpc_http_server::{Server, RpcServerError}; pub mod v1; +/// An object that can be extended with `IoDelegates` +pub trait Extendable { + /// Add `Delegate` to this object. + fn add_delegate(&self, delegate: IoDelegate); +} + /// Http server. pub struct RpcServer { handler: Arc, } +impl Extendable for RpcServer { + /// Add io delegate. + fn add_delegate(&self, delegate: IoDelegate) { + self.handler.add_delegate(delegate); + } +} + impl RpcServer { /// Construct new http server object. pub fn new() -> RpcServer { @@ -53,11 +66,6 @@ impl RpcServer { } } - /// Add io delegate. - pub fn add_delegate(&self, delegate: IoDelegate) where D: Send + Sync + 'static { - self.handler.add_delegate(delegate); - } - /// Start http server asynchronously and returns result with `Server` handle on success or an error. pub fn start_http(&self, addr: &SocketAddr, cors_domains: Vec) -> Result { let cors_domains = cors_domains.into_iter() diff --git a/rpc/src/v1/traits/mod.rs b/rpc/src/v1/traits/mod.rs index f3004a8d4..e5d5f3324 100644 --- a/rpc/src/v1/traits/mod.rs +++ b/rpc/src/v1/traits/mod.rs @@ -35,3 +35,5 @@ pub use self::personal::Personal; pub use self::ethcore::Ethcore; pub use self::traces::Traces; pub use self::rpc::Rpc; + + diff --git a/signer/src/ws_server/mod.rs b/signer/src/ws_server/mod.rs index bb10bc5c1..bc8fb33f8 100644 --- a/signer/src/ws_server/mod.rs +++ b/signer/src/ws_server/mod.rs @@ -25,6 +25,7 @@ use std::sync::Arc; use std::net::SocketAddr; use util::panics::{PanicHandler, OnPanicListener, MayPanic}; use jsonrpc_core::{IoHandler, IoDelegate}; +use rpc::Extendable; mod session; @@ -57,6 +58,12 @@ impl Default for ServerBuilder { } } +impl Extendable for ServerBuilder { + fn add_delegate(&self, delegate: IoDelegate) { + self.handler.add_delegate(delegate); + } +} + impl ServerBuilder { /// Creates new `ServerBuilder` pub fn new() -> Self { @@ -65,11 +72,6 @@ impl ServerBuilder { } } - /// Adds rpc delegate - pub fn add_delegate(&self, delegate: IoDelegate) where D: Send + Sync + 'static { - self.handler.add_delegate(delegate); - } - /// Starts a new `WebSocket` server in separate thread. /// Returns a `Server` handle which closes the server when droped. pub fn start(self, addr: SocketAddr) -> Result { From ffa113511b9c6cfa5973a52f11db66364710c2ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sat, 28 May 2016 14:21:43 +0200 Subject: [PATCH 03/19] Separating eth_filter --- rpc/src/v1/impls/eth.rs | 224 +++------------------------------ rpc/src/v1/impls/eth_filter.rs | 214 +++++++++++++++++++++++++++++++ rpc/src/v1/impls/mod.rs | 4 +- 3 files changed, 237 insertions(+), 205 deletions(-) create mode 100644 rpc/src/v1/impls/eth_filter.rs diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index f5af2543e..2c18640d7 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -18,7 +18,6 @@ extern crate ethash; -use std::collections::HashSet; use std::sync::{Arc, Weak, Mutex}; use std::ops::Deref; use ethsync::{SyncProvider, SyncState}; @@ -35,9 +34,8 @@ use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Act use ethcore::log_entry::LogEntry; use ethcore::filter::Filter as EthcoreFilter; use self::ethash::SeedHashCompute; -use v1::traits::{Eth, EthFilter, EthSigning}; +use v1::traits::{Eth, EthSigning}; use v1::types::{Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, TransactionRequest, CallRequest, OptionalValue, Index, Filter, Log, Receipt}; -use v1::helpers::{PollFilter, PollManager}; use v1::impls::{dispatch_transaction, sign_and_dispatch}; use util::keys::store::AccountProvider; use serde; @@ -170,6 +168,25 @@ impl EthClient where } } +pub fn pending_logs(miner: &M, filter: &EthcoreFilter) -> Vec where M: MinerService { + let receipts = miner.pending_receipts(); + + let pending_logs = receipts.into_iter() + .flat_map(|(hash, r)| r.logs.into_iter().map(|l| (hash.clone(), l)).collect::>()) + .collect::>(); + + let result = pending_logs.into_iter() + .filter(|pair| filter.matches(&pair.1)) + .map(|pair| { + let mut log = Log::from(pair.1); + log.transaction_hash = Some(pair.0); + log + }) + .collect(); + + result +} + const MAX_QUEUE_SIZE_TO_MINE_ON: usize = 4; // because uncles go back 6. fn params_len(params: &Params) -> usize { @@ -193,25 +210,6 @@ fn from_params_default_third(params: Params) -> Result<(F1, F2, BlockNum } } -fn pending_logs(miner: &M, filter: &EthcoreFilter) -> Vec where M: MinerService { - let receipts = miner.pending_receipts(); - - let pending_logs = receipts.into_iter() - .flat_map(|(hash, r)| r.logs.into_iter().map(|l| (hash.clone(), l)).collect::>()) - .collect::>(); - - let result = pending_logs.into_iter() - .filter(|pair| filter.matches(&pair.1)) - .map(|pair| { - let mut log = Log::from(pair.1); - log.transaction_hash = Some(pair.0); - log - }) - .collect(); - - result -} - // must be in range [-32099, -32000] const UNSUPPORTED_REQUEST_CODE: i64 = -32000; @@ -533,188 +531,6 @@ impl Eth for EthClient where } } -/// Eth filter rpc implementation. -pub struct EthFilterClient where - C: BlockChainClient, - M: MinerService { - - client: Weak, - miner: Weak, - polls: Mutex>, -} - -impl EthFilterClient where - C: BlockChainClient, - M: MinerService { - - /// Creates new Eth filter client. - pub fn new(client: &Arc, miner: &Arc) -> Self { - EthFilterClient { - client: Arc::downgrade(client), - miner: Arc::downgrade(miner), - polls: Mutex::new(PollManager::new()), - } - } -} - -impl EthFilter for EthFilterClient where - C: BlockChainClient + 'static, - M: MinerService + 'static { - - fn new_filter(&self, params: Params) -> Result { - from_params::<(Filter,)>(params) - .and_then(|(filter,)| { - let mut polls = self.polls.lock().unwrap(); - let block_number = take_weak!(self.client).chain_info().best_block_number; - let id = polls.create_poll(PollFilter::Logs(block_number, Default::default(), filter)); - to_value(&U256::from(id)) - }) - } - - fn new_block_filter(&self, params: Params) -> Result { - match params { - Params::None => { - let mut polls = self.polls.lock().unwrap(); - let id = polls.create_poll(PollFilter::Block(take_weak!(self.client).chain_info().best_block_number)); - to_value(&U256::from(id)) - }, - _ => Err(Error::invalid_params()) - } - } - - fn new_pending_transaction_filter(&self, params: Params) -> Result { - match params { - Params::None => { - let mut polls = self.polls.lock().unwrap(); - let pending_transactions = take_weak!(self.miner).pending_transactions_hashes(); - let id = polls.create_poll(PollFilter::PendingTransaction(pending_transactions)); - - to_value(&U256::from(id)) - }, - _ => Err(Error::invalid_params()) - } - } - - fn filter_changes(&self, params: Params) -> Result { - let client = take_weak!(self.client); - from_params::<(Index,)>(params) - .and_then(|(index,)| { - let mut polls = self.polls.lock().unwrap(); - match polls.poll_mut(&index.value()) { - None => Ok(Value::Array(vec![] as Vec)), - Some(filter) => match *filter { - PollFilter::Block(ref mut block_number) => { - // + 1, cause we want to return hashes including current block hash. - let current_number = client.chain_info().best_block_number + 1; - let hashes = (*block_number..current_number).into_iter() - .map(BlockID::Number) - .filter_map(|id| client.block_hash(id)) - .collect::>(); - - *block_number = current_number; - - to_value(&hashes) - }, - PollFilter::PendingTransaction(ref mut previous_hashes) => { - // get hashes of pending transactions - let current_hashes = take_weak!(self.miner).pending_transactions_hashes(); - - let new_hashes = - { - let previous_hashes_set = previous_hashes.iter().collect::>(); - - // find all new hashes - current_hashes - .iter() - .filter(|hash| !previous_hashes_set.contains(hash)) - .cloned() - .collect::>() - }; - - // save all hashes of pending transactions - *previous_hashes = current_hashes; - - // return new hashes - to_value(&new_hashes) - }, - PollFilter::Logs(ref mut block_number, ref mut previous_logs, ref filter) => { - // retrive the current block number - let current_number = client.chain_info().best_block_number; - - // check if we need to check pending hashes - let include_pending = filter.to_block == Some(BlockNumber::Pending); - - // build appropriate filter - let mut filter: EthcoreFilter = filter.clone().into(); - filter.from_block = BlockID::Number(*block_number); - filter.to_block = BlockID::Latest; - - // retrieve logs in range from_block..min(BlockID::Latest..to_block) - let mut logs = client.logs(filter.clone()) - .into_iter() - .map(From::from) - .collect::>(); - - // additionally retrieve pending logs - if include_pending { - let pending_logs = pending_logs(take_weak!(self.miner).deref(), &filter); - - // remove logs about which client was already notified about - let new_pending_logs: Vec<_> = pending_logs.iter() - .filter(|p| !previous_logs.contains(p)) - .cloned() - .collect(); - - // save all logs retrieved by client - *previous_logs = pending_logs.into_iter().collect(); - - // append logs array with new pending logs - logs.extend(new_pending_logs); - } - - // save current block number as next from block number - *block_number = current_number; - - to_value(&logs) - } - } - } - }) - } - - fn filter_logs(&self, params: Params) -> Result { - from_params::<(Index,)>(params) - .and_then(|(index,)| { - let mut polls = self.polls.lock().unwrap(); - match polls.poll(&index.value()) { - Some(&PollFilter::Logs(ref _block_number, ref _previous_log, ref filter)) => { - let include_pending = filter.to_block == Some(BlockNumber::Pending); - let filter: EthcoreFilter = filter.clone().into(); - let mut logs = take_weak!(self.client).logs(filter.clone()) - .into_iter() - .map(From::from) - .collect::>(); - - if include_pending { - logs.extend(pending_logs(take_weak!(self.miner).deref(), &filter)); - } - - to_value(&logs) - }, - // just empty array - _ => Ok(Value::Array(vec![] as Vec)), - } - }) - } - - fn uninstall_filter(&self, params: Params) -> Result { - from_params::<(Index,)>(params) - .and_then(|(index,)| { - self.polls.lock().unwrap().remove_poll(&index.value()); - to_value(&true) - }) - } -} /// Implementation of functions that require signing when no trusted signer is used. diff --git a/rpc/src/v1/impls/eth_filter.rs b/rpc/src/v1/impls/eth_filter.rs new file mode 100644 index 000000000..a17e64052 --- /dev/null +++ b/rpc/src/v1/impls/eth_filter.rs @@ -0,0 +1,214 @@ +// 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 . + +//! Eth Filter RPC implementation + +use std::ops::Deref; +use std::sync::{Arc, Weak, Mutex}; +use std::collections::HashSet; +use jsonrpc_core::*; +use util::numbers::*; +use ethminer::{MinerService}; +use ethcore::filter::Filter as EthcoreFilter; +use ethcore::client::{BlockChainClient, BlockID}; +use v1::traits::EthFilter; +use v1::types::{BlockNumber, Index, Filter, Log}; +use v1::helpers::{PollFilter, PollManager}; +use v1::impls::eth::pending_logs; + + +/// Eth filter rpc implementation. +pub struct EthFilterClient where + C: BlockChainClient, + M: MinerService { + + client: Weak, + miner: Weak, + polls: Mutex>, +} + +impl EthFilterClient where + C: BlockChainClient, + M: MinerService { + + /// Creates new Eth filter client. + pub fn new(client: &Arc, miner: &Arc) -> Self { + EthFilterClient { + client: Arc::downgrade(client), + miner: Arc::downgrade(miner), + polls: Mutex::new(PollManager::new()), + } + } +} + +impl EthFilter for EthFilterClient where + C: BlockChainClient + 'static, + M: MinerService + 'static { + + fn new_filter(&self, params: Params) -> Result { + from_params::<(Filter,)>(params) + .and_then(|(filter,)| { + let mut polls = self.polls.lock().unwrap(); + let block_number = take_weak!(self.client).chain_info().best_block_number; + let id = polls.create_poll(PollFilter::Logs(block_number, Default::default(), filter)); + to_value(&U256::from(id)) + }) + } + + fn new_block_filter(&self, params: Params) -> Result { + match params { + Params::None => { + let mut polls = self.polls.lock().unwrap(); + let id = polls.create_poll(PollFilter::Block(take_weak!(self.client).chain_info().best_block_number)); + to_value(&U256::from(id)) + }, + _ => Err(Error::invalid_params()) + } + } + + fn new_pending_transaction_filter(&self, params: Params) -> Result { + match params { + Params::None => { + let mut polls = self.polls.lock().unwrap(); + let pending_transactions = take_weak!(self.miner).pending_transactions_hashes(); + let id = polls.create_poll(PollFilter::PendingTransaction(pending_transactions)); + + to_value(&U256::from(id)) + }, + _ => Err(Error::invalid_params()) + } + } + + fn filter_changes(&self, params: Params) -> Result { + let client = take_weak!(self.client); + from_params::<(Index,)>(params) + .and_then(|(index,)| { + let mut polls = self.polls.lock().unwrap(); + match polls.poll_mut(&index.value()) { + None => Ok(Value::Array(vec![] as Vec)), + Some(filter) => match *filter { + PollFilter::Block(ref mut block_number) => { + // + 1, cause we want to return hashes including current block hash. + let current_number = client.chain_info().best_block_number + 1; + let hashes = (*block_number..current_number).into_iter() + .map(BlockID::Number) + .filter_map(|id| client.block_hash(id)) + .collect::>(); + + *block_number = current_number; + + to_value(&hashes) + }, + PollFilter::PendingTransaction(ref mut previous_hashes) => { + // get hashes of pending transactions + let current_hashes = take_weak!(self.miner).pending_transactions_hashes(); + + let new_hashes = + { + let previous_hashes_set = previous_hashes.iter().collect::>(); + + // find all new hashes + current_hashes + .iter() + .filter(|hash| !previous_hashes_set.contains(hash)) + .cloned() + .collect::>() + }; + + // save all hashes of pending transactions + *previous_hashes = current_hashes; + + // return new hashes + to_value(&new_hashes) + }, + PollFilter::Logs(ref mut block_number, ref mut previous_logs, ref filter) => { + // retrive the current block number + let current_number = client.chain_info().best_block_number; + + // check if we need to check pending hashes + let include_pending = filter.to_block == Some(BlockNumber::Pending); + + // build appropriate filter + let mut filter: EthcoreFilter = filter.clone().into(); + filter.from_block = BlockID::Number(*block_number); + filter.to_block = BlockID::Latest; + + // retrieve logs in range from_block..min(BlockID::Latest..to_block) + let mut logs = client.logs(filter.clone()) + .into_iter() + .map(From::from) + .collect::>(); + + // additionally retrieve pending logs + if include_pending { + let pending_logs = pending_logs(take_weak!(self.miner).deref(), &filter); + + // remove logs about which client was already notified about + let new_pending_logs: Vec<_> = pending_logs.iter() + .filter(|p| !previous_logs.contains(p)) + .cloned() + .collect(); + + // save all logs retrieved by client + *previous_logs = pending_logs.into_iter().collect(); + + // append logs array with new pending logs + logs.extend(new_pending_logs); + } + + // save current block number as next from block number + *block_number = current_number; + + to_value(&logs) + } + } + } + }) + } + + fn filter_logs(&self, params: Params) -> Result { + from_params::<(Index,)>(params) + .and_then(|(index,)| { + let mut polls = self.polls.lock().unwrap(); + match polls.poll(&index.value()) { + Some(&PollFilter::Logs(ref _block_number, ref _previous_log, ref filter)) => { + let include_pending = filter.to_block == Some(BlockNumber::Pending); + let filter: EthcoreFilter = filter.clone().into(); + let mut logs = take_weak!(self.client).logs(filter.clone()) + .into_iter() + .map(From::from) + .collect::>(); + + if include_pending { + logs.extend(pending_logs(take_weak!(self.miner).deref(), &filter)); + } + + to_value(&logs) + }, + // just empty array + _ => Ok(Value::Array(vec![] as Vec)), + } + }) + } + + fn uninstall_filter(&self, params: Params) -> Result { + from_params::<(Index,)>(params) + .and_then(|(index,)| { + self.polls.lock().unwrap().remove_poll(&index.value()); + to_value(&true) + }) + } +} diff --git a/rpc/src/v1/impls/mod.rs b/rpc/src/v1/impls/mod.rs index 13e4fd512..979463a5d 100644 --- a/rpc/src/v1/impls/mod.rs +++ b/rpc/src/v1/impls/mod.rs @@ -27,6 +27,7 @@ macro_rules! take_weak { mod web3; mod eth; +mod eth_filter; mod net; mod personal; mod ethcore; @@ -34,7 +35,8 @@ mod traces; mod rpc; pub use self::web3::Web3Client; -pub use self::eth::{EthClient, EthFilterClient, EthSigningUnsafeClient}; +pub use self::eth::{EthClient, EthSigningUnsafeClient}; +pub use self::eth_filter::EthFilterClient; pub use self::net::NetClient; pub use self::personal::PersonalClient; pub use self::ethcore::EthcoreClient; From f794018e958cdbe79383aaaefa03b702b21804ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sat, 28 May 2016 14:32:31 +0200 Subject: [PATCH 04/19] Separating eth_signing --- rpc/src/v1/impls/eth.rs | 52 ++---------------------- rpc/src/v1/impls/eth_filter.rs | 2 +- rpc/src/v1/impls/eth_signing.rs | 71 +++++++++++++++++++++++++++++++++ rpc/src/v1/impls/mod.rs | 4 +- 4 files changed, 78 insertions(+), 51 deletions(-) create mode 100644 rpc/src/v1/impls/eth_signing.rs diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 2c18640d7..f7c34ffa7 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -34,9 +34,9 @@ use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Act use ethcore::log_entry::LogEntry; use ethcore::filter::Filter as EthcoreFilter; use self::ethash::SeedHashCompute; -use v1::traits::{Eth, EthSigning}; -use v1::types::{Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, TransactionRequest, CallRequest, OptionalValue, Index, Filter, Log, Receipt}; -use v1::impls::{dispatch_transaction, sign_and_dispatch}; +use v1::traits::Eth; +use v1::types::{Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, CallRequest, OptionalValue, Index, Filter, Log, Receipt}; +use v1::impls::dispatch_transaction; use util::keys::store::AccountProvider; use serde; @@ -530,49 +530,3 @@ impl Eth for EthClient where }) } } - - - -/// Implementation of functions that require signing when no trusted signer is used. -pub struct EthSigningUnsafeClient where - C: BlockChainClient, - A: AccountProvider, - M: MinerService { - client: Weak, - accounts: Weak, - miner: Weak, -} - -impl EthSigningUnsafeClient where - C: BlockChainClient, - A: AccountProvider, - M: MinerService { - - /// Creates new EthClient. - pub fn new(client: &Arc, accounts: &Arc, miner: &Arc) - -> Self { - EthSigningUnsafeClient { - client: Arc::downgrade(client), - miner: Arc::downgrade(miner), - accounts: Arc::downgrade(accounts), - } - } -} - -impl EthSigning for EthSigningUnsafeClient where - C: BlockChainClient + 'static, - A: AccountProvider + 'static, - M: MinerService + 'static { - - fn send_transaction(&self, params: Params) -> Result { - from_params::<(TransactionRequest, )>(params) - .and_then(|(request, )| { - let accounts = take_weak!(self.accounts); - match accounts.account_secret(&request.from) { - Ok(secret) => sign_and_dispatch(&self.client, &self.miner, request, secret), - Err(_) => to_value(&H256::zero()) - } - }) - } - -} diff --git a/rpc/src/v1/impls/eth_filter.rs b/rpc/src/v1/impls/eth_filter.rs index a17e64052..c6aecdca2 100644 --- a/rpc/src/v1/impls/eth_filter.rs +++ b/rpc/src/v1/impls/eth_filter.rs @@ -21,7 +21,7 @@ use std::sync::{Arc, Weak, Mutex}; use std::collections::HashSet; use jsonrpc_core::*; use util::numbers::*; -use ethminer::{MinerService}; +use ethminer::MinerService; use ethcore::filter::Filter as EthcoreFilter; use ethcore::client::{BlockChainClient, BlockID}; use v1::traits::EthFilter; diff --git a/rpc/src/v1/impls/eth_signing.rs b/rpc/src/v1/impls/eth_signing.rs new file mode 100644 index 000000000..9c193e1d2 --- /dev/null +++ b/rpc/src/v1/impls/eth_signing.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 . + +//! Eth Signing RPC implementation. + +use std::sync::{Arc, Weak}; +use jsonrpc_core::*; +use ethminer::MinerService; +use ethcore::client::BlockChainClient; +use util::numbers::*; +use util::keys::store::AccountProvider; +use v1::traits::EthSigning; +use v1::types::TransactionRequest; +use v1::impls::sign_and_dispatch; + +/// Implementation of functions that require signing when no trusted signer is used. +pub struct EthSigningUnsafeClient where + C: BlockChainClient, + A: AccountProvider, + M: MinerService { + client: Weak, + accounts: Weak, + miner: Weak, +} + +impl EthSigningUnsafeClient where + C: BlockChainClient, + A: AccountProvider, + M: MinerService { + + /// Creates new EthClient. + pub fn new(client: &Arc, accounts: &Arc, miner: &Arc) + -> Self { + EthSigningUnsafeClient { + client: Arc::downgrade(client), + miner: Arc::downgrade(miner), + accounts: Arc::downgrade(accounts), + } + } +} + +impl EthSigning for EthSigningUnsafeClient where + C: BlockChainClient + 'static, + A: AccountProvider + 'static, + M: MinerService + 'static { + + fn send_transaction(&self, params: Params) -> Result { + from_params::<(TransactionRequest, )>(params) + .and_then(|(request, )| { + let accounts = take_weak!(self.accounts); + match accounts.account_secret(&request.from) { + Ok(secret) => sign_and_dispatch(&self.client, &self.miner, request, secret), + Err(_) => to_value(&H256::zero()) + } + }) + } + +} diff --git a/rpc/src/v1/impls/mod.rs b/rpc/src/v1/impls/mod.rs index 979463a5d..d3b7944f7 100644 --- a/rpc/src/v1/impls/mod.rs +++ b/rpc/src/v1/impls/mod.rs @@ -28,6 +28,7 @@ macro_rules! take_weak { mod web3; mod eth; mod eth_filter; +mod eth_signing; mod net; mod personal; mod ethcore; @@ -35,8 +36,9 @@ mod traces; mod rpc; pub use self::web3::Web3Client; -pub use self::eth::{EthClient, EthSigningUnsafeClient}; +pub use self::eth::EthClient; pub use self::eth_filter::EthFilterClient; +pub use self::eth_signing::EthSigningUnsafeClient; pub use self::net::NetClient; pub use self::personal::PersonalClient; pub use self::ethcore::EthcoreClient; From 6d5ba59515ac23cad62861b66b02ec96c1c6e0a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sat, 28 May 2016 17:07:40 +0200 Subject: [PATCH 05/19] Stubs for Personal Signer methods --- parity/main.rs | 5 +- parity/rpc_apis.rs | 11 ++- rpc/src/lib.rs | 1 + rpc/src/v1/helpers/mod.rs | 2 + .../src/v1/helpers}/signing_queue.rs | 35 ++++++---- rpc/src/v1/impls/eth_signing.rs | 28 ++++++++ rpc/src/v1/impls/mod.rs | 4 +- rpc/src/v1/impls/personal.rs | 2 +- rpc/src/v1/impls/personal_signer.rs | 69 +++++++++++++++++++ rpc/src/v1/mod.rs | 1 + rpc/src/v1/tests/mocked/eth_signing.rs | 17 +++++ rpc/src/v1/tests/mocked/personal_signer.rs | 17 +++++ rpc/src/v1/traits/mod.rs | 2 +- rpc/src/v1/traits/personal.rs | 23 +++++++ rpc/src/v1/types/transaction_request.rs | 2 +- signer/src/lib.rs | 2 - 16 files changed, 198 insertions(+), 23 deletions(-) rename {signer/src => rpc/src/v1/helpers}/signing_queue.rs (58%) create mode 100644 rpc/src/v1/impls/personal_signer.rs create mode 100644 rpc/src/v1/tests/mocked/eth_signing.rs create mode 100644 rpc/src/v1/tests/mocked/personal_signer.rs diff --git a/parity/main.rs b/parity/main.rs index 874bb35b5..5c8cc60e0 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -79,6 +79,7 @@ use std::fs::File; use std::str::{FromStr, from_utf8}; use std::thread::sleep; use std::time::Duration; +use std::collections::HashSet; use rustc_serialize::hex::FromHex; use ctrlc::CtrlC; use util::{H256, ToPretty, NetworkConfiguration, PayloadInfo, Bytes}; @@ -199,6 +200,8 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig) let sync = EthSync::register(service.network(), sync_config, client.clone(), miner.clone()); let deps_for_rpc_apis = Arc::new(rpc_apis::Dependencies { + signer_enabled: conf.args.flag_signer, + signer_queue: Arc::new(Mutex::new(HashSet::new())), client: client.clone(), sync: sync.clone(), secret_store: account_service.clone(), @@ -239,7 +242,7 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig) // Set up a signer let signer_server = signer::start(signer::Configuration { - enabled: conf.args.flag_signer, + enabled: deps_for_rpc_apis.signer_enabled, port: conf.args.flag_signer_port, }, signer::Dependencies { panic_handler: panic_handler.clone(), diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index 1f1994d9e..7d6e55fee 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -26,7 +26,7 @@ use util::RotatingLogger; use util::keys::store::AccountService; use util::network_settings::NetworkSettings; -use ethcore_rpc::Extendable; +use ethcore_rpc::{SigningQueue, Extendable}; pub enum Api { Web3, @@ -61,6 +61,8 @@ impl FromStr for Api { } pub struct Dependencies { + pub signer_enabled: bool, + pub signer_queue: Arc, pub client: Arc, pub sync: Arc, pub secret_store: Arc, @@ -115,7 +117,12 @@ pub fn setup_rpc(server: T, deps: Arc, apis: Option Api::Eth => { server.add_delegate(EthClient::new(&deps.client, &deps.sync, &deps.secret_store, &deps.miner, &deps.external_miner).to_delegate()); server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate()); - server.add_delegate(EthSigningUnsafeClient::new(&deps.client, &deps.secret_store, &deps.miner).to_delegate()); + + if deps.signer_enabled { + server.add_delegate(EthSigningQueueClient::new(&deps.signer_queue).to_delegate()); + } else { + server.add_delegate(EthSigningUnsafeClient::new(&deps.client, &deps.secret_store, &deps.miner).to_delegate()); + } }, Api::Personal => { server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner).to_delegate()) diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index a82c70af0..80ecf8b71 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -39,6 +39,7 @@ use self::jsonrpc_core::{IoHandler, IoDelegate}; pub use jsonrpc_http_server::{Server, RpcServerError}; pub mod v1; +pub use v1::SigningQueue; /// An object that can be extended with `IoDelegates` pub trait Extendable { diff --git a/rpc/src/v1/helpers/mod.rs b/rpc/src/v1/helpers/mod.rs index b1a5c05ba..8e5a5564d 100644 --- a/rpc/src/v1/helpers/mod.rs +++ b/rpc/src/v1/helpers/mod.rs @@ -16,6 +16,8 @@ mod poll_manager; mod poll_filter; +mod signing_queue; pub use self::poll_manager::PollManager; pub use self::poll_filter::PollFilter; +pub use self::signing_queue::SigningQueue; diff --git a/signer/src/signing_queue.rs b/rpc/src/v1/helpers/signing_queue.rs similarity index 58% rename from signer/src/signing_queue.rs rename to rpc/src/v1/helpers/signing_queue.rs index 611d467c2..b6ab6126f 100644 --- a/signer/src/signing_queue.rs +++ b/rpc/src/v1/helpers/signing_queue.rs @@ -14,44 +14,51 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::sync::Mutex; use std::collections::HashSet; -use rpc::v1::types::TransactionRequest; +use v1::types::TransactionRequest; -pub trait SigningQueue { - fn add_request(&mut self, transaction: TransactionRequest); +/// A queue of transactions awaiting to be confirmed and signed. +pub trait SigningQueue: Send + Sync { + /// Add new request to the queue. + fn add_request(&self, transaction: TransactionRequest); - fn remove_request(&mut self, id: TransactionRequest); + /// Remove request from the queue. + fn remove_request(&self, id: TransactionRequest); - fn requests(&self) -> &HashSet; + /// Return copy of all the requests in the queue. + fn requests(&self) -> HashSet; } -impl SigningQueue for HashSet { - fn add_request(&mut self, transaction: TransactionRequest) { - self.insert(transaction); +impl SigningQueue for Mutex> { + fn add_request(&self, transaction: TransactionRequest) { + self.lock().unwrap().insert(transaction); } - fn remove_request(&mut self, id: TransactionRequest) { - self.remove(&id); + fn remove_request(&self, id: TransactionRequest) { + self.lock().unwrap().remove(&id); } - fn requests(&self) -> &HashSet { - self + fn requests(&self) -> HashSet { + let queue = self.lock().unwrap(); + queue.clone() } } #[cfg(test)] mod test { + use std::sync::Mutex; use std::collections::HashSet; use util::hash::Address; use util::numbers::U256; - use rpc::v1::types::TransactionRequest; + use v1::types::TransactionRequest; use super::*; #[test] fn should_work_for_hashset() { // given - let mut queue = HashSet::new(); + let mut queue = Mutex::new(HashSet::new()); let request = TransactionRequest { from: Address::from(1), diff --git a/rpc/src/v1/impls/eth_signing.rs b/rpc/src/v1/impls/eth_signing.rs index 9c193e1d2..2a4c845b8 100644 --- a/rpc/src/v1/impls/eth_signing.rs +++ b/rpc/src/v1/impls/eth_signing.rs @@ -22,10 +22,38 @@ use ethminer::MinerService; use ethcore::client::BlockChainClient; use util::numbers::*; use util::keys::store::AccountProvider; +use v1::helpers::SigningQueue; use v1::traits::EthSigning; use v1::types::TransactionRequest; use v1::impls::sign_and_dispatch; + +/// Implementation of functions that require signing when no trusted signer is used. +pub struct EthSigningQueueClient { + queue: Weak, +} + +impl EthSigningQueueClient { + /// Creates a new signing queue client given shared signing queue. + pub fn new(queue: &Arc) -> Self { + EthSigningQueueClient { + queue: Arc::downgrade(queue), + } + } +} + +impl EthSigning for EthSigningQueueClient { + fn send_transaction(&self, params: Params) -> Result { + from_params::<(TransactionRequest, )>(params) + .and_then(|(request, )| { + let queue = take_weak!(self.queue); + queue.add_request(request); + // TODO [ToDr] Block and wait for confirmation? + to_value(&H256::zero()) + }) + } +} + /// Implementation of functions that require signing when no trusted signer is used. pub struct EthSigningUnsafeClient where C: BlockChainClient, diff --git a/rpc/src/v1/impls/mod.rs b/rpc/src/v1/impls/mod.rs index d3b7944f7..d3a9b70a2 100644 --- a/rpc/src/v1/impls/mod.rs +++ b/rpc/src/v1/impls/mod.rs @@ -31,6 +31,7 @@ mod eth_filter; mod eth_signing; mod net; mod personal; +mod personal_signer; mod ethcore; mod traces; mod rpc; @@ -38,9 +39,10 @@ mod rpc; pub use self::web3::Web3Client; pub use self::eth::EthClient; pub use self::eth_filter::EthFilterClient; -pub use self::eth_signing::EthSigningUnsafeClient; +pub use self::eth_signing::{EthSigningUnsafeClient, EthSigningQueueClient}; pub use self::net::NetClient; pub use self::personal::PersonalClient; +pub use self::personal_signer::SignerClient; pub use self::ethcore::EthcoreClient; pub use self::traces::TracesClient; pub use self::rpc::RpcClient; diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 19e902996..30d541772 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -20,7 +20,7 @@ use jsonrpc_core::*; use v1::traits::Personal; use v1::types::TransactionRequest; use v1::impls::sign_and_dispatch; -use util::keys::store::*; +use util::keys::store::AccountProvider; use util::numbers::*; use ethcore::client::BlockChainClient; use ethminer::MinerService; diff --git a/rpc/src/v1/impls/personal_signer.rs b/rpc/src/v1/impls/personal_signer.rs new file mode 100644 index 000000000..bfb3c9eb5 --- /dev/null +++ b/rpc/src/v1/impls/personal_signer.rs @@ -0,0 +1,69 @@ +// 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 . + +//! Transactions Confirmations (personal) rpc implementation + +use std::sync::{Arc, Weak}; +use jsonrpc_core::*; +use v1::traits::SignerPersonal; +use v1::types::TransactionRequest; +use v1::impls::sign_and_dispatch; +use v1::helpers::SigningQueue; +use util::keys::store::AccountProvider; +use util::numbers::*; +use ethcore::client::BlockChainClient; +use ethminer::MinerService; + +/// Transactions confirmation (personal) rpc implementation. +pub struct SignerClient + where A: AccountProvider, C: BlockChainClient, M: MinerService { + queue: Weak, + accounts: Weak, + client: Weak, + miner: Weak, +} + +impl SignerClient + where A: AccountProvider, C: BlockChainClient, M: MinerService { + + /// Create new instance of signer client. + pub fn new(store: &Arc, client: &Arc, miner: &Arc, queue: &Arc) -> Self { + SignerClient { + queue: Arc::downgrade(queue), + accounts: Arc::downgrade(store), + client: Arc::downgrade(client), + miner: Arc::downgrade(miner), + } + } +} + +impl SignerPersonal for SignerClient + where A: AccountProvider, C: BlockChainClient, M: MinerService { + + fn transactions_to_confirm(&self, params: Params) -> Result { + let queue = take_weak!(self.queue); + to_value(&queue.requests()) + } + + fn confirm_transaction(&self, params: Params) -> Result { + Err(Error::internal_error()) + } + + fn reject_transaction(&self, params: Params) -> Result { + Err(Error::internal_error()) + } +} + diff --git a/rpc/src/v1/mod.rs b/rpc/src/v1/mod.rs index 49f9e3a38..308e921d2 100644 --- a/rpc/src/v1/mod.rs +++ b/rpc/src/v1/mod.rs @@ -27,3 +27,4 @@ pub mod types; pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Personal, Net, Ethcore, Traces, Rpc}; pub use self::impls::*; +pub use self::helpers::SigningQueue; diff --git a/rpc/src/v1/tests/mocked/eth_signing.rs b/rpc/src/v1/tests/mocked/eth_signing.rs new file mode 100644 index 000000000..05ceaf3d3 --- /dev/null +++ b/rpc/src/v1/tests/mocked/eth_signing.rs @@ -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 . + + diff --git a/rpc/src/v1/tests/mocked/personal_signer.rs b/rpc/src/v1/tests/mocked/personal_signer.rs new file mode 100644 index 000000000..05ceaf3d3 --- /dev/null +++ b/rpc/src/v1/tests/mocked/personal_signer.rs @@ -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 . + + diff --git a/rpc/src/v1/traits/mod.rs b/rpc/src/v1/traits/mod.rs index e5d5f3324..2355d6137 100644 --- a/rpc/src/v1/traits/mod.rs +++ b/rpc/src/v1/traits/mod.rs @@ -31,7 +31,7 @@ pub mod rpc; pub use self::web3::Web3; pub use self::eth::{Eth, EthFilter, EthSigning}; pub use self::net::Net; -pub use self::personal::Personal; +pub use self::personal::{Personal, SignerPersonal}; pub use self::ethcore::Ethcore; pub use self::traces::Traces; pub use self::rpc::Rpc; diff --git a/rpc/src/v1/traits/personal.rs b/rpc/src/v1/traits/personal.rs index 0619d7ada..d8eb7ee75 100644 --- a/rpc/src/v1/traits/personal.rs +++ b/rpc/src/v1/traits/personal.rs @@ -43,3 +43,26 @@ pub trait Personal: Sized + Send + Sync + 'static { delegate } } + +/// Personal extension for transactions confirmations rpc interface. +pub trait SignerPersonal: Sized + Send + Sync + 'static { + + /// Returns a list of transactions to confirm. + fn transactions_to_confirm(&self, _: Params) -> Result; + + /// Confirm and send a specific transaction. + fn confirm_transaction(&self, _: Params) -> Result; + + /// Reject the transaction request. + fn reject_transaction(&self, _: Params) -> Result; + + /// 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("personal_transactionsToConfirm", SignerPersonal::transactions_to_confirm); + delegate.add_method("personal_confirmTransaction", SignerPersonal::confirm_transaction); + delegate.add_method("personal_rejectTransaction", SignerPersonal::reject_transaction); + delegate + } +} + diff --git a/rpc/src/v1/types/transaction_request.rs b/rpc/src/v1/types/transaction_request.rs index 1b51e6b12..276f14f07 100644 --- a/rpc/src/v1/types/transaction_request.rs +++ b/rpc/src/v1/types/transaction_request.rs @@ -21,7 +21,7 @@ use util::numbers::U256; use v1::types::bytes::Bytes; /// Transaction request coming from RPC -#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Deserialize)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)] pub struct TransactionRequest { /// Sender pub from: Address, diff --git a/signer/src/lib.rs b/signer/src/lib.rs index a39fe68f0..60f7e9aba 100644 --- a/signer/src/lib.rs +++ b/signer/src/lib.rs @@ -53,9 +53,7 @@ extern crate ethcore_rpc as rpc; extern crate jsonrpc_core; extern crate ws; -mod signing_queue; mod ws_server; - pub use ws_server::*; #[cfg(test)] From 8c3b56511ae0a45ced33e8236420bb953bc14099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sat, 28 May 2016 17:22:43 +0200 Subject: [PATCH 06/19] Test for EthSigningQueueClient --- rpc/src/v1/helpers/signing_queue.rs | 2 +- rpc/src/v1/tests/mocked/eth_signing.rs | 60 ++++++++++++++++++++++++++ rpc/src/v1/tests/mocked/mod.rs | 2 + 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/rpc/src/v1/helpers/signing_queue.rs b/rpc/src/v1/helpers/signing_queue.rs index b6ab6126f..c7abd4924 100644 --- a/rpc/src/v1/helpers/signing_queue.rs +++ b/rpc/src/v1/helpers/signing_queue.rs @@ -58,7 +58,7 @@ mod test { #[test] fn should_work_for_hashset() { // given - let mut queue = Mutex::new(HashSet::new()); + let queue = Mutex::new(HashSet::new()); let request = TransactionRequest { from: Address::from(1), diff --git a/rpc/src/v1/tests/mocked/eth_signing.rs b/rpc/src/v1/tests/mocked/eth_signing.rs index 05ceaf3d3..07adc44b5 100644 --- a/rpc/src/v1/tests/mocked/eth_signing.rs +++ b/rpc/src/v1/tests/mocked/eth_signing.rs @@ -14,4 +14,64 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::collections::HashSet; +use std::sync::{Arc, Mutex}; +use jsonrpc_core::IoHandler; +use v1::impls::EthSigningQueueClient; +use v1::traits::EthSigning; +use v1::helpers::SigningQueue; +use util::keys::TestAccount; +struct EthSignerTester { + pub queue: Arc, + pub io: IoHandler, +} + +impl Default for EthSignerTester { + fn default() -> Self { + let queue : Arc = Arc::new(Mutex::new(HashSet::new())); + let io = IoHandler::new(); + io.add_delegate(EthSigningQueueClient::new(&queue).to_delegate()); + + EthSignerTester { + queue: queue, + io: io, + } + } +} + +fn eth_signer() -> EthSignerTester { + EthSignerTester::default() +} + + + +#[test] +fn should_add_transaction_to_queue() { + // given + let tester = eth_signer(); + let account = TestAccount::new("123"); + let address = account.address(); + assert_eq!(tester.queue.requests().len(), 0); + + // when + let request = r#"{ + "jsonrpc": "2.0", + "method": "eth_sendTransaction", + "params": [{ + "from": ""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", + "gas": "0x76c0", + "gasPrice": "0x9184e72a000", + "value": "0x9184e72a" + }], + "id": 1 + }"#; + let response = r#"{"jsonrpc":"2.0","result":"0x0000000000000000000000000000000000000000000000000000000000000000","id":1}"#; + + + // then + assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); + assert_eq!(tester.queue.requests().len(), 1); + +} diff --git a/rpc/src/v1/tests/mocked/mod.rs b/rpc/src/v1/tests/mocked/mod.rs index 98caf6e08..450960f78 100644 --- a/rpc/src/v1/tests/mocked/mod.rs +++ b/rpc/src/v1/tests/mocked/mod.rs @@ -17,8 +17,10 @@ //! RPC serialization tests. mod eth; +mod eth_signing; mod net; mod web3; mod personal; +mod personal_signer; mod ethcore; mod rpc; From 99f9747a3f916cc1ffa7eac1f8d8bf9b5bc94d83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sat, 28 May 2016 18:50:00 +0200 Subject: [PATCH 07/19] TransactionConfirmation API --- rpc/src/v1/helpers/mod.rs | 2 +- rpc/src/v1/helpers/signing_queue.rs | 52 ++++--- rpc/src/v1/impls/personal_signer.rs | 36 ++++- rpc/src/v1/mod.rs | 2 +- rpc/src/v1/tests/mocked/eth_signing.rs | 20 ++- rpc/src/v1/tests/mocked/personal.rs | 2 +- rpc/src/v1/tests/mocked/personal_signer.rs | 152 +++++++++++++++++++++ rpc/src/v1/traits/mod.rs | 2 +- rpc/src/v1/traits/personal.rs | 8 +- rpc/src/v1/types/mod.rs.in | 2 +- rpc/src/v1/types/transaction_request.rs | 39 ++++++ 11 files changed, 274 insertions(+), 43 deletions(-) diff --git a/rpc/src/v1/helpers/mod.rs b/rpc/src/v1/helpers/mod.rs index 8e5a5564d..2acf98bf2 100644 --- a/rpc/src/v1/helpers/mod.rs +++ b/rpc/src/v1/helpers/mod.rs @@ -20,4 +20,4 @@ mod signing_queue; pub use self::poll_manager::PollManager; pub use self::poll_filter::PollFilter; -pub use self::signing_queue::SigningQueue; +pub use self::signing_queue::{ConfirmationsQueue, SigningQueue}; diff --git a/rpc/src/v1/helpers/signing_queue.rs b/rpc/src/v1/helpers/signing_queue.rs index c7abd4924..4e26fbaf6 100644 --- a/rpc/src/v1/helpers/signing_queue.rs +++ b/rpc/src/v1/helpers/signing_queue.rs @@ -15,41 +15,57 @@ // along with Parity. If not, see . use std::sync::Mutex; -use std::collections::HashSet; -use v1::types::TransactionRequest; +use std::collections::HashMap; +use v1::types::{TransactionRequest, TransactionConfirmation}; +use util::U256; /// A queue of transactions awaiting to be confirmed and signed. pub trait SigningQueue: Send + Sync { /// Add new request to the queue. - fn add_request(&self, transaction: TransactionRequest); + fn add_request(&self, transaction: TransactionRequest) -> U256; /// Remove request from the queue. - fn remove_request(&self, id: TransactionRequest); + fn remove_request(&self, id: U256) -> Option; /// Return copy of all the requests in the queue. - fn requests(&self) -> HashSet; + fn requests(&self) -> Vec; } -impl SigningQueue for Mutex> { - fn add_request(&self, transaction: TransactionRequest) { - self.lock().unwrap().insert(transaction); +#[derive(Default)] +pub struct ConfirmationsQueue { + id: Mutex, + queue: Mutex>, +} + +impl SigningQueue for ConfirmationsQueue { + fn add_request(&self, transaction: TransactionRequest) -> U256 { + // Increment id + let id = { + let mut last_id = self.id.lock().unwrap(); + *last_id = *last_id + U256::from(1); + *last_id + }; + let mut queue = self.queue.lock().unwrap(); + queue.insert(id, TransactionConfirmation { + id: id, + transaction: transaction, + }); + id } - fn remove_request(&self, id: TransactionRequest) { - self.lock().unwrap().remove(&id); + fn remove_request(&self, id: U256) -> Option { + self.queue.lock().unwrap().remove(&id) } - fn requests(&self) -> HashSet { - let queue = self.lock().unwrap(); - queue.clone() + fn requests(&self) -> Vec { + let queue = self.queue.lock().unwrap(); + queue.values().cloned().collect() } } #[cfg(test)] mod test { - use std::sync::Mutex; - use std::collections::HashSet; use util::hash::Address; use util::numbers::U256; use v1::types::TransactionRequest; @@ -58,7 +74,7 @@ mod test { #[test] fn should_work_for_hashset() { // given - let queue = Mutex::new(HashSet::new()); + let queue = ConfirmationsQueue::default(); let request = TransactionRequest { from: Address::from(1), @@ -76,6 +92,8 @@ mod test { // then assert_eq!(all.len(), 1); - assert!(all.contains(&request)); + let el = all.get(0).unwrap(); + assert_eq!(el.id, U256::from(1)); + assert_eq!(el.transaction, request); } } diff --git a/rpc/src/v1/impls/personal_signer.rs b/rpc/src/v1/impls/personal_signer.rs index bfb3c9eb5..6afedfea9 100644 --- a/rpc/src/v1/impls/personal_signer.rs +++ b/rpc/src/v1/impls/personal_signer.rs @@ -18,8 +18,8 @@ use std::sync::{Arc, Weak}; use jsonrpc_core::*; -use v1::traits::SignerPersonal; -use v1::types::TransactionRequest; +use v1::traits::PersonalSigner; +use v1::types::TransactionModification; use v1::impls::sign_and_dispatch; use v1::helpers::SigningQueue; use util::keys::store::AccountProvider; @@ -50,20 +50,44 @@ impl SignerClient } } -impl SignerPersonal for SignerClient +impl PersonalSigner for SignerClient where A: AccountProvider, C: BlockChainClient, M: MinerService { - fn transactions_to_confirm(&self, params: Params) -> Result { + fn transactions_to_confirm(&self, _params: Params) -> Result { let queue = take_weak!(self.queue); to_value(&queue.requests()) } fn confirm_transaction(&self, params: Params) -> Result { - Err(Error::internal_error()) + from_params::<(U256, TransactionModification, String)>(params).and_then( + |(id, modification, pass)| { + let accounts = take_weak!(self.accounts); + let queue = take_weak!(self.queue); + queue.remove_request(id) + .and_then(|confirmation| { + let mut request = confirmation.transaction; + // apply modification + if let Some(gas_price) = modification.gas_price { + request.gas_price = Some(gas_price); + } + match accounts.locked_account_secret(&request.from, &pass) { + Ok(secret) => Some(sign_and_dispatch(&self.client, &self.miner, request, secret)), + Err(_) => None + } + }) + .unwrap_or_else(|| to_value(&H256::zero())) + } + ) } fn reject_transaction(&self, params: Params) -> Result { - Err(Error::internal_error()) + from_params::<(U256, )>(params).and_then( + |(id, )| { + let queue = take_weak!(self.queue); + let res = queue.remove_request(id); + to_value(&res.is_some()) + } + ) } } diff --git a/rpc/src/v1/mod.rs b/rpc/src/v1/mod.rs index 308e921d2..deb580d2b 100644 --- a/rpc/src/v1/mod.rs +++ b/rpc/src/v1/mod.rs @@ -25,6 +25,6 @@ pub mod traits; pub mod tests; pub mod types; -pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Personal, Net, Ethcore, Traces, Rpc}; +pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Personal, PersonalSigner, Net, Ethcore, Traces, Rpc}; pub use self::impls::*; pub use self::helpers::SigningQueue; diff --git a/rpc/src/v1/tests/mocked/eth_signing.rs b/rpc/src/v1/tests/mocked/eth_signing.rs index 07adc44b5..7522fabec 100644 --- a/rpc/src/v1/tests/mocked/eth_signing.rs +++ b/rpc/src/v1/tests/mocked/eth_signing.rs @@ -14,42 +14,40 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use std::collections::HashSet; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use jsonrpc_core::IoHandler; use v1::impls::EthSigningQueueClient; use v1::traits::EthSigning; -use v1::helpers::SigningQueue; +use v1::helpers::{ConfirmationsQueue, SigningQueue}; use util::keys::TestAccount; -struct EthSignerTester { +struct EthSigningTester { pub queue: Arc, pub io: IoHandler, } -impl Default for EthSignerTester { +impl Default for EthSigningTester { fn default() -> Self { - let queue : Arc = Arc::new(Mutex::new(HashSet::new())); + let queue: Arc = Arc::new(ConfirmationsQueue::default()); let io = IoHandler::new(); io.add_delegate(EthSigningQueueClient::new(&queue).to_delegate()); - EthSignerTester { + EthSigningTester { queue: queue, io: io, } } } -fn eth_signer() -> EthSignerTester { - EthSignerTester::default() +fn eth_signing() -> EthSigningTester { + EthSigningTester::default() } - #[test] fn should_add_transaction_to_queue() { // given - let tester = eth_signer(); + let tester = eth_signing(); let account = TestAccount::new("123"); let address = account.address(); assert_eq!(tester.queue.requests().len(), 0); diff --git a/rpc/src/v1/tests/mocked/personal.rs b/rpc/src/v1/tests/mocked/personal.rs index 991b13cba..8bc3ab3c8 100644 --- a/rpc/src/v1/tests/mocked/personal.rs +++ b/rpc/src/v1/tests/mocked/personal.rs @@ -176,4 +176,4 @@ fn sign_and_send_transaction() { let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#; assert_eq!(tester.io.handle_request(request.as_ref()), Some(response)); -} \ No newline at end of file +} diff --git a/rpc/src/v1/tests/mocked/personal_signer.rs b/rpc/src/v1/tests/mocked/personal_signer.rs index 05ceaf3d3..b6d28b986 100644 --- a/rpc/src/v1/tests/mocked/personal_signer.rs +++ b/rpc/src/v1/tests/mocked/personal_signer.rs @@ -14,4 +14,156 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::sync::Arc; +use std::str::FromStr; +use std::collections::HashMap; +use jsonrpc_core::IoHandler; +use util::numbers::*; +use util::keys::{TestAccount, TestAccountProvider}; +use ethcore::client::TestBlockChainClient; +use ethcore::transaction::{Transaction, Action}; +use v1::{SignerClient, PersonalSigner}; +use v1::tests::helpers::TestMinerService; +use v1::helpers::{SigningQueue, ConfirmationsQueue}; +use v1::types::TransactionRequest; + + +struct PersonalSignerTester { + queue: Arc, + accounts: Arc, + io: IoHandler, + miner: Arc, + // these unused fields are necessary to keep the data alive + // as the handler has only weak pointers. + _client: Arc, +} + +fn blockchain_client() -> Arc { + let client = TestBlockChainClient::new(); + Arc::new(client) +} + +fn accounts_provider() -> Arc { + let accounts = HashMap::new(); + let ap = TestAccountProvider::new(accounts); + Arc::new(ap) +} + +fn miner_service() -> Arc { + Arc::new(TestMinerService::default()) +} + +fn signer_tester() -> PersonalSignerTester { + let queue: Arc = Arc::new(ConfirmationsQueue::default()); + let accounts = accounts_provider(); + let client = blockchain_client(); + let miner = miner_service(); + + let io = IoHandler::new(); + io.add_delegate(SignerClient::new(&accounts, &client, &miner, &queue).to_delegate()); + + PersonalSignerTester { + queue: queue, + accounts: accounts, + io: io, + miner: miner, + _client: client, + } +} + + +#[test] +fn should_return_list_of_transactions_in_queue() { + // given + let tester = signer_tester(); + tester.queue.add_request(TransactionRequest { + from: Address::from(1), + to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), + gas_price: Some(U256::from(10_000)), + gas: Some(U256::from(10_000_000)), + value: Some(U256::from(1)), + data: None, + nonce: None, + }); + + // when + let request = r#"{"jsonrpc":"2.0","method":"personal_transactionsToConfirm","params":[],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":[{"id":"0x01","transaction":{"data":null,"from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x01"}}],"id":1}"#; + + // then + assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); +} + + +#[test] +fn should_reject_transaction_from_queue_without_dispatching() { + // given + let tester = signer_tester(); + tester.queue.add_request(TransactionRequest { + from: Address::from(1), + to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), + gas_price: Some(U256::from(10_000)), + gas: Some(U256::from(10_000_000)), + value: Some(U256::from(1)), + data: None, + nonce: None, + }); + assert_eq!(tester.queue.requests().len(), 1); + + // when + let request = r#"{"jsonrpc":"2.0","method":"personal_rejectTransaction","params":["0x01"],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; + + // then + assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); + assert_eq!(tester.queue.requests().len(), 0); + assert_eq!(tester.miner.imported_transactions.lock().unwrap().len(), 0); +} + +#[test] +fn should_confirm_transaction_and_dispatch() { + // given + let tester = signer_tester(); + let account = TestAccount::new("test"); + let address = account.address(); + let secret = account.secret.clone(); + let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(); + tester.accounts.accounts + .write() + .unwrap() + .insert(address, account); + tester.queue.add_request(TransactionRequest { + from: address, + to: Some(recipient), + gas_price: Some(U256::from(10_000)), + gas: Some(U256::from(10_000_000)), + value: Some(U256::from(1)), + data: None, + nonce: None, + }); + 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![] + }.sign(&secret); + + assert_eq!(tester.queue.requests().len(), 1); + + // when + let request = r#"{ + "jsonrpc":"2.0", + "method":"personal_confirmTransaction", + "params":["0x01", {"gasPrice":"0x1000"}, "test"], + "id":1 + }"#; + 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(&request), Some(response.to_owned())); + assert_eq!(tester.queue.requests().len(), 0); + assert_eq!(tester.miner.imported_transactions.lock().unwrap().len(), 1); +} diff --git a/rpc/src/v1/traits/mod.rs b/rpc/src/v1/traits/mod.rs index 2355d6137..5384b0ef4 100644 --- a/rpc/src/v1/traits/mod.rs +++ b/rpc/src/v1/traits/mod.rs @@ -31,7 +31,7 @@ pub mod rpc; pub use self::web3::Web3; pub use self::eth::{Eth, EthFilter, EthSigning}; pub use self::net::Net; -pub use self::personal::{Personal, SignerPersonal}; +pub use self::personal::{Personal, PersonalSigner}; pub use self::ethcore::Ethcore; pub use self::traces::Traces; pub use self::rpc::Rpc; diff --git a/rpc/src/v1/traits/personal.rs b/rpc/src/v1/traits/personal.rs index d8eb7ee75..cde66be2c 100644 --- a/rpc/src/v1/traits/personal.rs +++ b/rpc/src/v1/traits/personal.rs @@ -45,7 +45,7 @@ pub trait Personal: Sized + Send + Sync + 'static { } /// Personal extension for transactions confirmations rpc interface. -pub trait SignerPersonal: Sized + Send + Sync + 'static { +pub trait PersonalSigner: Sized + Send + Sync + 'static { /// Returns a list of transactions to confirm. fn transactions_to_confirm(&self, _: Params) -> Result; @@ -59,9 +59,9 @@ pub trait SignerPersonal: Sized + Send + Sync + 'static { /// 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("personal_transactionsToConfirm", SignerPersonal::transactions_to_confirm); - delegate.add_method("personal_confirmTransaction", SignerPersonal::confirm_transaction); - delegate.add_method("personal_rejectTransaction", SignerPersonal::reject_transaction); + delegate.add_method("personal_transactionsToConfirm", PersonalSigner::transactions_to_confirm); + delegate.add_method("personal_confirmTransaction", PersonalSigner::confirm_transaction); + delegate.add_method("personal_rejectTransaction", PersonalSigner::reject_transaction); delegate } } diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index 824a061ef..b4e82a28b 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -38,7 +38,7 @@ pub use self::log::Log; pub use self::optionals::OptionalValue; pub use self::sync::{SyncStatus, SyncInfo}; pub use self::transaction::Transaction; -pub use self::transaction_request::TransactionRequest; +pub use self::transaction_request::{TransactionRequest, TransactionConfirmation, TransactionModification}; pub use self::call_request::CallRequest; pub use self::receipt::Receipt; pub use self::trace::Trace; diff --git a/rpc/src/v1/types/transaction_request.rs b/rpc/src/v1/types/transaction_request.rs index 276f14f07..93d6a479b 100644 --- a/rpc/src/v1/types/transaction_request.rs +++ b/rpc/src/v1/types/transaction_request.rs @@ -40,6 +40,24 @@ pub struct TransactionRequest { pub nonce: Option, } +/// Transaction confirmation waiting in a queue +#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize)] +pub struct TransactionConfirmation { + /// Id of this confirmation + pub id: U256, + /// TransactionRequest + pub transaction: TransactionRequest, +} + +/// Possible modifications to the confirmed transaction sent by SystemUI +#[derive(Debug, PartialEq, Deserialize)] +pub struct TransactionModification { + /// Modified gas price + #[serde(rename="gasPrice")] + pub gas_price: Option, +} + + #[cfg(test)] mod tests { use std::str::FromStr; @@ -135,5 +153,26 @@ mod tests { nonce: None, }); } + + #[test] + fn should_deserialize_modification() { + // given + let s1 = r#"{ + "gasPrice":"0x0ba43b7400" + }"#; + let s2 = r#"{}"#; + + // when + let res1: TransactionModification = serde_json::from_str(s1).unwrap(); + let res2: TransactionModification = serde_json::from_str(s2).unwrap(); + + // then + assert_eq!(res1, TransactionModification { + gas_price: Some(U256::from_str("0ba43b7400").unwrap()), + }); + assert_eq!(res2, TransactionModification { + gas_price: None, + }); + } } From a7dfa83da1384cec65d888334bc5abcc4f72933f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sat, 28 May 2016 19:05:23 +0200 Subject: [PATCH 08/19] Exposing PersonalSigner API --- parity/main.rs | 3 +-- parity/rpc_apis.rs | 19 ++++++++++++++++--- rpc/src/lib.rs | 2 +- rpc/src/v1/helpers/signing_queue.rs | 1 + rpc/src/v1/impls/eth_signing.rs | 6 +++--- rpc/src/v1/impls/personal_signer.rs | 6 +++--- rpc/src/v1/mod.rs | 2 +- rpc/src/v1/tests/mocked/eth_signing.rs | 4 ++-- rpc/src/v1/tests/mocked/personal_signer.rs | 4 ++-- 9 files changed, 30 insertions(+), 17 deletions(-) diff --git a/parity/main.rs b/parity/main.rs index 5c8cc60e0..59f08c1fe 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -79,7 +79,6 @@ use std::fs::File; use std::str::{FromStr, from_utf8}; use std::thread::sleep; use std::time::Duration; -use std::collections::HashSet; use rustc_serialize::hex::FromHex; use ctrlc::CtrlC; use util::{H256, ToPretty, NetworkConfiguration, PayloadInfo, Bytes}; @@ -201,7 +200,7 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig) let deps_for_rpc_apis = Arc::new(rpc_apis::Dependencies { signer_enabled: conf.args.flag_signer, - signer_queue: Arc::new(Mutex::new(HashSet::new())), + signer_queue: Arc::new(rpc_apis::ConfirmationsQueue::default()), client: client.clone(), sync: sync.clone(), secret_store: account_service.clone(), diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index 7d6e55fee..4278fb060 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -26,7 +26,17 @@ use util::RotatingLogger; use util::keys::store::AccountService; use util::network_settings::NetworkSettings; -use ethcore_rpc::{SigningQueue, Extendable}; +#[cfg(feature="rpc")] +pub use ethcore_rpc::ConfirmationsQueue; +#[cfg(not(feature="rpc"))] +#[derive(Default)] +pub struct ConfirmationsQueue; + +#[cfg(feature="rpc")] +use ethcore_rpc::Extendable; + + + pub enum Api { Web3, @@ -62,7 +72,7 @@ impl FromStr for Api { pub struct Dependencies { pub signer_enabled: bool, - pub signer_queue: Arc, + pub signer_queue: Arc, pub client: Arc, pub sync: Arc, pub secret_store: Arc, @@ -125,7 +135,10 @@ pub fn setup_rpc(server: T, deps: Arc, apis: Option } }, Api::Personal => { - server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner).to_delegate()) + server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner).to_delegate()); + if deps.signer_enabled { + server.add_delegate(SignerClient::new(&deps.secret_store, &deps.client, &deps.miner, &deps.signer_queue).to_delegate()); + } }, Api::Ethcore => { server.add_delegate(EthcoreClient::new(&deps.miner, deps.logger.clone(), deps.settings.clone()).to_delegate()) diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 80ecf8b71..699ee9ba1 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -39,7 +39,7 @@ use self::jsonrpc_core::{IoHandler, IoDelegate}; pub use jsonrpc_http_server::{Server, RpcServerError}; pub mod v1; -pub use v1::SigningQueue; +pub use v1::{SigningQueue, ConfirmationsQueue}; /// An object that can be extended with `IoDelegates` pub trait Extendable { diff --git a/rpc/src/v1/helpers/signing_queue.rs b/rpc/src/v1/helpers/signing_queue.rs index 4e26fbaf6..b2e89522d 100644 --- a/rpc/src/v1/helpers/signing_queue.rs +++ b/rpc/src/v1/helpers/signing_queue.rs @@ -31,6 +31,7 @@ pub trait SigningQueue: Send + Sync { fn requests(&self) -> Vec; } +/// Queue for all unconfirmed transactions. #[derive(Default)] pub struct ConfirmationsQueue { id: Mutex, diff --git a/rpc/src/v1/impls/eth_signing.rs b/rpc/src/v1/impls/eth_signing.rs index 2a4c845b8..f4a972fb5 100644 --- a/rpc/src/v1/impls/eth_signing.rs +++ b/rpc/src/v1/impls/eth_signing.rs @@ -22,7 +22,7 @@ use ethminer::MinerService; use ethcore::client::BlockChainClient; use util::numbers::*; use util::keys::store::AccountProvider; -use v1::helpers::SigningQueue; +use v1::helpers::{SigningQueue, ConfirmationsQueue}; use v1::traits::EthSigning; use v1::types::TransactionRequest; use v1::impls::sign_and_dispatch; @@ -30,12 +30,12 @@ use v1::impls::sign_and_dispatch; /// Implementation of functions that require signing when no trusted signer is used. pub struct EthSigningQueueClient { - queue: Weak, + queue: Weak, } impl EthSigningQueueClient { /// Creates a new signing queue client given shared signing queue. - pub fn new(queue: &Arc) -> Self { + pub fn new(queue: &Arc) -> Self { EthSigningQueueClient { queue: Arc::downgrade(queue), } diff --git a/rpc/src/v1/impls/personal_signer.rs b/rpc/src/v1/impls/personal_signer.rs index 6afedfea9..cf4e927ac 100644 --- a/rpc/src/v1/impls/personal_signer.rs +++ b/rpc/src/v1/impls/personal_signer.rs @@ -21,7 +21,7 @@ use jsonrpc_core::*; use v1::traits::PersonalSigner; use v1::types::TransactionModification; use v1::impls::sign_and_dispatch; -use v1::helpers::SigningQueue; +use v1::helpers::{SigningQueue, ConfirmationsQueue}; use util::keys::store::AccountProvider; use util::numbers::*; use ethcore::client::BlockChainClient; @@ -30,7 +30,7 @@ use ethminer::MinerService; /// Transactions confirmation (personal) rpc implementation. pub struct SignerClient where A: AccountProvider, C: BlockChainClient, M: MinerService { - queue: Weak, + queue: Weak, accounts: Weak, client: Weak, miner: Weak, @@ -40,7 +40,7 @@ impl SignerClient where A: AccountProvider, C: BlockChainClient, M: MinerService { /// Create new instance of signer client. - pub fn new(store: &Arc, client: &Arc, miner: &Arc, queue: &Arc) -> Self { + pub fn new(store: &Arc, client: &Arc, miner: &Arc, queue: &Arc) -> Self { SignerClient { queue: Arc::downgrade(queue), accounts: Arc::downgrade(store), diff --git a/rpc/src/v1/mod.rs b/rpc/src/v1/mod.rs index deb580d2b..54628d892 100644 --- a/rpc/src/v1/mod.rs +++ b/rpc/src/v1/mod.rs @@ -27,4 +27,4 @@ pub mod types; pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Personal, PersonalSigner, Net, Ethcore, Traces, Rpc}; pub use self::impls::*; -pub use self::helpers::SigningQueue; +pub use self::helpers::{SigningQueue, ConfirmationsQueue}; diff --git a/rpc/src/v1/tests/mocked/eth_signing.rs b/rpc/src/v1/tests/mocked/eth_signing.rs index 7522fabec..6eb6e3fd6 100644 --- a/rpc/src/v1/tests/mocked/eth_signing.rs +++ b/rpc/src/v1/tests/mocked/eth_signing.rs @@ -22,13 +22,13 @@ use v1::helpers::{ConfirmationsQueue, SigningQueue}; use util::keys::TestAccount; struct EthSigningTester { - pub queue: Arc, + pub queue: Arc, pub io: IoHandler, } impl Default for EthSigningTester { fn default() -> Self { - let queue: Arc = Arc::new(ConfirmationsQueue::default()); + let queue = Arc::new(ConfirmationsQueue::default()); let io = IoHandler::new(); io.add_delegate(EthSigningQueueClient::new(&queue).to_delegate()); diff --git a/rpc/src/v1/tests/mocked/personal_signer.rs b/rpc/src/v1/tests/mocked/personal_signer.rs index b6d28b986..cd1f81d9a 100644 --- a/rpc/src/v1/tests/mocked/personal_signer.rs +++ b/rpc/src/v1/tests/mocked/personal_signer.rs @@ -29,7 +29,7 @@ use v1::types::TransactionRequest; struct PersonalSignerTester { - queue: Arc, + queue: Arc, accounts: Arc, io: IoHandler, miner: Arc, @@ -54,7 +54,7 @@ fn miner_service() -> Arc { } fn signer_tester() -> PersonalSignerTester { - let queue: Arc = Arc::new(ConfirmationsQueue::default()); + let queue = Arc::new(ConfirmationsQueue::default()); let accounts = accounts_provider(); let client = blockchain_client(); let miner = miner_service(); From d4e66ba52f2f5d7cdbd98731cb8644afda708023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sat, 28 May 2016 19:26:01 +0200 Subject: [PATCH 09/19] Defining ApiSets dependent on context --- parity/dapps.rs | 3 +-- parity/rpc.rs | 2 +- parity/rpc_apis.rs | 30 +++++++++++++++++++++--------- parity/signer.rs | 3 +-- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/parity/dapps.rs b/parity/dapps.rs index 4ea74d7f2..91742d9e3 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -84,8 +84,7 @@ pub fn setup_dapps_server( use ethcore_dapps as dapps; let server = dapps::ServerBuilder::new(); - // TODO [ToDr] Specify apis - let server = rpc_apis::setup_rpc(server, deps.apis.clone(), None); + let server = rpc_apis::setup_rpc(server, deps.apis.clone(), rpc_apis::ApiSet::UnsafeContext); let start_result = match auth { None => { server.start_unsecure_http(url) diff --git a/parity/rpc.rs b/parity/rpc.rs index af998ff97..66f504408 100644 --- a/parity/rpc.rs +++ b/parity/rpc.rs @@ -75,7 +75,7 @@ pub fn new_ipc(conf: IpcConfiguration, deps: &Dependencies) -> Option, deps: &Dependencies) -> Server { let apis = rpc_apis::from_str(apis); let server = Server::new(); - rpc_apis::setup_rpc(server, deps.apis.clone(), Some(apis)) + rpc_apis::setup_rpc(server, deps.apis.clone(), rpc_apis::ApiSet::List(apis)) } #[cfg(not(feature = "rpc"))] diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index 4278fb060..c73a70fee 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -35,9 +35,6 @@ pub struct ConfirmationsQueue; #[cfg(feature="rpc")] use ethcore_rpc::Extendable; - - - pub enum Api { Web3, Net, @@ -47,10 +44,17 @@ pub enum Api { Traces, Rpc, } + pub enum ApiError { UnknownApi(String) } +pub enum ApiSet { + SafeContext, + UnsafeContext, + List(Vec), +} + impl FromStr for Api { type Err = ApiError; @@ -108,14 +112,22 @@ pub fn from_str(apis: Vec<&str>) -> Vec { }) } -pub fn setup_rpc(server: T, deps: Arc, apis: Option>) -> T { +fn list_apis(apis: ApiSet, signer_enabled: bool) -> Vec { + match apis { + ApiSet::List(apis) => apis, + ApiSet::UnsafeContext if signer_enabled => { + vec![Api::Web3, Api::Net, Api::Eth, Api::Ethcore, Api::Traces, Api::Rpc] + } + _ => { + vec![Api::Web3, Api::Net, Api::Eth, Api::Personal, Api::Ethcore, Api::Traces, Api::Rpc] + } + } +} + +pub fn setup_rpc(server: T, deps: Arc, apis: ApiSet) -> T { use ethcore_rpc::v1::*; - let apis = match apis { - Some(api) => api, - None => vec![Api::Web3, Api::Net, Api::Eth, Api::Personal, Api::Ethcore, Api::Traces, Api::Rpc], - }; - + let apis = list_apis(apis, deps.signer_enabled); for api in &apis { match *api { Api::Web3 => { diff --git a/parity/signer.rs b/parity/signer.rs index b2224b106..d549b89cb 100644 --- a/parity/signer.rs +++ b/parity/signer.rs @@ -52,8 +52,7 @@ fn do_start(conf: Configuration, deps: Dependencies) -> SignerServer { let start_result = { let server = signer::ServerBuilder::new(); - // TODO [ToDr] Setup APIS - let server = rpc_apis::setup_rpc(server, deps.apis, None); + let server = rpc_apis::setup_rpc(server, deps.apis, rpc_apis::ApiSet::SafeContext); server.start(addr) }; From 1ba39538a717d4c74077d308a729a65d84b30791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sat, 28 May 2016 19:47:34 +0200 Subject: [PATCH 10/19] Removing types --- Cargo.lock | 5 ----- signer/Cargo.toml | 8 -------- signer/build.rs | 27 --------------------------- signer/src/lib.rs | 6 ------ signer/src/types/mod.rs | 23 ----------------------- signer/src/types/mod.rs.in | 25 ------------------------- 6 files changed, 94 deletions(-) delete mode 100644 signer/src/types/mod.rs delete mode 100644 signer/src/types/mod.rs.in diff --git a/Cargo.lock b/Cargo.lock index 7950ecc4a..62ec76bd4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,12 +356,7 @@ dependencies = [ "ethcore-util 1.2.0", "jsonrpc-core 2.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_codegen 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syntex 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)", "ws 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/signer/Cargo.toml b/signer/Cargo.toml index 51b1b1e8d..170c9320e 100644 --- a/signer/Cargo.toml +++ b/signer/Cargo.toml @@ -9,13 +9,8 @@ build = "build.rs" [build-dependencies] rustc_version = "0.1" -serde_codegen = { version = "0.7.0", optional = true } -syntex = "^0.32.0" [dependencies] -serde = "0.7.0" -serde_json = "0.7.0" -rustc-serialize = "0.3" jsonrpc-core = "2.0" log = "0.3" env_logger = "0.3" @@ -23,10 +18,7 @@ ws = "0.4.7" ethcore-util = { path = "../util" } ethcore-rpc = { path = "../rpc" } -serde_macros = { version = "0.7.0", optional = true } clippy = { version = "0.0.69", optional = true} [features] -default = ["serde_codegen"] -nightly = ["serde_macros"] dev = ["clippy"] diff --git a/signer/build.rs b/signer/build.rs index 2bcfc7da5..41b9a1b3e 100644 --- a/signer/build.rs +++ b/signer/build.rs @@ -19,34 +19,7 @@ extern crate rustc_version; use rustc_version::{version_meta, Channel}; fn main() { - serde::main(); if let Channel::Nightly = version_meta().channel { println!("cargo:rustc-cfg=nightly"); } } - -#[cfg(not(feature = "serde_macros"))] -mod serde { - extern crate syntex; - extern crate serde_codegen; - - use std::env; - use std::path::Path; - - pub fn main() { - let out_dir = env::var_os("OUT_DIR").unwrap(); - - let src = Path::new("src/types/mod.rs.in"); - let dst = Path::new(&out_dir).join("mod.rs"); - - let mut registry = syntex::Registry::new(); - - serde_codegen::register(&mut registry); - registry.expand("", &src, &dst).unwrap(); - } -} - -#[cfg(feature = "serde_macros")] -mod serde { - pub fn main() {} -} diff --git a/signer/src/lib.rs b/signer/src/lib.rs index 60f7e9aba..74018d9b1 100644 --- a/signer/src/lib.rs +++ b/signer/src/lib.rs @@ -17,8 +17,6 @@ #![warn(missing_docs)] #![cfg_attr(all(nightly, feature="dev"), feature(plugin))] #![cfg_attr(all(nightly, feature="dev"), plugin(clippy))] -// Generated by serde -#![cfg_attr(all(nightly, feature="dev"), allow(redundant_closure_call))] //! Signer module //! @@ -44,10 +42,6 @@ extern crate log; extern crate env_logger; -extern crate serde; -extern crate serde_json; -extern crate rustc_serialize; - extern crate ethcore_util as util; extern crate ethcore_rpc as rpc; extern crate jsonrpc_core; diff --git a/signer/src/types/mod.rs b/signer/src/types/mod.rs deleted file mode 100644 index d5e15046a..000000000 --- a/signer/src/types/mod.rs +++ /dev/null @@ -1,23 +0,0 @@ -// 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 . - -//! Reusable types with JSON Serialization. - -#[cfg(feature = "serde_macros")] -include!("mod.rs.in"); - -#[cfg(not(feature = "serde_macros"))] -include!(concat!(env!("OUT_DIR"), "/mod.rs")); diff --git a/signer/src/types/mod.rs.in b/signer/src/types/mod.rs.in deleted file mode 100644 index a59f81ece..000000000 --- a/signer/src/types/mod.rs.in +++ /dev/null @@ -1,25 +0,0 @@ -// 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 . - - - - - - - - - -// TODO [ToDr] Types are empty for now. But they are about to come. From ba296408d54245110c450a9193d755ea77b004f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 30 May 2016 18:50:11 +0200 Subject: [PATCH 11/19] Supporting sending notification to WS connected SystemUIs --- rpc/src/v1/helpers/signing_queue.rs | 271 +++++++++++++++++++++++++--- rpc/src/v1/impls/eth.rs | 2 +- rpc/src/v1/impls/eth_signing.rs | 8 +- rpc/src/v1/impls/mod.rs | 12 +- rpc/src/v1/impls/personal.rs | 2 +- rpc/src/v1/impls/personal_signer.rs | 18 +- 6 files changed, 273 insertions(+), 40 deletions(-) diff --git a/rpc/src/v1/helpers/signing_queue.rs b/rpc/src/v1/helpers/signing_queue.rs index b2e89522d..65aef8a33 100644 --- a/rpc/src/v1/helpers/signing_queue.rs +++ b/rpc/src/v1/helpers/signing_queue.rs @@ -14,10 +14,34 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use std::sync::Mutex; +use std::thread; +use std::time::{Instant, Duration}; +use std::sync::{mpsc, Mutex, RwLock}; use std::collections::HashMap; use v1::types::{TransactionRequest, TransactionConfirmation}; -use util::U256; +use util::{U256, H256}; + + +/// Messages that queue informs about +#[derive(Debug, PartialEq)] +pub enum QueueMessage { + /// Receiver should stop work upon receiving `Finish` message. + Finish, + /// Informs about new transaction request. + NewRequest(U256), +} + +/// Defines possible errors returned from queue receiving method. +#[derive(Debug, PartialEq)] +pub enum QueueError { + /// Returned when method has been already used (no receiver available). + AlreadyUsed, + /// Returned when receiver encounters an error. + ReceiverError(mpsc::RecvError), +} + +/// Message Receiver type +pub type QueueMessageReceiver = mpsc::Receiver; /// A queue of transactions awaiting to be confirmed and signed. pub trait SigningQueue: Send + Sync { @@ -25,17 +49,106 @@ pub trait SigningQueue: Send + Sync { fn add_request(&self, transaction: TransactionRequest) -> U256; /// Remove request from the queue. - fn remove_request(&self, id: U256) -> Option; + /// Notify possible waiters that transaction was rejected. + fn request_rejected(&self, id: U256) -> Option; + + /// Remove request from the queue. + /// Notify possible waiters that transaction was confirmed and got given hash. + fn request_confirmed(&self, id: U256, hash: H256) -> Option; + + /// Returns a request if it is contained in the queue. + fn peek(&self, id: &U256) -> Option; /// Return copy of all the requests in the queue. fn requests(&self) -> Vec; + + /// Blocks for some time waiting for confirmation. + /// Returns `None` when timeout reached or transaction was rejected. + /// Returns transaction hash when transaction was confirmed. + fn wait_with_timeout(&self, id: U256) -> Option; +} + +/// Time you need to confirm the transaction in UI. +/// Unless we have a multi-threaded RPC this will lock +/// any other incoming call! +const QUEUE_TIMEOUT_DURATION_SEC : u64 = 20; + +#[derive(Debug, Clone)] +enum QueueStatus { + Waiting, + Rejected, + Confirmed(H256), } /// Queue for all unconfirmed transactions. -#[derive(Default)] pub struct ConfirmationsQueue { id: Mutex, - queue: Mutex>, + waiters: RwLock>, + queue: RwLock>, + sender: Mutex>, + receiver: Mutex>>, +} + +impl Default for ConfirmationsQueue { + fn default() -> Self { + let (send, recv) = mpsc::channel(); + + ConfirmationsQueue { + id: Mutex::new(U256::from(0)), + waiters: RwLock::new(HashMap::new()), + queue: RwLock::new(HashMap::new()), + sender: Mutex::new(send), + receiver: Mutex::new(Some(recv)), + } + } +} + +impl ConfirmationsQueue { + /// Blocks the thread and starts listening for notifications. + /// For each event `listener` callback function will be invoked. + /// This method can be used only once. + pub fn start_listening(&self, listener: F) -> Result<(), QueueError> + where F: Fn(QueueMessage) -> () { + let recv = self.receiver.lock().unwrap().take(); + if let None = recv { + return Err(QueueError::AlreadyUsed); + } + let recv = recv.expect("Check for none is done earlier."); + + loop { + let message = try!(recv.recv().map_err(|e| QueueError::ReceiverError(e))); + if let QueueMessage::Finish = message { + return Ok(()); + } + + listener(message); + } + } + + /// Notifies receiver that the communcation is over. + pub fn finish(&self) { + self.notify(QueueMessage::Finish); + } + + fn notify(&self, message: QueueMessage) { + // We don't really care about the result + let _ = self.sender.lock().unwrap().send(message); + } + + fn remove(&self, id: U256) -> Option { + self.queue.write().unwrap().remove(&id) + } + + fn update_status(&self, id: U256, status: QueueStatus) { + let mut waiters = self.waiters.write().unwrap(); + waiters.insert(id, status); + } +} + +impl Drop for ConfirmationsQueue { + fn drop(&mut self) { + self.finish(); + } } impl SigningQueue for ConfirmationsQueue { @@ -46,38 +159,98 @@ impl SigningQueue for ConfirmationsQueue { *last_id = *last_id + U256::from(1); *last_id }; - let mut queue = self.queue.lock().unwrap(); - queue.insert(id, TransactionConfirmation { - id: id, - transaction: transaction, - }); + // Add request to queue + { + let mut queue = self.queue.write().unwrap(); + queue.insert(id, TransactionConfirmation { + id: id, + transaction: transaction, + }); + } + // Notify listeners + self.notify(QueueMessage::NewRequest(id)); id } - fn remove_request(&self, id: U256) -> Option { - self.queue.lock().unwrap().remove(&id) + fn peek(&self, id: &U256) -> Option { + self.queue.read().unwrap().get(id).cloned() + } + + fn request_rejected(&self, id: U256) -> Option { + let o = self.remove(id); + self.update_status(id, QueueStatus::Rejected); + o + } + + fn request_confirmed(&self, id: U256, hash: H256) -> Option { + let o = self.remove(id); + self.update_status(id, QueueStatus::Confirmed(hash)); + o } fn requests(&self) -> Vec { - let queue = self.queue.lock().unwrap(); + let queue = self.queue.read().unwrap(); queue.values().cloned().collect() } + + fn wait_with_timeout(&self, id: U256) -> Option { + { + let mut waiters = self.waiters.write().unwrap(); + let r = waiters.insert(id, QueueStatus::Waiting); + match r { + // This is ok, we can have many waiters + Some(QueueStatus::Waiting) | None => {}, + // There already was a response for someone. + // The one waiting for it will cleanup, so... + Some(v) => { + // ... insert old status back + waiters.insert(id, v.clone()); + if let QueueStatus::Confirmed(h) = v { + return Some(h); + } + return None; + }, + } + } + + // Now wait for a response + let deadline = Instant::now() + Duration::from_secs(QUEUE_TIMEOUT_DURATION_SEC); + while Instant::now() < deadline { + let status = { + let waiters = self.waiters.read().unwrap(); + waiters.get(&id).expect("Only the waiting thread can remove any message.").clone() + }; + + match status { + QueueStatus::Waiting => thread::sleep(Duration::from_millis(50)), + QueueStatus::Confirmed(h) => { + self.waiters.write().unwrap().remove(&id); + return Some(h); + }, + QueueStatus::Rejected => { + self.waiters.write().unwrap().remove(&id); + return None; + }, + } + } + // We reached the timeout. Just return `None` + None + } } #[cfg(test)] mod test { + use std::time::Duration; + use std::thread; + use std::sync::{Arc, Mutex}; use util::hash::Address; - use util::numbers::U256; + use util::numbers::{U256, H256}; use v1::types::TransactionRequest; use super::*; - #[test] - fn should_work_for_hashset() { - // given - let queue = ConfirmationsQueue::default(); - - let request = TransactionRequest { + fn request() -> TransactionRequest { + TransactionRequest { from: Address::from(1), to: Some(Address::from(2)), gas_price: None, @@ -85,7 +258,63 @@ mod test { value: Some(U256::from(10_000_000)), data: None, nonce: None, - }; + } + } + + #[test] + fn should_wait_for_hash() { + // given + let queue = Arc::new(ConfirmationsQueue::default()); + let request = request(); + + // when + let q = queue.clone(); + let handle = thread::spawn(move || { + let v = q.add_request(request); + q.wait_with_timeout(v).expect("Should return hash") + }); + + let id = U256::from(1); + while queue.peek(&id).is_none() { + // Just wait for the other thread to start + thread::sleep(Duration::from_millis(100)); + } + queue.request_confirmed(id, H256::from(1)); + + // then + assert_eq!(handle.join().expect("Thread should finish nicely"), H256::from(1)); + } + + #[test] + fn should_receive_notification() { + // given + let received = Arc::new(Mutex::new(None)); + let queue = Arc::new(ConfirmationsQueue::default()); + let request = request(); + + // when + let q = queue.clone(); + let r = received.clone(); + let handle = thread::spawn(move || { + q.start_listening(move |notification| { + let mut v = r.lock().unwrap(); + *v = Some(notification); + }).expect("Should be closed nicely.") + }); + let v = queue.add_request(request); + queue.finish(); + + // then + handle.join().expect("Thread should finish nicely"); + let r = received.lock().unwrap().take(); + assert_eq!(r, Some(QueueMessage::NewRequest(v))); + } + + #[test] + fn should_add_transactions() { + // given + let queue = ConfirmationsQueue::default(); + let request = request(); // when queue.add_request(request.clone()); diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index f7c34ffa7..44bdef243 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -497,7 +497,7 @@ impl Eth for EthClient where .and_then(|(raw_transaction, )| { let raw_transaction = raw_transaction.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) => to_value(&dispatch_transaction(&*take_weak!(self.client), &*take_weak!(self.miner), signed_transaction)), Err(_) => to_value(&H256::zero()), } }) diff --git a/rpc/src/v1/impls/eth_signing.rs b/rpc/src/v1/impls/eth_signing.rs index f4a972fb5..2ecc00f05 100644 --- a/rpc/src/v1/impls/eth_signing.rs +++ b/rpc/src/v1/impls/eth_signing.rs @@ -47,9 +47,9 @@ impl EthSigning for EthSigningQueueClient { from_params::<(TransactionRequest, )>(params) .and_then(|(request, )| { let queue = take_weak!(self.queue); - queue.add_request(request); - // TODO [ToDr] Block and wait for confirmation? - to_value(&H256::zero()) + let id = queue.add_request(request); + let result = queue.wait_with_timeout(id); + to_value(&result.unwrap_or_else(H256::new)) }) } } @@ -90,7 +90,7 @@ impl EthSigning for EthSigningUnsafeClient where .and_then(|(request, )| { let accounts = take_weak!(self.accounts); match accounts.account_secret(&request.from) { - Ok(secret) => sign_and_dispatch(&self.client, &self.miner, request, secret), + Ok(secret) => to_value(&sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), request, secret)), Err(_) => to_value(&H256::zero()) } }) diff --git a/rpc/src/v1/impls/mod.rs b/rpc/src/v1/impls/mod.rs index d3a9b70a2..4bf5c88e7 100644 --- a/rpc/src/v1/impls/mod.rs +++ b/rpc/src/v1/impls/mod.rs @@ -48,16 +48,14 @@ pub use self::traces::TracesClient; pub use self::rpc::RpcClient; use v1::types::TransactionRequest; -use std::sync::Weak; use ethminer::{AccountDetails, MinerService}; use ethcore::client::BlockChainClient; use ethcore::transaction::{Action, SignedTransaction, Transaction}; use util::numbers::*; use util::rlp::encode; use util::bytes::ToPretty; -use jsonrpc_core::{Error, to_value, Value}; -fn dispatch_transaction(client: &C, miner: &M, signed_transaction: SignedTransaction) -> Result +fn dispatch_transaction(client: &C, miner: &M, signed_transaction: SignedTransaction) -> H256 where C: BlockChainClient, M: MinerService { let hash = signed_transaction.hash(); @@ -71,18 +69,16 @@ fn dispatch_transaction(client: &C, miner: &M, signed_transaction: SignedT }; match import { - Ok(_) => to_value(&hash), + Ok(_) => hash, Err(e) => { warn!("Error sending transaction: {:?}", e); - to_value(&H256::zero()) + H256::zero() } } } -fn sign_and_dispatch(client: &Weak, miner: &Weak, request: TransactionRequest, secret: H256) -> Result +fn sign_and_dispatch(client: &C, miner: &M, request: TransactionRequest, secret: H256) -> H256 where C: BlockChainClient, M: MinerService { - let client = take_weak!(client); - let miner = take_weak!(miner); let signed_transaction = { Transaction { diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 30d541772..4a419f1e3 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -83,7 +83,7 @@ impl Personal for PersonalClient .and_then(|(request, password)| { let accounts = take_weak!(self.accounts); match accounts.locked_account_secret(&request.from, &password) { - Ok(secret) => sign_and_dispatch(&self.client, &self.miner, request, secret), + Ok(secret) => to_value(&sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), request, secret)), Err(_) => to_value(&H256::zero()), } }) diff --git a/rpc/src/v1/impls/personal_signer.rs b/rpc/src/v1/impls/personal_signer.rs index cf4e927ac..9fd197db2 100644 --- a/rpc/src/v1/impls/personal_signer.rs +++ b/rpc/src/v1/impls/personal_signer.rs @@ -63,19 +63,27 @@ impl PersonalSigner for SignerClient Some(sign_and_dispatch(&self.client, &self.miner, request, secret)), + Ok(secret) => { + let hash = sign_and_dispatch(&*client, &*miner, request, secret); + queue.request_confirmed(id, hash); + Some(to_value(&hash)) + }, Err(_) => None } }) - .unwrap_or_else(|| to_value(&H256::zero())) + .unwrap_or_else(|| { + queue.request_rejected(id); + to_value(&H256::zero()) + }) } ) } @@ -84,7 +92,7 @@ impl PersonalSigner for SignerClient(params).and_then( |(id, )| { let queue = take_weak!(self.queue); - let res = queue.remove_request(id); + let res = queue.request_rejected(id); to_value(&res.is_some()) } ) From 84882922b462559378ac7a551d7d19bad550ce69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 30 May 2016 19:30:16 +0200 Subject: [PATCH 12/19] Sending a notification on every new messages --- parity/signer.rs | 2 +- signer/src/ws_server/mod.rs | 35 +++++++++++++++++++---------------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/parity/signer.rs b/parity/signer.rs index d549b89cb..978e4e636 100644 --- a/parity/signer.rs +++ b/parity/signer.rs @@ -51,7 +51,7 @@ fn do_start(conf: Configuration, deps: Dependencies) -> SignerServer { }); let start_result = { - let server = signer::ServerBuilder::new(); + let server = signer::ServerBuilder::new(daps.apis.signer_queue.clone()); let server = rpc_apis::setup_rpc(server, deps.apis, rpc_apis::ApiSet::SafeContext); server.start(addr) }; diff --git a/signer/src/ws_server/mod.rs b/signer/src/ws_server/mod.rs index bc8fb33f8..910c1af85 100644 --- a/signer/src/ws_server/mod.rs +++ b/signer/src/ws_server/mod.rs @@ -25,7 +25,7 @@ use std::sync::Arc; use std::net::SocketAddr; use util::panics::{PanicHandler, OnPanicListener, MayPanic}; use jsonrpc_core::{IoHandler, IoDelegate}; -use rpc::Extendable; +use rpc::{Extendable, ConfirmationsQueue}; mod session; @@ -49,15 +49,10 @@ impl From for ServerError { /// Builder for `WebSockets` server pub struct ServerBuilder { + queue: Arc, handler: Arc, } -impl Default for ServerBuilder { - fn default() -> Self { - ServerBuilder::new() - } -} - impl Extendable for ServerBuilder { fn add_delegate(&self, delegate: IoDelegate) { self.handler.add_delegate(delegate); @@ -66,30 +61,40 @@ impl Extendable for ServerBuilder { impl ServerBuilder { /// Creates new `ServerBuilder` - pub fn new() -> Self { + pub fn new(queue: Arc) -> Self { ServerBuilder { - handler: Arc::new(IoHandler::new()) + queue: queue, + handler: Arc::new(IoHandler::new()), } } /// Starts a new `WebSocket` server in separate thread. /// Returns a `Server` handle which closes the server when droped. pub fn start(self, addr: SocketAddr) -> Result { - Server::start(addr, self.handler) + Server::start(addr, self.handler).and_then(|(server, broadcaster)| { + // Fire up queue notifications broadcasting + let queue = self.queue.clone(); + thread::spawn(move || { + queue.start_listening(|_message| { + broadcaster.send("new_message").unwrap(); + }).expect("It's the only place we are running start_listening. It shouldn't fail."); + }).expect("We should be able to create the thread"); + + Ok(server) + }) } } /// `WebSockets` server implementation. pub struct Server { handle: Option>>, - broadcaster: ws::Sender, panic_handler: Arc, } impl Server { /// Starts a new `WebSocket` server in separate thread. /// Returns a `Server` handle which closes the server when droped. - pub fn start(addr: SocketAddr, handler: Arc) -> Result { + fn start(addr: SocketAddr, handler: Arc) -> Result<(Server, ws::Sender), ServerError> { let config = { let mut config = ws::Settings::default(); config.max_connections = 5; @@ -111,11 +116,10 @@ impl Server { }); // Return a handle - Ok(Server { + Ok((Server { handle: Some(handle), - broadcaster: broadcaster, panic_handler: panic_handler, - }) + }, broadcaster)) } } @@ -127,7 +131,6 @@ impl MayPanic for Server { impl Drop for Server { fn drop(&mut self) { - self.broadcaster.shutdown().expect("WsServer should close nicely."); self.handle.take().unwrap().join().unwrap(); } } From b4bc395c6efd85bae818fffcb64c73462231172a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 30 May 2016 20:23:19 +0200 Subject: [PATCH 13/19] Adding logs to signing queue --- parity/signer.rs | 2 +- rpc/src/v1/helpers/signing_queue.rs | 8 ++++++- signer/src/lib.rs | 8 +++++-- signer/src/ws_server/mod.rs | 37 ++++++++++++++++++----------- 4 files changed, 37 insertions(+), 18 deletions(-) diff --git a/parity/signer.rs b/parity/signer.rs index 978e4e636..a7de993fb 100644 --- a/parity/signer.rs +++ b/parity/signer.rs @@ -51,7 +51,7 @@ fn do_start(conf: Configuration, deps: Dependencies) -> SignerServer { }); let start_result = { - let server = signer::ServerBuilder::new(daps.apis.signer_queue.clone()); + let server = signer::ServerBuilder::new(deps.apis.signer_queue.clone()); let server = rpc_apis::setup_rpc(server, deps.apis, rpc_apis::ApiSet::SafeContext); server.start(addr) }; diff --git a/rpc/src/v1/helpers/signing_queue.rs b/rpc/src/v1/helpers/signing_queue.rs index 65aef8a33..b0625b170 100644 --- a/rpc/src/v1/helpers/signing_queue.rs +++ b/rpc/src/v1/helpers/signing_queue.rs @@ -166,6 +166,7 @@ impl SigningQueue for ConfirmationsQueue { id: id, transaction: transaction, }); + debug!(target: "own_tx", "Signer: New transaction ({:?}) in confirmation queue.", id); } // Notify listeners self.notify(QueueMessage::NewRequest(id)); @@ -177,12 +178,14 @@ impl SigningQueue for ConfirmationsQueue { } fn request_rejected(&self, id: U256) -> Option { + debug!(target: "own_tx", "Signer: Transaction rejected ({:?}).", id); let o = self.remove(id); self.update_status(id, QueueStatus::Rejected); o } fn request_confirmed(&self, id: U256, hash: H256) -> Option { + debug!(target: "own_tx", "Signer: Transaction confirmed ({:?}).", id); let o = self.remove(id); self.update_status(id, QueueStatus::Confirmed(hash)); o @@ -213,6 +216,7 @@ impl SigningQueue for ConfirmationsQueue { } } + info!(target: "own_tx", "Signer: Awaiting transaction confirmation... ({:?}).", id); // Now wait for a response let deadline = Instant::now() + Duration::from_secs(QUEUE_TIMEOUT_DURATION_SEC); while Instant::now() < deadline { @@ -233,7 +237,9 @@ impl SigningQueue for ConfirmationsQueue { }, } } - // We reached the timeout. Just return `None` + // We reached the timeout. Just return `None` and make sure to remove waiting. + trace!(target: "own_tx", "Signer: Confirmation timeout reached... ({:?}).", id); + self.waiters.write().unwrap().remove(&id); None } } diff --git a/signer/src/lib.rs b/signer/src/lib.rs index 74018d9b1..8391d42b4 100644 --- a/signer/src/lib.rs +++ b/signer/src/lib.rs @@ -30,11 +30,15 @@ //! //! ``` //! extern crate ethcore_signer; +//! extern crate ethcore_rpc; //! -//! use ethcore_signer::Server; +//! use std::sync::Arc; +//! use ethcore_signer::ServerBuilder; +//! use ethcore_rpc::ConfirmationsQueue; //! //! fn main() { -//! let _server = Server::start("127.0.0.1:8084".parse().unwrap()); +//! let queue = Arc::new(ConfirmationsQueue::default()); +//! let _server = ServerBuilder::new(queue).start("127.0.0.1:8084".parse().unwrap()); //! } //! ``` diff --git a/signer/src/ws_server/mod.rs b/signer/src/ws_server/mod.rs index 910c1af85..0a4499af8 100644 --- a/signer/src/ws_server/mod.rs +++ b/signer/src/ws_server/mod.rs @@ -71,30 +71,22 @@ impl ServerBuilder { /// Starts a new `WebSocket` server in separate thread. /// Returns a `Server` handle which closes the server when droped. pub fn start(self, addr: SocketAddr) -> Result { - Server::start(addr, self.handler).and_then(|(server, broadcaster)| { - // Fire up queue notifications broadcasting - let queue = self.queue.clone(); - thread::spawn(move || { - queue.start_listening(|_message| { - broadcaster.send("new_message").unwrap(); - }).expect("It's the only place we are running start_listening. It shouldn't fail."); - }).expect("We should be able to create the thread"); - - Ok(server) - }) + Server::start(addr, self.handler, self.queue) } } /// `WebSockets` server implementation. pub struct Server { handle: Option>>, + broadcaster_handle: Option>, + queue: Arc, panic_handler: Arc, } impl Server { /// Starts a new `WebSocket` server in separate thread. /// Returns a `Server` handle which closes the server when droped. - fn start(addr: SocketAddr, handler: Arc) -> Result<(Server, ws::Sender), ServerError> { + fn start(addr: SocketAddr, handler: Arc, queue: Arc) -> Result { let config = { let mut config = ws::Settings::default(); config.max_connections = 5; @@ -108,6 +100,7 @@ impl Server { let panic_handler = PanicHandler::new_in_arc(); let ph = panic_handler.clone(); let broadcaster = ws.broadcaster(); + // Spawn a thread with event loop let handle = thread::spawn(move || { ph.catch_panic(move || { @@ -115,11 +108,25 @@ impl Server { }).unwrap() }); + // Spawn a thread for broadcasting + let ph = panic_handler.clone(); + let q = queue.clone(); + let broadcaster_handle = thread::spawn(move || { + ph.catch_panic(move || { + q.start_listening(|_message| { + // TODO [ToDr] Some better structure here for messages. + broadcaster.send("new_message").unwrap(); + }).expect("It's the only place we are running start_listening. It shouldn't fail.") + }).unwrap() + }); + // Return a handle - Ok((Server { + Ok(Server { handle: Some(handle), + broadcaster_handle: Some(broadcaster_handle), + queue: queue, panic_handler: panic_handler, - }, broadcaster)) + }) } } @@ -131,6 +138,8 @@ impl MayPanic for Server { impl Drop for Server { fn drop(&mut self) { + self.queue.finish(); + self.broadcaster_handle.take().unwrap().join().unwrap(); self.handle.take().unwrap().join().unwrap(); } } From baa2f7c5bbf55a4d929d3d63fc6a7afbba6d1eb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 30 May 2016 20:39:20 +0200 Subject: [PATCH 14/19] Shutting down broadcaster --- signer/src/ws_server/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/signer/src/ws_server/mod.rs b/signer/src/ws_server/mod.rs index 0a4499af8..c987d7a87 100644 --- a/signer/src/ws_server/mod.rs +++ b/signer/src/ws_server/mod.rs @@ -116,7 +116,8 @@ impl Server { q.start_listening(|_message| { // TODO [ToDr] Some better structure here for messages. broadcaster.send("new_message").unwrap(); - }).expect("It's the only place we are running start_listening. It shouldn't fail.") + }).expect("It's the only place we are running start_listening. It shouldn't fail."); + broadcaster.shutdown().expect("Broadcaster should close gently.") }).unwrap() }); From 4d29508b4c6ca8bf8874e5a3afbea26dd6505a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 31 May 2016 20:12:47 +0200 Subject: [PATCH 15/19] Minimal System UI --- Cargo.lock | 41 +++++++++++++++++++++++++++++---- signer/Cargo.toml | 3 ++- signer/src/lib.rs | 1 + signer/src/ws_server/mod.rs | 2 +- signer/src/ws_server/session.rs | 23 ++++++++++++++++++ 5 files changed, 63 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c81020f66..c2d455efe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -358,8 +358,9 @@ dependencies = [ "ethcore-util 1.2.0", "jsonrpc-core 2.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-minimal-sysui 0.1.0", "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "ws 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "ws 0.4.6 (git+https://github.com/ethcore/ws-rs.git)", ] [[package]] @@ -701,6 +702,22 @@ dependencies = [ "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "mio" +version = "0.5.0" +source = "git+https://github.com/carllerche/mio.git#f4aa49a9d2c4507fb33a4533d5238e0365f67c99" +dependencies = [ + "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "miow 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.5.0-pre (git+https://github.com/carllerche/nix-rust?rev=c4257f8a76)", + "slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "mio" version = "0.5.1" @@ -758,6 +775,15 @@ dependencies = [ "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "nix" +version = "0.5.0-pre" +source = "git+https://github.com/carllerche/nix-rust?rev=c4257f8a76#c4257f8a76b69b0d2e9a001d83e4bef67c03b23f" +dependencies = [ + "bitflags 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nix" version = "0.5.0" @@ -921,6 +947,10 @@ dependencies = [ "parity-dapps 0.3.0 (git+https://github.com/ethcore/parity-dapps-rs.git)", ] +[[package]] +name = "parity-minimal-sysui" +version = "0.1.0" + [[package]] name = "phf" version = "0.7.14" @@ -1426,15 +1456,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "ws" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.4.6" +source = "git+https://github.com/ethcore/ws-rs.git#c0c2a3fc30dc77c4e6d4d90756f8bc3b5cfbc311" dependencies = [ "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.5.0 (git+https://github.com/carllerche/mio.git)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "sha1 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "url 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/signer/Cargo.toml b/signer/Cargo.toml index 170c9320e..6b9f2036f 100644 --- a/signer/Cargo.toml +++ b/signer/Cargo.toml @@ -14,9 +14,10 @@ rustc_version = "0.1" jsonrpc-core = "2.0" log = "0.3" env_logger = "0.3" -ws = "0.4.7" +ws = { git = "https://github.com/ethcore/ws-rs.git" } ethcore-util = { path = "../util" } ethcore-rpc = { path = "../rpc" } +parity-minimal-sysui = { path = "../../parity-dapps-minimal-sysui-rs" } clippy = { version = "0.0.69", optional = true} diff --git a/signer/src/lib.rs b/signer/src/lib.rs index 8391d42b4..fb3e76cca 100644 --- a/signer/src/lib.rs +++ b/signer/src/lib.rs @@ -50,6 +50,7 @@ extern crate ethcore_util as util; extern crate ethcore_rpc as rpc; extern crate jsonrpc_core; extern crate ws; +extern crate parity_minimal_sysui as sysui; mod ws_server; pub use ws_server::*; diff --git a/signer/src/ws_server/mod.rs b/signer/src/ws_server/mod.rs index c987d7a87..0d55fd906 100644 --- a/signer/src/ws_server/mod.rs +++ b/signer/src/ws_server/mod.rs @@ -89,7 +89,7 @@ impl Server { fn start(addr: SocketAddr, handler: Arc, queue: Arc) -> Result { let config = { let mut config = ws::Settings::default(); - config.max_connections = 5; + config.max_connections = 10; config.method_strict = true; config }; diff --git a/signer/src/ws_server/session.rs b/signer/src/ws_server/session.rs index 258e05d5b..02850e739 100644 --- a/signer/src/ws_server/session.rs +++ b/signer/src/ws_server/session.rs @@ -17,6 +17,7 @@ //! Session handlers factory. use ws; +use sysui; use std::sync::Arc; use jsonrpc_core::IoHandler; @@ -26,6 +27,28 @@ pub struct Session { } impl ws::Handler for Session { + fn on_request(&mut self, req: &ws::Request) -> ws::Result<(ws::Response)> { + // Detect if it's a websocket request. + if req.header("sec-websocket-key").is_some() { + return ws::Response::from_request(req); + } + + // Otherwise try to serve a page. + sysui::handle(req.resource()) + .map(|f| { + let content_len = format!("{}", f.content.as_bytes().len()); + let mut res = ws::Response::ok(f.content.into()); + { + let mut headers = res.headers_mut(); + headers.push(("Server".into(), "Parity/SystemUI".as_bytes().to_vec())); + headers.push(("Connection".into(), "Closed".as_bytes().to_vec())); + headers.push(("Content-Length".into(), content_len.as_bytes().to_vec())); + headers.push(("Content-Type".into(), f.mime.as_bytes().to_vec())); + } + Ok(res) + }).unwrap_or_else(|| ws::Response::from_request(req)) + } + fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> { let req = try!(msg.as_text()); match self.handler.handle_request(req) { From ed0d60bc16b7118a3d461912eb27dbedbca6d61c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 31 May 2016 20:21:46 +0200 Subject: [PATCH 16/19] Fixing clippy warnings --- signer/src/ws_server/session.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/signer/src/ws_server/session.rs b/signer/src/ws_server/session.rs index 02850e739..442a44317 100644 --- a/signer/src/ws_server/session.rs +++ b/signer/src/ws_server/session.rs @@ -35,18 +35,22 @@ impl ws::Handler for Session { // Otherwise try to serve a page. sysui::handle(req.resource()) - .map(|f| { - let content_len = format!("{}", f.content.as_bytes().len()); - let mut res = ws::Response::ok(f.content.into()); - { - let mut headers = res.headers_mut(); - headers.push(("Server".into(), "Parity/SystemUI".as_bytes().to_vec())); - headers.push(("Connection".into(), "Closed".as_bytes().to_vec())); - headers.push(("Content-Length".into(), content_len.as_bytes().to_vec())); - headers.push(("Content-Type".into(), f.mime.as_bytes().to_vec())); - } - Ok(res) - }).unwrap_or_else(|| ws::Response::from_request(req)) + .map_or_else( + // return error + || ws::Response::from_request(req), + // or serve the file + |f| { + let content_len = format!("{}", f.content.as_bytes().len()); + let mut res = ws::Response::ok(f.content.into()); + { + let mut headers = res.headers_mut(); + headers.push(("Server".into(), b"Parity/SystemUI".to_vec())); + headers.push(("Connection".into(), b"Closed".to_vec())); + headers.push(("Content-Length".into(), content_len.as_bytes().to_vec())); + headers.push(("Content-Type".into(), f.mime.as_bytes().to_vec())); + } + Ok(res) + }) } fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> { From e9bcce05a1048b3e5720e3d5a5cedcb5fbc0426d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 2 Jun 2016 12:12:31 +0200 Subject: [PATCH 17/19] Refactoring the signing queue --- rpc/src/v1/helpers/signing_queue.rs | 236 ++++++++++++++++------------ rpc/src/v1/impls/eth_signing.rs | 2 +- 2 files changed, 133 insertions(+), 105 deletions(-) diff --git a/rpc/src/v1/helpers/signing_queue.rs b/rpc/src/v1/helpers/signing_queue.rs index b0625b170..4f860e438 100644 --- a/rpc/src/v1/helpers/signing_queue.rs +++ b/rpc/src/v1/helpers/signing_queue.rs @@ -16,19 +16,23 @@ use std::thread; use std::time::{Instant, Duration}; -use std::sync::{mpsc, Mutex, RwLock}; +use std::sync::{mpsc, Mutex, RwLock, Arc}; use std::collections::HashMap; use v1::types::{TransactionRequest, TransactionConfirmation}; use util::{U256, H256}; -/// Messages that queue informs about +/// Possible events happening in the queue that can be listened to. #[derive(Debug, PartialEq)] -pub enum QueueMessage { +pub enum QueueEvent { /// Receiver should stop work upon receiving `Finish` message. Finish, - /// Informs about new transaction request. + /// Informs about new request. NewRequest(U256), + /// Request rejected. + RequestRejected(U256), + /// Request resolved. + RequestConfirmed(U256), } /// Defines possible errors returned from queue receiving method. @@ -41,19 +45,20 @@ pub enum QueueError { } /// Message Receiver type -pub type QueueMessageReceiver = mpsc::Receiver; +pub type QueueEventReceiver = mpsc::Receiver; /// A queue of transactions awaiting to be confirmed and signed. pub trait SigningQueue: Send + Sync { /// Add new request to the queue. - fn add_request(&self, transaction: TransactionRequest) -> U256; + /// Returns a `ConfirmationPromise` that can be used to await for resolution of given request. + fn add_request(&self, transaction: TransactionRequest) -> ConfirmationPromise; - /// Remove request from the queue. - /// Notify possible waiters that transaction was rejected. + /// Removes a request from the queue. + /// Notifies possible token holders that transaction was rejected. fn request_rejected(&self, id: U256) -> Option; - /// Remove request from the queue. - /// Notify possible waiters that transaction was confirmed and got given hash. + /// Removes a request from the queue. + /// Notifies possible token holders that transaction was confirmed and given hash was assigned. fn request_confirmed(&self, id: U256, hash: H256) -> Option; /// Returns a request if it is contained in the queue. @@ -61,32 +66,89 @@ pub trait SigningQueue: Send + Sync { /// Return copy of all the requests in the queue. fn requests(&self) -> Vec; +} - /// Blocks for some time waiting for confirmation. - /// Returns `None` when timeout reached or transaction was rejected. - /// Returns transaction hash when transaction was confirmed. - fn wait_with_timeout(&self, id: U256) -> Option; +#[derive(Debug, PartialEq)] +enum ConfirmationResult { + /// The transaction has not yet been confirmed nor rejected. + Waiting, + /// The transaction has been rejected. + Rejected, + /// The transaction has been confirmed. + Confirmed(H256), } /// Time you need to confirm the transaction in UI. +/// This is the amount of time token holder will wait before +/// returning `None`. /// Unless we have a multi-threaded RPC this will lock /// any other incoming call! const QUEUE_TIMEOUT_DURATION_SEC : u64 = 20; -#[derive(Debug, Clone)] -enum QueueStatus { - Waiting, - Rejected, - Confirmed(H256), +/// A handle to submitted request. +/// Allows to block and wait for a resolution of that request. +pub struct ConfirmationToken { + result: Arc>, + handle: thread::Thread, + request: TransactionConfirmation, +} + +pub struct ConfirmationPromise { + id: U256, + result: Arc>, +} + +impl ConfirmationToken { + /// Submit solution to all listeners + fn resolve(&self, result: Option) { + let mut res = self.result.lock().unwrap(); + *res = result.map_or(ConfirmationResult::Rejected, |h| ConfirmationResult::Confirmed(h)); + // Notify listener + self.handle.unpark(); + } + + fn as_promise(&self) -> ConfirmationPromise { + ConfirmationPromise { + id: self.request.id, + result: self.result.clone(), + } + } +} + +impl ConfirmationPromise { + /// Blocks current thread and awaits for + /// resolution of the transaction (rejected / confirmed) + /// Returns `None` if transaction was rejected or timeout reached. + /// Returns `Some(hash)` if transaction was confirmed. + pub fn wait_with_timeout(&self) -> Option { + let timeout = Duration::from_secs(QUEUE_TIMEOUT_DURATION_SEC); + let deadline = Instant::now() + timeout; + + info!(target: "own_tx", "Signer: Awaiting transaction confirmation... ({:?}).", self.id); + while Instant::now() < deadline { + // Park thread + thread::park_timeout(timeout); + // Take confirmation result + let res = self.result.lock().unwrap(); + // Check the result + match *res { + ConfirmationResult::Rejected => return None, + ConfirmationResult::Confirmed(h) => return Some(h), + ConfirmationResult::Waiting => continue, + } + } + // We reached the timeout. Just return `None` and make sure to remove waiting. + trace!(target: "own_tx", "Signer: Confirmation timeout reached... ({:?}).", self.id); + None + } } /// Queue for all unconfirmed transactions. pub struct ConfirmationsQueue { id: Mutex, - waiters: RwLock>, - queue: RwLock>, - sender: Mutex>, - receiver: Mutex>>, + queue: RwLock>, + sender: Mutex>, + receiver: Mutex>>, } impl Default for ConfirmationsQueue { @@ -95,7 +157,6 @@ impl Default for ConfirmationsQueue { ConfirmationsQueue { id: Mutex::new(U256::from(0)), - waiters: RwLock::new(HashMap::new()), queue: RwLock::new(HashMap::new()), sender: Mutex::new(send), receiver: Mutex::new(Some(recv)), @@ -104,11 +165,11 @@ impl Default for ConfirmationsQueue { } impl ConfirmationsQueue { - /// Blocks the thread and starts listening for notifications. - /// For each event `listener` callback function will be invoked. - /// This method can be used only once. + /// Blocks the thread and starts listening for notifications regarding all actions in the queue. + /// For each event, `listener` callback will be invoked. + /// This method can be used only once (only single consumer of events can exist). pub fn start_listening(&self, listener: F) -> Result<(), QueueError> - where F: Fn(QueueMessage) -> () { + where F: Fn(QueueEvent) -> () { let recv = self.receiver.lock().unwrap().take(); if let None = recv { return Err(QueueError::AlreadyUsed); @@ -117,7 +178,7 @@ impl ConfirmationsQueue { loop { let message = try!(recv.recv().map_err(|e| QueueError::ReceiverError(e))); - if let QueueMessage::Finish = message { + if let QueueEvent::Finish = message { return Ok(()); } @@ -125,23 +186,35 @@ impl ConfirmationsQueue { } } - /// Notifies receiver that the communcation is over. + /// Notifies consumer that the communcation is over. + /// No more events will be sent after this function is invoked. pub fn finish(&self) { - self.notify(QueueMessage::Finish); + self.notify(QueueEvent::Finish); } - fn notify(&self, message: QueueMessage) { + /// Notifies receiver about the event happening in this queue. + fn notify(&self, message: QueueEvent) { // We don't really care about the result let _ = self.sender.lock().unwrap().send(message); } - fn remove(&self, id: U256) -> Option { - self.queue.write().unwrap().remove(&id) - } + /// Removes transaction from this queue and notifies `ConfirmationPromise` holders about the result. + /// Notifies also a receiver about that event. + fn remove(&self, id: U256, result: Option) -> Option { + let token = self.queue.write().unwrap().remove(&id); - fn update_status(&self, id: U256, status: QueueStatus) { - let mut waiters = self.waiters.write().unwrap(); - waiters.insert(id, status); + if let Some(token) = token { + // notify receiver about the event + self.notify(result.map_or_else( + || QueueEvent::RequestRejected(id), + |_| QueueEvent::RequestConfirmed(id) + )); + // notify token holders about resolution + token.resolve(result); + // return a result + return Some(token.request.clone()); + } + None } } @@ -152,7 +225,7 @@ impl Drop for ConfirmationsQueue { } impl SigningQueue for ConfirmationsQueue { - fn add_request(&self, transaction: TransactionRequest) -> U256 { + fn add_request(&self, transaction: TransactionRequest) -> ConfirmationPromise { // Increment id let id = { let mut last_id = self.id.lock().unwrap(); @@ -160,87 +233,42 @@ impl SigningQueue for ConfirmationsQueue { *last_id }; // Add request to queue - { + let res = { let mut queue = self.queue.write().unwrap(); - queue.insert(id, TransactionConfirmation { - id: id, - transaction: transaction, + queue.insert(id, ConfirmationToken { + result: Arc::new(Mutex::new(ConfirmationResult::Waiting)), + handle: thread::current(), + request: TransactionConfirmation { + id: id, + transaction: transaction, + }, }); debug!(target: "own_tx", "Signer: New transaction ({:?}) in confirmation queue.", id); - } + queue.get(&id).map(|token| token.as_promise()).expect("Token was just inserted.") + }; // Notify listeners - self.notify(QueueMessage::NewRequest(id)); - id + self.notify(QueueEvent::NewRequest(id)); + res + } fn peek(&self, id: &U256) -> Option { - self.queue.read().unwrap().get(id).cloned() + self.queue.read().unwrap().get(id).map(|token| token.request.clone()) } fn request_rejected(&self, id: U256) -> Option { debug!(target: "own_tx", "Signer: Transaction rejected ({:?}).", id); - let o = self.remove(id); - self.update_status(id, QueueStatus::Rejected); - o + self.remove(id, None) } fn request_confirmed(&self, id: U256, hash: H256) -> Option { debug!(target: "own_tx", "Signer: Transaction confirmed ({:?}).", id); - let o = self.remove(id); - self.update_status(id, QueueStatus::Confirmed(hash)); - o + self.remove(id, Some(hash)) } fn requests(&self) -> Vec { let queue = self.queue.read().unwrap(); - queue.values().cloned().collect() - } - - fn wait_with_timeout(&self, id: U256) -> Option { - { - let mut waiters = self.waiters.write().unwrap(); - let r = waiters.insert(id, QueueStatus::Waiting); - match r { - // This is ok, we can have many waiters - Some(QueueStatus::Waiting) | None => {}, - // There already was a response for someone. - // The one waiting for it will cleanup, so... - Some(v) => { - // ... insert old status back - waiters.insert(id, v.clone()); - if let QueueStatus::Confirmed(h) = v { - return Some(h); - } - return None; - }, - } - } - - info!(target: "own_tx", "Signer: Awaiting transaction confirmation... ({:?}).", id); - // Now wait for a response - let deadline = Instant::now() + Duration::from_secs(QUEUE_TIMEOUT_DURATION_SEC); - while Instant::now() < deadline { - let status = { - let waiters = self.waiters.read().unwrap(); - waiters.get(&id).expect("Only the waiting thread can remove any message.").clone() - }; - - match status { - QueueStatus::Waiting => thread::sleep(Duration::from_millis(50)), - QueueStatus::Confirmed(h) => { - self.waiters.write().unwrap().remove(&id); - return Some(h); - }, - QueueStatus::Rejected => { - self.waiters.write().unwrap().remove(&id); - return None; - }, - } - } - // We reached the timeout. Just return `None` and make sure to remove waiting. - trace!(target: "own_tx", "Signer: Confirmation timeout reached... ({:?}).", id); - self.waiters.write().unwrap().remove(&id); - None + queue.values().map(|token| token.request.clone()).collect() } } @@ -277,7 +305,7 @@ mod test { let q = queue.clone(); let handle = thread::spawn(move || { let v = q.add_request(request); - q.wait_with_timeout(v).expect("Should return hash") + v.wait_with_timeout().expect("Should return hash") }); let id = U256::from(1); @@ -307,13 +335,13 @@ mod test { *v = Some(notification); }).expect("Should be closed nicely.") }); - let v = queue.add_request(request); + queue.add_request(request); queue.finish(); // then handle.join().expect("Thread should finish nicely"); let r = received.lock().unwrap().take(); - assert_eq!(r, Some(QueueMessage::NewRequest(v))); + assert_eq!(r, Some(QueueEvent::NewRequest(U256::from(1)))); } #[test] diff --git a/rpc/src/v1/impls/eth_signing.rs b/rpc/src/v1/impls/eth_signing.rs index d7e997c71..a5b3f2592 100644 --- a/rpc/src/v1/impls/eth_signing.rs +++ b/rpc/src/v1/impls/eth_signing.rs @@ -54,7 +54,7 @@ impl EthSigning for EthSigningQueueClient { .and_then(|(request, )| { let queue = take_weak!(self.queue); let id = queue.add_request(request); - let result = queue.wait_with_timeout(id); + let result = id.wait_with_timeout(); to_value(&result.unwrap_or_else(H256::new)) }) } From ee3f6082045a48e4cbd6f795101a2359b6c6c62d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 2 Jun 2016 13:19:44 +0200 Subject: [PATCH 18/19] Fixing wait loop in case of spurious wake-ups. --- rpc/src/v1/helpers/signing_queue.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/rpc/src/v1/helpers/signing_queue.rs b/rpc/src/v1/helpers/signing_queue.rs index 4f860e438..0ded8998c 100644 --- a/rpc/src/v1/helpers/signing_queue.rs +++ b/rpc/src/v1/helpers/signing_queue.rs @@ -125,9 +125,13 @@ impl ConfirmationPromise { let deadline = Instant::now() + timeout; info!(target: "own_tx", "Signer: Awaiting transaction confirmation... ({:?}).", self.id); - while Instant::now() < deadline { - // Park thread - thread::park_timeout(timeout); + loop { + let now = Instant::now(); + if now >= deadline { + break; + } + // Park thread (may wake up spuriously) + thread::park_timeout(deadline - now); // Take confirmation result let res = self.result.lock().unwrap(); // Check the result @@ -137,7 +141,7 @@ impl ConfirmationPromise { ConfirmationResult::Waiting => continue, } } - // We reached the timeout. Just return `None` and make sure to remove waiting. + // We reached the timeout. Just return `None` trace!(target: "own_tx", "Signer: Confirmation timeout reached... ({:?}).", self.id); None } From cce18cb4c546b5af0b9bf923c25f50d6d43c4c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 2 Jun 2016 13:32:33 +0200 Subject: [PATCH 19/19] Enabling DAOdapp --- dapps/src/apps.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dapps/src/apps.rs b/dapps/src/apps.rs index 130b20fb9..4bf550ce1 100644 --- a/dapps/src/apps.rs +++ b/dapps/src/apps.rs @@ -63,12 +63,12 @@ fn wallet_page(pages: &mut Endpoints) { #[cfg(not(feature = "parity-dapps-wallet"))] fn wallet_page(_pages: &mut Endpoints) {} -#[cfg(feature = "parity-dapps-daodapp")] +#[cfg(feature = "parity-dapps-dao")] fn daodapp_page(pages: &mut Endpoints) { - extern crate parity_dapps_daodapp; - insert::(pages, "dao"); + extern crate parity_dapps_dao; + insert::(pages, "dao"); } -#[cfg(not(feature = "parity-dapps-daodapp"))] +#[cfg(not(feature = "parity-dapps-dao"))] fn daodapp_page(_pages: &mut Endpoints) {} #[cfg(feature = "parity-dapps-makerotc")]