diff --git a/Cargo.lock b/Cargo.lock index 291e90562..ce5c1f481 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -357,12 +357,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/dapps/src/apps.rs b/dapps/src/apps.rs index 559282584..130b20fb9 100644 --- a/dapps/src/apps.rs +++ b/dapps/src/apps.rs @@ -38,11 +38,16 @@ pub fn utils() -> Box { pub fn all_endpoints() -> Endpoints { let mut pages = Endpoints::new(); - pages.insert("proxy".to_owned(), ProxyPac::boxed()); + pages.insert("proxy".into(), ProxyPac::boxed()); + // Home page needs to be safe embed + // because we use Cross-Origin LocalStorage. + // TODO [ToDr] Account naming should be moved to parity. + pages.insert("home".into(), Box::new( + PageEndpoint::new_safe_to_embed(parity_dapps_builtins::App::default()) + )); insert::(&mut pages, "status"); insert::(&mut pages, "parity"); - insert::(&mut pages, "home"); wallet_page(&mut pages); daodapp_page(&mut pages); diff --git a/dapps/src/endpoint.rs b/dapps/src/endpoint.rs index 60708f549..28ca6ea11 100644 --- a/dapps/src/endpoint.rs +++ b/dapps/src/endpoint.rs @@ -21,7 +21,7 @@ use hyper::{header, server, Decoder, Encoder, Next}; use hyper::net::HttpStream; use std::io::Write; -use std::collections::HashMap; +use std::collections::BTreeMap; #[derive(Debug, PartialEq, Default, Clone)] pub struct EndpointPath { @@ -45,7 +45,7 @@ pub trait Endpoint : Send + Sync { fn to_handler(&self, path: EndpointPath) -> Box>; } -pub type Endpoints = HashMap>; +pub type Endpoints = BTreeMap>; pub type Handler = server::Handler; pub struct ContentHandler { diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 27c215108..231e7b080 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/dapps/src/page/mod.rs b/dapps/src/page/mod.rs index 71989bca7..819988310 100644 --- a/dapps/src/page/mod.rs +++ b/dapps/src/page/mod.rs @@ -30,6 +30,8 @@ pub struct PageEndpoint { pub app: Arc, /// Prefix to strip from the path (when `None` deducted from `app_id`) pub prefix: Option, + /// Safe to be loaded in frame by other origin. (use wisely!) + safe_to_embed: bool, } impl PageEndpoint { @@ -37,6 +39,7 @@ impl PageEndpoint { PageEndpoint { app: Arc::new(app), prefix: None, + safe_to_embed: false, } } @@ -44,6 +47,18 @@ impl PageEndpoint { PageEndpoint { app: Arc::new(app), prefix: Some(prefix), + safe_to_embed: false, + } + } + + /// Creates new `PageEndpoint` which can be safely used in iframe + /// even from different origin. It might be dangerous (clickjacking). + /// Use wisely! + pub fn new_safe_to_embed(app: T) -> Self { + PageEndpoint { + app: Arc::new(app), + prefix: None, + safe_to_embed: true, } } } @@ -61,6 +76,7 @@ impl Endpoint for PageEndpoint { path: path, file: None, write_pos: 0, + safe_to_embed: self.safe_to_embed, }) } } @@ -83,6 +99,7 @@ struct PageHandler { path: EndpointPath, file: Option, write_pos: usize, + safe_to_embed: bool, } impl PageHandler { @@ -128,6 +145,9 @@ impl server::Handler for PageHandler { if let Some(f) = self.file.as_ref().and_then(|f| self.app.file(f)) { res.set_status(StatusCode::Ok); res.headers_mut().set(header::ContentType(f.content_type.parse().unwrap())); + if !self.safe_to_embed { + res.headers_mut().set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]); + } Next::write() } else { res.set_status(StatusCode::NotFound); @@ -192,6 +212,7 @@ fn should_extract_path_with_appid() { }, file: None, write_pos: 0, + safe_to_embed: true, }; // when diff --git a/parity/cli.rs b/parity/cli.rs index 60b622bf7..95b77a00d 100644 --- a/parity/cli.rs +++ b/parity/cli.rs @@ -76,13 +76,13 @@ API and Console Options: interface. APIS is a comma-delimited list of API name. Possible name are web3, eth, net, personal, ethcore, traces. - [default: web3,eth,net,personal,ethcore,traces]. + [default: web3,eth,net,personal,traces]. --ipc-off Disable JSON-RPC over IPC service. --ipc-path PATH Specify custom path for JSON-RPC over IPC service [default: $HOME/.parity/jsonrpc.ipc]. --ipc-apis APIS Specify custom API set available via JSON-RPC over - IPC [default: web3,eth,net,personal,ethcore]. + IPC [default: web3,eth,net,personal,traces]. --dapps-off Disable the Dapps server (e.g. status page). --dapps-port PORT Specify the port portion of the Dapps server diff --git a/parity/dapps.rs b/parity/dapps.rs index 7909bb9bc..6a0a8cea7 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -22,9 +22,8 @@ use ethsync::EthSync; use ethcore::miner::{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 +40,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,17 +85,10 @@ 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(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()); - + 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/main.rs b/parity/main.rs index 0575d80ea..ca5e39a1a 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -66,6 +66,7 @@ mod cli; mod configuration; mod migration; mod signer; +mod rpc_apis; use std::io::{Write, Read, BufReader, BufRead}; use std::ops::Deref; @@ -194,8 +195,9 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig) // Sync let sync = EthSync::register(service.network(), sync_config, client.clone()); - let dependencies = Arc::new(rpc::Dependencies { - panic_handler: panic_handler.clone(), + let deps_for_rpc_apis = Arc::new(rpc_apis::Dependencies { + signer_enabled: conf.args.flag_signer, + signer_queue: Arc::new(rpc_apis::ConfirmationsQueue::default()), client: client.clone(), sync: sync.clone(), secret_store: account_service.clone(), @@ -205,6 +207,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, @@ -226,26 +233,16 @@ 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 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(), - 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 0d8d7562e..65e6b72e1 100644 --- a/parity/rpc.rs +++ b/parity/rpc.rs @@ -15,7 +15,6 @@ // along with Parity. If not, see . -use std::collections::BTreeMap; use std::str::FromStr; use std::sync::Arc; use std::net::SocketAddr; @@ -24,10 +23,9 @@ use ethsync::EthSync; use ethcore::miner::{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 +50,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,58 +70,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()); - }, - "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(), rpc_apis::ApiSet::List(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.") @@ -137,27 +94,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..c73a70fee --- /dev/null +++ b/parity/rpc_apis.rs @@ -0,0 +1,168 @@ +// 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; + +#[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, + Net, + Eth, + Personal, + Ethcore, + Traces, + Rpc, +} + +pub enum ApiError { + UnknownApi(String) +} + +pub enum ApiSet { + SafeContext, + UnsafeContext, + List(Vec), +} + +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 signer_enabled: bool, + pub signer_queue: Arc, + 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), + }) +} + +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 = list_apis(apis, deps.signer_enabled); + 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()); + + 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()); + 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()) + }, + 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 3f6197e3a..c250771eb 100644 --- a/parity/signer.rs +++ b/parity/signer.rs @@ -21,6 +21,7 @@ use ethcore::miner::{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 +37,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,13 +55,8 @@ 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(EthFilterClient::new(&deps.client, &deps.miner).to_delegate()); - server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner).to_delegate()); + let server = rpc_apis::setup_rpc(server, deps.apis, rpc_apis::ApiSet::SafeContext); server.start(addr) }; diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 80c92388a..ab5069334 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -43,12 +43,26 @@ use self::jsonrpc_core::{IoHandler, IoDelegate}; pub use jsonrpc_http_server::{Server, RpcServerError}; pub mod v1; +pub use v1::{SigningQueue, ConfirmationsQueue}; + +/// 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 { @@ -57,11 +71,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/helpers/mod.rs b/rpc/src/v1/helpers/mod.rs index b1a5c05ba..2acf98bf2 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::{ConfirmationsQueue, SigningQueue}; diff --git a/rpc/src/v1/helpers/signing_queue.rs b/rpc/src/v1/helpers/signing_queue.rs new file mode 100644 index 000000000..eee4328ee --- /dev/null +++ b/rpc/src/v1/helpers/signing_queue.rs @@ -0,0 +1,108 @@ +// 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::sync::Mutex; +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) -> U256; + + /// Remove request from the queue. + fn remove_request(&self, id: U256) -> Option; + + /// Return copy of all the requests in the queue. + fn requests(&self) -> Vec; +} + +/// Queue for all unconfirmed transactions. +pub struct ConfirmationsQueue { + id: Mutex, + queue: Mutex>, +} + +impl Default for ConfirmationsQueue { + fn default() -> Self { + ConfirmationsQueue { + id: Mutex::new(U256::from(0)), + queue: Mutex::new(HashMap::new()), + } + } +} + +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: U256) -> Option { + self.queue.lock().unwrap().remove(&id) + } + + fn requests(&self) -> Vec { + let queue = self.queue.lock().unwrap(); + queue.values().cloned().collect() + } +} + + +#[cfg(test)] +mod test { + use util::hash::Address; + use util::numbers::U256; + use v1::types::TransactionRequest; + use super::*; + + #[test] + fn should_work_for_hashset() { + // given + let queue = ConfirmationsQueue::default(); + + let request = TransactionRequest { + from: Address::from(1), + to: Some(Address::from(2)), + gas_price: None, + gas: None, + value: Some(U256::from(10_000_000)), + data: None, + nonce: None, + }; + + // when + queue.add_request(request.clone()); + let all = queue.requests(); + + // then + assert_eq!(all.len(), 1); + 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/eth.rs b/rpc/src/v1/impls/eth.rs index ad15fb2f7..1af4491f2 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}; @@ -36,10 +35,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, EthFilter}; -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 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 serde; /// Eth rpc implementation. @@ -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; @@ -496,23 +494,6 @@ impl Eth for EthClient where }) } - fn sign(&self, params: Params) -> Result { - from_params::<(Address, H256)>(params).and_then(|(addr, msg)| { - to_value(&take_weak!(self.accounts).sign(&addr, &msg).unwrap_or(H520::zero())) - }) - } - - 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, )| { diff --git a/rpc/src/v1/impls/eth_filter.rs b/rpc/src/v1/impls/eth_filter.rs new file mode 100644 index 000000000..c6aecdca2 --- /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/eth_signing.rs b/rpc/src/v1/impls/eth_signing.rs new file mode 100644 index 000000000..04011902b --- /dev/null +++ b/rpc/src/v1/impls/eth_signing.rs @@ -0,0 +1,111 @@ +// 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::helpers::{SigningQueue, ConfirmationsQueue}; +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 sign(&self, _params: Params) -> Result { + // TODO [ToDr] Implement sign when rest of the signing queue is ready. + rpc_unimplemented!() + } + + 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, + 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 sign(&self, params: Params) -> Result { + from_params::<(Address, H256)>(params).and_then(|(addr, msg)| { + to_value(&take_weak!(self.accounts).sign(&addr, &msg).unwrap_or(H520::zero())) + }) + } + + 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 2f1c07ff6..33c434122 100644 --- a/rpc/src/v1/impls/mod.rs +++ b/rpc/src/v1/impls/mod.rs @@ -31,16 +31,22 @@ macro_rules! rpc_unimplemented { mod web3; mod eth; +mod eth_filter; +mod eth_signing; mod net; mod personal; +mod personal_signer; mod ethcore; mod traces; mod rpc; pub use self::web3::Web3Client; -pub use self::eth::{EthClient, EthFilterClient}; +pub use self::eth::EthClient; +pub use self::eth_filter::EthFilterClient; +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 074bb8c77..e5c3cd1a5 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::MiningBlockChainClient; use ethcore::miner::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..cf4e927ac --- /dev/null +++ b/rpc/src/v1/impls/personal_signer.rs @@ -0,0 +1,93 @@ +// 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::PersonalSigner; +use v1::types::TransactionModification; +use v1::impls::sign_and_dispatch; +use v1::helpers::{SigningQueue, ConfirmationsQueue}; +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 PersonalSigner 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 { + 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 { + 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 b1ab256c0..54628d892 100644 --- a/rpc/src/v1/mod.rs +++ b/rpc/src/v1/mod.rs @@ -25,5 +25,6 @@ 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, PersonalSigner, Net, Ethcore, Traces, Rpc}; pub use self::impls::*; +pub use self::helpers::{SigningQueue, ConfirmationsQueue}; diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 4dc22df56..93887fb63 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -36,8 +36,8 @@ use util::keys::{AccountProvider, TestAccount, TestAccountProvider}; use jsonrpc_core::IoHandler; use ethjson::blockchain::BlockChain; -use v1::traits::eth::Eth; -use v1::impls::EthClient; +use v1::traits::eth::{Eth, EthSigning}; +use v1::impls::{EthClient, EthSigningUnsafeClient}; use v1::tests::helpers::{TestSyncProvider, Config}; fn account_provider() -> Arc { @@ -110,10 +110,15 @@ impl EthTester { &miner_service, &external_miner ); + let eth_sign = EthSigningUnsafeClient::new( + &client, + &account_provider, + &miner_service + ); let handler = IoHandler::new(); - let delegate = eth_client.to_delegate(); - handler.add_delegate(delegate); + handler.add_delegate(eth_client.to_delegate()); + handler.add_delegate(eth_sign.to_delegate()); EthTester { _miner: miner_service, diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index 579f57e6c..63e66f797 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -27,7 +27,7 @@ use ethcore::receipt::LocalizedReceipt; use ethcore::transaction::{Transaction, Action}; use ethcore::miner::{ExternalMiner, MinerService}; use ethsync::SyncState; -use v1::{Eth, EthClient}; +use v1::{Eth, EthClient, EthSigning, EthSigningUnsafeClient}; use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService}; use rustc_serialize::hex::ToHex; @@ -72,8 +72,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/tests/mocked/eth_signing.rs b/rpc/src/v1/tests/mocked/eth_signing.rs new file mode 100644 index 000000000..6eb6e3fd6 --- /dev/null +++ b/rpc/src/v1/tests/mocked/eth_signing.rs @@ -0,0 +1,75 @@ +// 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::sync::Arc; +use jsonrpc_core::IoHandler; +use v1::impls::EthSigningQueueClient; +use v1::traits::EthSigning; +use v1::helpers::{ConfirmationsQueue, SigningQueue}; +use util::keys::TestAccount; + +struct EthSigningTester { + pub queue: Arc, + pub io: IoHandler, +} + +impl Default for EthSigningTester { + fn default() -> Self { + let queue = Arc::new(ConfirmationsQueue::default()); + let io = IoHandler::new(); + io.add_delegate(EthSigningQueueClient::new(&queue).to_delegate()); + + EthSigningTester { + queue: queue, + io: io, + } + } +} + +fn eth_signing() -> EthSigningTester { + EthSigningTester::default() +} + + +#[test] +fn should_add_transaction_to_queue() { + // given + let tester = eth_signing(); + 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 dc09d998d..986dbfdef 100644 --- a/rpc/src/v1/tests/mocked/mod.rs +++ b/rpc/src/v1/tests/mocked/mod.rs @@ -18,8 +18,10 @@ //! method calls properly. mod eth; +mod eth_signing; mod net; mod web3; mod personal; +mod personal_signer; mod ethcore; mod rpc; 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 new file mode 100644 index 000000000..cd1f81d9a --- /dev/null +++ b/rpc/src/v1/tests/mocked/personal_signer.rs @@ -0,0 +1,169 @@ +// 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::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::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/eth.rs b/rpc/src/v1/traits/eth.rs index 1577053f4..c4369ff2a 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; - /// Signs the data with given address signature. - fn sign(&self, _: Params) -> Result; - - /// Sends transaction. - fn send_transaction(&self, _: Params) -> Result; - /// Sends signed transaction. fn send_raw_transaction(&self, _: Params) -> Result; @@ -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; + + /// Sends transaction. + fn send_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("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 0728fd06a..d994ffc24 100644 --- a/rpc/src/v1/traits/mod.rs +++ b/rpc/src/v1/traits/mod.rs @@ -25,9 +25,11 @@ 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::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 d66161c54..a36358766 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 PersonalSigner: 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", 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 1b51e6b12..93d6a479b 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, @@ -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, + }); + } } 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 f3a963ac7..e2df72bcc 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 //! @@ -45,18 +43,12 @@ 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; extern crate ws; -mod signing_queue; mod ws_server; - pub use ws_server::*; #[cfg(test)] diff --git a/signer/src/signing_queue.rs b/signer/src/signing_queue.rs deleted file mode 100644 index 611d467c2..000000000 --- a/signer/src/signing_queue.rs +++ /dev/null @@ -1,74 +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 . - -use std::collections::HashSet; -use rpc::v1::types::TransactionRequest; - -pub trait SigningQueue { - fn add_request(&mut self, transaction: TransactionRequest); - - fn remove_request(&mut self, id: TransactionRequest); - - fn requests(&self) -> &HashSet; -} - -impl SigningQueue for HashSet { - fn add_request(&mut self, transaction: TransactionRequest) { - self.insert(transaction); - } - - fn remove_request(&mut self, id: TransactionRequest) { - self.remove(&id); - } - - fn requests(&self) -> &HashSet { - self - } -} - - -#[cfg(test)] -mod test { - use std::collections::HashSet; - use util::hash::Address; - use util::numbers::U256; - use rpc::v1::types::TransactionRequest; - use super::*; - - #[test] - fn should_work_for_hashset() { - // given - let mut queue = HashSet::new(); - - let request = TransactionRequest { - from: Address::from(1), - to: Some(Address::from(2)), - gas_price: None, - gas: None, - value: Some(U256::from(10_000_000)), - data: None, - nonce: None, - }; - - // when - queue.add_request(request.clone()); - let all = queue.requests(); - - // then - assert_eq!(all.len(), 1); - assert!(all.contains(&request)); - } -} 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. 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 {