From b77fdcdd6876a2f05f1a0b63f6f9f50d7d5088ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 27 May 2016 13:03:00 +0200 Subject: [PATCH] WebSockets server for signer --- Cargo.lock | 27 ++++++++ dapps/src/lib.rs | 6 +- parity/cli.rs | 7 ++ parity/main.rs | 24 ++++++- parity/setup_log.rs | 2 + parity/signer.rs | 67 +++++++++++++++++++ signer/Cargo.toml | 4 +- signer/src/lib.rs | 12 ++++ signer/src/types/bytes.rs | 4 +- signer/src/types/mod.rs | 2 + signer/src/types/mod.rs.in | 2 - signer/src/ws_server.rs | 128 +++++++++++++++++++++++++++++++++++++ 12 files changed, 275 insertions(+), 10 deletions(-) create mode 100644 parity/signer.rs create mode 100644 signer/src/ws_server.rs diff --git a/Cargo.lock b/Cargo.lock index 8379e101e..66df9192a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,6 +105,11 @@ name = "bloomchain" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "byteorder" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bytes" version = "0.3.0" @@ -356,6 +361,7 @@ dependencies = [ "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)", ] [[package]] @@ -1167,6 +1173,14 @@ dependencies = [ "serde 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "sha1" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "sha3" version = "0.1.0" @@ -1411,6 +1425,19 @@ name = "winapi-build" version = "0.1.1" 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" +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)", + "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)", +] + [[package]] name = "ws2_32-sys" version = "0.2.1" diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index c6e75072d..e0676881f 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -34,9 +34,9 @@ //! let io = IoHandler::new(); //! io.add_method("say_hello", SayHello); //! let _server = Server::start_unsecure_http( -//! &"127.0.0.1:3030".parse().unwrap(), -//! Arc::new(io) -//! ); +//! &"127.0.0.1:3030".parse().unwrap(), +//! Arc::new(io) +//! ); //! } //! ``` //! diff --git a/parity/cli.rs b/parity/cli.rs index 4bb74f188..60b622bf7 100644 --- a/parity/cli.rs +++ b/parity/cli.rs @@ -97,6 +97,11 @@ API and Console Options: --dapps-pass PASSWORD Specify password for Dapps server. Use only in conjunction with --dapps-user. + --signer Enable Trusted Signer WebSocket endpoint used by + System UIs. + --signer-port PORT Specify the port of Trusted Signer server + [default: 8180]. + Sealing/Mining Options: --force-sealing Force the node to author new blocks as if it were always sealing/mining. @@ -234,6 +239,8 @@ pub struct Args { pub flag_dapps_interface: String, pub flag_dapps_user: Option, pub flag_dapps_pass: Option, + pub flag_signer: bool, + pub flag_signer_port: u16, pub flag_force_sealing: bool, pub flag_author: String, pub flag_usd_per_tx: String, diff --git a/parity/main.rs b/parity/main.rs index e87828f64..1e9ab33f5 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -50,6 +50,11 @@ extern crate ethcore_rpc; #[cfg(feature = "dapps")] extern crate ethcore_dapps; +#[cfg(feature = "ethcore-signer")] +extern crate ethcore_signer; + + + #[macro_use] mod die; mod price_info; @@ -63,6 +68,7 @@ mod io_handler; mod cli; mod configuration; mod migration; +mod signer; use std::io::{Write, Read, BufReader, BufRead}; use std::ops::Deref; @@ -89,6 +95,7 @@ use informant::Informant; use die::*; use cli::print_version; use rpc::RpcServer; +use signer::SignerServer; use dapps::WebappServer; use io_handler::ClientIoHandler; use configuration::Configuration; @@ -231,6 +238,14 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig) settings: network_settings.clone(), }); + // Set up a signer + let signer_server = signer::start(signer::Configuration { + enabled: conf.args.flag_signer, + port: conf.args.flag_signer_port, + }, signer::Dependencies { + panic_handler: panic_handler.clone(), + }); + // Register IO handler let io_handler = Arc::new(ClientIoHandler { client: service.client(), @@ -241,7 +256,7 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig) service.io().register_handler(io_handler).expect("Error registering IO handler"); // Handle exit - wait_for_exit(panic_handler, rpc_server, dapps_server); + wait_for_exit(panic_handler, rpc_server, dapps_server, signer_server); } fn flush_stdout() { @@ -453,7 +468,12 @@ fn execute_account_cli(conf: Configuration) { } } -fn wait_for_exit(panic_handler: Arc, _rpc_server: Option, _dapps_server: Option) { +fn wait_for_exit( + panic_handler: Arc, + _rpc_server: Option, + _dapps_server: Option, + _signer_server: Option + ) { let exit = Arc::new(Condvar::new()); // Handle possible exits diff --git a/parity/setup_log.rs b/parity/setup_log.rs index 0fbc76fb3..4ed153fc2 100644 --- a/parity/setup_log.rs +++ b/parity/setup_log.rs @@ -27,6 +27,8 @@ pub fn setup_log(init: &Option) -> Arc { let mut levels = String::new(); let mut builder = LogBuilder::new(); + // Disable ws info logging by default. + builder.filter(Some("ws"), LogLevelFilter::Warn); builder.filter(None, LogLevelFilter::Info); if env::var("RUST_LOG").is_ok() { diff --git a/parity/signer.rs b/parity/signer.rs new file mode 100644 index 000000000..e7f7a9cb9 --- /dev/null +++ b/parity/signer.rs @@ -0,0 +1,67 @@ +// 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 util::panics::{PanicHandler, ForwardPanic}; +use die::*; + +#[cfg(feature = "ethcore-signer")] +use ethcore_signer as signer; +#[cfg(feature = "ethcore-signer")] +pub use ethcore_signer::Server as SignerServer; +#[cfg(not(feature = "ethcore-signer"))] +pub struct SignerServer; + +pub struct Configuration { + pub enabled: bool, + pub port: u16, +} + +pub struct Dependencies { + pub panic_handler: Arc, +} + +#[cfg(feature = "ethcore-signer")] +pub fn start(conf: Configuration, deps: Dependencies) -> Option { + if !conf.enabled { + return None; + } + + let addr = format!("127.0.0.1:{}", conf.port).parse().unwrap_or_else(|_| die!("Invalid port specified: {}", conf.port)); + let start_result = signer::Server::start(addr); + + match start_result { + Err(signer::ServerError::IoError(err)) => die_with_io_error("Trusted Signer", err), + Err(e) => die!("Trusted Signer: {:?}", e), + Ok(server) => { + deps.panic_handler.forward_from(&server); + Some(server) + }, + } +} + +#[cfg(not(feature = "ethcore-signer"))] +pub fn start(conf: Configuration) -> !{ + if !conf.enabled { + return None; + } + + die!("Your Parity version has been compiled without Trusted Signer support.") +} + + + + diff --git a/signer/Cargo.toml b/signer/Cargo.toml index 0d7f562ab..be77a3fd9 100644 --- a/signer/Cargo.toml +++ b/signer/Cargo.toml @@ -15,11 +15,13 @@ syntex = "^0.32.0" [dependencies] serde = "0.7.0" serde_json = "0.7.0" -serde_macros = { version = "0.7.0", optional = true } rustc-serialize = "0.3" ethcore-util = { path = "../util" } log = "0.3" env_logger = "0.3" +ws = "0.4.7" + +serde_macros = { version = "0.7.0", optional = true } clippy = { version = "0.0.69", optional = true} [features] diff --git a/signer/src/lib.rs b/signer/src/lib.rs index d8e7f63ec..13f8d2fa6 100644 --- a/signer/src/lib.rs +++ b/signer/src/lib.rs @@ -30,7 +30,15 @@ //! and their responsibility is to confirm (or confirm and sign) //! the transaction for you. //! +//! ``` +//! extern crate ethcore_signer; //! +//! use ethcore_signer::Server; +//! +//! fn main() { +//! let _server = Server::start("127.0.0.1:8084".parse().unwrap()); +//! } +//! ``` #[macro_use] extern crate log; @@ -41,10 +49,14 @@ extern crate serde_json; extern crate rustc_serialize; extern crate ethcore_util as util; +extern crate ws; mod signing_queue; +mod ws_server; pub mod types; +pub use ws_server::*; + #[cfg(test)] mod tests { #[test] diff --git a/signer/src/types/bytes.rs b/signer/src/types/bytes.rs index d8896f849..503927676 100644 --- a/signer/src/types/bytes.rs +++ b/signer/src/types/bytes.rs @@ -14,13 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! Serializable wrapper around vector of bytes + use rustc_serialize::hex::ToHex; use serde::{Serialize, Serializer, Deserialize, Deserializer, Error}; use serde::de::Visitor; use util::common::FromHex; -///! Serializable wrapper around vector of bytes - /// Wrapper structure around vector of bytes. #[derive(Debug, PartialEq, Eq, Default, Hash, Clone)] pub struct Bytes(pub Vec); diff --git a/signer/src/types/mod.rs b/signer/src/types/mod.rs index adf9be071..d5e15046a 100644 --- a/signer/src/types/mod.rs +++ b/signer/src/types/mod.rs @@ -14,6 +14,8 @@ // 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"); diff --git a/signer/src/types/mod.rs.in b/signer/src/types/mod.rs.in index 8e9befa4f..986e3d6e2 100644 --- a/signer/src/types/mod.rs.in +++ b/signer/src/types/mod.rs.in @@ -14,7 +14,5 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -///! Reusable types with JSON Serialization. - pub mod transaction_request; pub mod bytes; diff --git a/signer/src/ws_server.rs b/signer/src/ws_server.rs new file mode 100644 index 000000000..9be392e69 --- /dev/null +++ b/signer/src/ws_server.rs @@ -0,0 +1,128 @@ +// 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 . + +//! `WebSockets` server. + +use ws; +use std; +use std::thread; +use std::ops::Drop; +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::net::SocketAddr; +use util::panics::{PanicHandler, OnPanicListener, MayPanic}; + +/// Signer startup error +#[derive(Debug)] +pub enum ServerError { + /// Wrapped `std::io::Error` + IoError(std::io::Error), + /// Other `ws-rs` error + Other(ws::Error) +} + +impl From for ServerError { + fn from(err: ws::Error) -> Self { + match err.kind { + ws::ErrorKind::Io(e) => ServerError::IoError(e), + _ => ServerError::Other(err), + } + } +} + +/// `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) -> Result { + let config = { + let mut config = ws::Settings::default(); + config.max_connections = 5; + config.method_strict = true; + config + }; + + // Create WebSocket + let session_id = Arc::new(AtomicUsize::new(1)); + let ws = try!(ws::Builder::new().with_settings(config).build(Factory { + session_id: session_id, + })); + + 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 || { + ws.listen(addr).unwrap() + }).unwrap() + }); + + // Return a handle + Ok(Server { + handle: Some(handle), + broadcaster: broadcaster, + panic_handler: panic_handler, + }) + } +} + +impl MayPanic for Server { + fn on_panic(&self, closure: F) where F: OnPanicListener { + self.panic_handler.on_panic(closure); + } +} + +impl Drop for Server { + fn drop(&mut self) { + self.broadcaster.shutdown().expect("WsServer should close nicely."); + self.handle.take().unwrap().join().unwrap(); + } +} + +struct Session { + id: usize, + out: ws::Sender, +} + +impl ws::Handler for Session { + fn on_open(&mut self, _shake: ws::Handshake) -> ws::Result<()> { + try!(self.out.send(format!("Hello client no: {}. We are not implemented yet.", self.id))); + try!(self.out.close(ws::CloseCode::Normal)); + Ok(()) + } +} + +struct Factory { + session_id: Arc, +} + +impl ws::Factory for Factory { + type Handler = Session; + + fn connection_made(&mut self, sender: ws::Sender) -> Self::Handler { + Session { + id: self.session_id.fetch_add(1, Ordering::SeqCst), + out: sender, + } + } +}