From 3c634701ddc6d6694f2760548c20ae4df869f4e9 Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Tue, 14 Feb 2017 19:30:37 +0100 Subject: [PATCH 01/51] Squashed --- Cargo.lock | 72 +++++++++++++++++++++++++ Cargo.toml | 1 + ipfs/Cargo.toml | 14 +++++ ipfs/src/error.rs | 86 +++++++++++++++++++++++++++++ ipfs/src/handler.rs | 129 ++++++++++++++++++++++++++++++++++++++++++++ ipfs/src/lib.rs | 123 ++++++++++++++++++++++++++++++++++++++++++ parity/main.rs | 1 + parity/run.rs | 7 ++- 8 files changed, 432 insertions(+), 1 deletion(-) create mode 100644 ipfs/Cargo.toml create mode 100644 ipfs/src/error.rs create mode 100644 ipfs/src/handler.rs create mode 100644 ipfs/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index a01351fbd..7fc7cbc41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,6 +33,7 @@ dependencies = [ "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "parity-hash-fetch 1.6.0", + "parity-ipfs 1.6.0", "parity-reactor 0.1.0", "parity-rpc-client 1.4.0", "parity-updater 1.6.0", @@ -104,6 +105,11 @@ dependencies = [ "syntex_syntax 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "base-x" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "base32" version = "0.3.1" @@ -189,6 +195,16 @@ name = "cfg-if" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "cid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "multibase 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "multihash 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "varmint 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "clippy" version = "0.0.103" @@ -422,6 +438,7 @@ dependencies = [ "base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore 1.6.0", "ethcore-devtools 1.6.0", "ethcore-rpc 1.6.0", "ethcore-util 1.6.0", @@ -1306,6 +1323,23 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "multibase" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base-x 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "multihash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ring 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nanomsg" version = "0.5.1" @@ -1562,6 +1596,18 @@ dependencies = [ "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parity-ipfs" +version = "1.6.0" +dependencies = [ + "cid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore 1.6.0", + "ethcore-util 1.6.0", + "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", + "multihash 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rlp 0.1.0", +] + [[package]] name = "parity-reactor" version = "0.1.0" @@ -1817,6 +1863,15 @@ dependencies = [ "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ring" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rlp" version = "0.1.0" @@ -2326,6 +2381,11 @@ name = "unicode-xid" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "untrusted" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "url" version = "1.2.0" @@ -2353,6 +2413,11 @@ name = "utf8-ranges" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "varmint" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "vecio" version = "0.1.0" @@ -2450,6 +2515,7 @@ dependencies = [ "checksum app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7d1c0d48a81bbb13043847f957971f4d87c81542d80ece5e84ba3cba4058fd4" "checksum arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "16e3bdb2f54b3ace0285975d59a97cf8ed3855294b2b6bc651fcf22a9c352975" "checksum aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07d344974f0a155f091948aa389fb1b912d3a58414fbdb9c8d446d193ee3496a" +"checksum base-x 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2f59103b47307f76e03bef1633aec7fa9e29bfb5aa6daf5a334f94233c71f6c1" "checksum base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1b9605ba46d61df0410d8ac686b0007add8172eba90e8e909c347856fe794d8c" "checksum bigint 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2311bcd71b281e142a095311c22509f0d6bcd87b3000d7dbaa810929b9d6f6ae" "checksum bit-set 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e6e1e6fb1c9e3d6fcdec57216a74eaa03e41f52a22f13a16438251d8e88b89da" @@ -2464,6 +2530,7 @@ dependencies = [ "checksum bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27" "checksum bytes 0.4.0-dev (git+https://github.com/carllerche/bytes)" = "" "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" +"checksum cid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0ad0fdcfbfdfa789a0cf941dd19f7f1d3a377522f6e4c2a760d246ac56b4780" "checksum clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "5b4fabf979ddf6419a313c1c0ada4a5b95cfd2049c56e8418d622d27b4b6ff32" "checksum clippy_lints 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "ce96ec05bfe018a0d5d43da115e54850ea2217981ff0f2e462780ab9d594651a" "checksum cookie 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90266f45846f14a1e986c77d1e9c2626b8c342ed806fe60241ec38cc8697b245" @@ -2531,6 +2598,8 @@ dependencies = [ "checksum mio 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "410a1a0ff76f5a226f1e4e3ff1756128e65cd30166e39c3892283e2ac09d5b67" "checksum miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d5bfc6782530ac8ace97af10a540054a37126b63b0702ddaaa243b73b5745b9a" "checksum msdos_time 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c04b68cc63a8480fb2550343695f7be72effdec953a9d4508161c3e69041c7d8" +"checksum multibase 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b9c35dac080fd6e16a99924c8dfdef0af89d797dd851adab25feaffacf7850d6" +"checksum multihash 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "755d5a39bee3faaf649437e873beab334990221b2faf1f2e56ca10a9e4600235" "checksum nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)" = "" "checksum nanomsg-sys 0.5.0 (git+https://github.com/ethcore/nanomsg.rs.git)" = "" "checksum native-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4e52995154bb6f0b41e4379a279482c9387c1632e3798ba4e511ef8c54ee09" @@ -2581,6 +2650,7 @@ dependencies = [ "checksum regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)" = "b4329b8928a284580a1c63ec9d846b12f6d3472317243ff7077aff11f23f2b29" "checksum regex-syntax 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "841591b1e05609a643e3b4d0045fce04f701daba7151ddcd3ad47b080693d5a9" "checksum reqwest 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bef9ed8fdfcc30947d6b774938dc0c3f369a474efe440df2c7f278180b2d2e6" +"checksum ring 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "87ac4fce2ee4bb10dd106788e90fdfa4c5a7f3f9f6aae29824db77dc57e2767d" "checksum rocksdb 0.4.5 (git+https://github.com/ethcore/rust-rocksdb)" = "" "checksum rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)" = "" "checksum rotor 0.6.3 (git+https://github.com/ethcore/rotor)" = "" @@ -2642,9 +2712,11 @@ dependencies = [ "checksum unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "26643a2f83bac55f1976fb716c10234485f9202dcd65cfbdf9da49867b271172" "checksum unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "36dff09cafb4ec7c8cf0023eb0b686cb6ce65499116a12201c9e11840ca01beb" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +"checksum untrusted 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "193df64312e3515fd983ded55ad5bcaa7647a035804828ed757e832ce6029ef3" "checksum url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afe9ec54bc4db14bc8744b7fed060d785ac756791450959b2248443319d5b119" "checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47" "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" +"checksum varmint 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5211976e8f86adc9920dd7621777bf8974c7812e48eb2aeb97fb1c26cd55ae84" "checksum vecio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0795a11576d29ae80525a3fda315bf7b534f8feb9d34101e5fe63fb95bb2fd24" "checksum vergen 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "56b639f935488eb40f06d17c3e3bcc3054f6f75d264e187b1107c8d1cba8d31c" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" diff --git a/Cargo.toml b/Cargo.toml index f304c917b..520a6c4c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ rlp = { path = "util/rlp" } rpc-cli = { path = "rpc_cli" } parity-rpc-client = { path = "rpc_client" } parity-hash-fetch = { path = "hash-fetch" } +parity-ipfs = { path = "ipfs" } parity-updater = { path = "updater" } parity-reactor = { path = "util/reactor" } ethcore-dapps = { path = "dapps", optional = true } diff --git a/ipfs/Cargo.toml b/ipfs/Cargo.toml new file mode 100644 index 000000000..46a1dd3aa --- /dev/null +++ b/ipfs/Cargo.toml @@ -0,0 +1,14 @@ +[package] +description = "Parity IPFS crate" +name = "parity-ipfs" +version = "1.6.0" +license = "GPL-3.0" +authors = ["Parity Technologies "] + +[dependencies] +ethcore = { path = "../ethcore" } +ethcore-util = { path = "../util" } +rlp = { path = "../util/rlp" } +hyper = { default-features = false, git = "https://github.com/ethcore/hyper" } +cid = "~0.2.0" +multihash = "~0.5.0" diff --git a/ipfs/src/error.rs b/ipfs/src/error.rs new file mode 100644 index 000000000..6513c6da0 --- /dev/null +++ b/ipfs/src/error.rs @@ -0,0 +1,86 @@ +// Copyright 2015-2017 Parity Technologies (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 {multihash, cid, hyper}; +use handler::Out; + +pub type Result = ::std::result::Result; + +/// IPFS server error +#[derive(Debug)] +pub enum ServerError { + /// Wrapped `std::io::Error` + IoError(::std::io::Error), + /// Other `hyper` error + Other(hyper::error::Error), +} + +pub enum Error { + CidParsingFailed, + UnsupportedHash, + UnsupportedCid, + BlockNotFound, + TransactionNotFound, + StateRootNotFound, +} + +impl From for Out { + fn from(err: Error) -> Out { + use self::Error::*; + + match err { + UnsupportedHash => Out::Bad("Hash must be Keccak-256"), + UnsupportedCid => Out::Bad("CID codec not supported"), + CidParsingFailed => Out::Bad("CID parsing failed"), + BlockNotFound => Out::NotFound("Block not found"), + TransactionNotFound => Out::NotFound("Transaction not found"), + StateRootNotFound => Out::NotFound("State root not found"), + } + } +} + +impl From for Error { + fn from(_: cid::Error) -> Error { + Error::CidParsingFailed + } +} + +impl From for Error { + fn from(_: multihash::Error) -> Error { + Error::CidParsingFailed + } +} + +impl From<::std::io::Error> for ServerError { + fn from(err: ::std::io::Error) -> ServerError { + ServerError::IoError(err) + } +} + +impl From for ServerError { + fn from(err: hyper::error::Error) -> ServerError { + ServerError::Other(err) + } +} + +impl From for String { + fn from(err: ServerError) -> String { + match err { + ServerError::IoError(err) => err.to_string(), + ServerError::Other(err) => err.to_string(), + } + } +} diff --git a/ipfs/src/handler.rs b/ipfs/src/handler.rs new file mode 100644 index 000000000..e8a8549d6 --- /dev/null +++ b/ipfs/src/handler.rs @@ -0,0 +1,129 @@ +use {rlp, multihash}; +use error::{Error, Result}; +use cid::{ToCid, Codec}; + +use std::sync::Arc; +use std::ops::Deref; +use multihash::Hash; +use hyper::Next; +use util::{Bytes, H256}; +use ethcore::client::{BlockId, TransactionId, BlockChainClient}; + +type Reason = &'static str; + +pub enum Out { + OctetStream(Bytes), + NotFound(Reason), + Bad(Reason), +} + +pub struct IpfsHandler { + client: Arc, + out: Out, +} + +impl IpfsHandler { + pub fn new(client: Arc) -> Self { + IpfsHandler { + client: client, + out: Out::NotFound("Route not found") + } + } + + pub fn out(&self) -> &Out { + &self.out + } + + pub fn route(&mut self, path: &str, query: Option<&str>) -> Next { + let result = match path { + "/api/v0/block/get" => self.route_cid(query), + _ => return Next::write(), + }; + + match result { + Ok(_) => Next::write(), + Err(err) => { + self.out = err.into(); + + Next::write() + } + } + } + + fn route_cid(&mut self, query: Option<&str>) -> Result<()> { + let query = query.unwrap_or(""); + + let cid = get_param(&query, "arg").ok_or(Error::CidParsingFailed)?.to_cid()?; + + let mh = multihash::decode(&cid.hash)?; + + if mh.alg != Hash::Keccak256 { return Err(Error::UnsupportedHash); } + + let hash: H256 = mh.digest.into(); + + match cid.codec { + Codec::EthereumBlock => self.get_block(hash), + Codec::EthereumBlockList => self.get_block_list(hash), + Codec::EthereumTx => self.get_transaction(hash), + Codec::EthereumStateTrie => self.get_state_trie(hash), + _ => return Err(Error::UnsupportedCid), + } + } + + fn get_block(&mut self, hash: H256) -> Result<()> { + let block_id = BlockId::Hash(hash); + let block = self.client.block_header(block_id).ok_or(Error::BlockNotFound)?; + + self.out = Out::OctetStream(block.into_inner()); + + Ok(()) + } + + fn get_block_list(&mut self, hash: H256) -> Result<()> { + let ommers = self.client.find_uncles(&hash).ok_or(Error::BlockNotFound)?; + + self.out = Out::OctetStream(rlp::encode(&ommers).to_vec()); + + Ok(()) + } + + fn get_transaction(&mut self, hash: H256) -> Result<()> { + let tx_id = TransactionId::Hash(hash); + let tx = self.client.transaction(tx_id).ok_or(Error::TransactionNotFound)?; + + self.out = Out::OctetStream(rlp::encode(tx.deref()).to_vec()); + + Ok(()) + } + + fn get_state_trie(&mut self, hash: H256) -> Result<()> { + let data = self.client.state_data(&hash).ok_or(Error::StateRootNotFound)?; + + self.out = Out::OctetStream(data); + + Ok(()) + } +} + +/// Get a query parameter's value by name. +pub fn get_param<'a>(query: &'a str, name: &str) -> Option<&'a str> { + query.split('&') + .find(|part| part.starts_with(name) && part[name.len()..].starts_with("=")) + .map(|part| &part[name.len() + 1..]) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_param() { + let query = "foo=100&bar=200&qux=300"; + + assert_eq!(get_param(query, "foo"), Some("100")); + assert_eq!(get_param(query, "bar"), Some("200")); + assert_eq!(get_param(query, "qux"), Some("300")); + assert_eq!(get_param(query, "bar="), None); + assert_eq!(get_param(query, "200"), None); + } +} diff --git a/ipfs/src/lib.rs b/ipfs/src/lib.rs new file mode 100644 index 000000000..341d80ab8 --- /dev/null +++ b/ipfs/src/lib.rs @@ -0,0 +1,123 @@ +// Copyright 2015-2017 Parity Technologies (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 . + +extern crate hyper; +extern crate multihash; +extern crate cid; + +extern crate rlp; +extern crate ethcore; +extern crate ethcore_util as util; + +mod error; +mod handler; + +use std::sync::Arc; +use error::ServerError; +use handler::{IpfsHandler, Out}; +use hyper::server::{Listening, Handler, Request, Response}; +use hyper::net::HttpStream; +use hyper::header::{ContentLength, ContentType}; +use hyper::{Next, Encoder, Decoder, Method, RequestUri, StatusCode}; +use ethcore::client::BlockChainClient; + +impl Handler for IpfsHandler { + fn on_request(&mut self, req: Request) -> Next { + if *req.method() != Method::Get { + return Next::write(); + } + + let (path, query) = match *req.uri() { + RequestUri::AbsolutePath { ref path, ref query } => (path, query.as_ref().map(AsRef::as_ref)), + _ => return Next::write(), + }; + + self.route(path, query) + } + + fn on_request_readable(&mut self, _decoder: &mut Decoder) -> Next { + Next::write() + } + + fn on_response(&mut self, res: &mut Response) -> Next { + use Out::*; + + match *self.out() { + OctetStream(ref bytes) => { + let headers = res.headers_mut(); + + headers.set(ContentLength(bytes.len() as u64)); + headers.set(ContentType("application/octet-stream".parse() + .expect("Static content type; qed"))); + + Next::write() + }, + NotFound(reason) => { + res.set_status(StatusCode::NotFound); + + res.headers_mut().set(ContentLength(reason.len() as u64)); + res.headers_mut().set(ContentType("text/plain".parse() + .expect("Static content type; qed"))); + + Next::write() + }, + Bad(reason) => { + res.set_status(StatusCode::BadRequest); + + res.headers_mut().set(ContentLength(reason.len() as u64)); + res.headers_mut().set(ContentType("text/plain".parse() + .expect("Static content type; qed"))); + + Next::write() + } + } + } + + fn on_response_writable(&mut self, transport: &mut Encoder) -> Next { + use Out::*; + + match *self.out() { + OctetStream(ref bytes) => { + // Nothing to do here + let _ = transport.write(&bytes); + + Next::end() + }, + NotFound(reason) | Bad(reason) => { + // Nothing to do here + let _ = transport.write(reason.as_bytes()); + + Next::end() + } + } + } +} + +pub fn start_server(client: Arc) -> Result { + let addr = "0.0.0.0:5001".parse().expect("can't fail on static input; qed"); + + hyper::Server::http(&addr)? + .handle(move |_| IpfsHandler::new(client.clone())) + .map(|(listening, srv)| { + + ::std::thread::spawn(move || { + srv.run(); + }); + + listening + }) + .map_err(Into::into) +} diff --git a/parity/main.rs b/parity/main.rs index 63d59d5fa..199dff4b4 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -56,6 +56,7 @@ extern crate ethcore_signer; extern crate ethcore_util as util; extern crate ethsync; extern crate parity_hash_fetch as hash_fetch; +extern crate parity_ipfs as ipfs; extern crate parity_reactor; extern crate parity_updater as updater; extern crate rpc_cli; diff --git a/parity/run.rs b/parity/run.rs index 56cd26ea0..728505068 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -47,6 +47,7 @@ use dir::Directories; use cache::CacheConfig; use user_defaults::UserDefaults; use dapps; +use ipfs; use signer; use modules; use rpc_apis; @@ -420,6 +421,10 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R }; let signer_server = signer::start(cmd.signer_conf.clone(), signer_deps)?; + + // the ipfs server + let ipfs_server = ipfs::start_server(client.clone())?; + // the informant let informant = Arc::new(Informant::new( service.client(), @@ -476,7 +481,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R let restart = wait_for_exit(panic_handler, Some(updater), can_restart); // drop this stuff as soon as exit detected. - drop((http_server, ipc_server, dapps_server, signer_server, event_loop)); + drop((http_server, ipc_server, dapps_server, signer_server, ipfs_server, event_loop)); info!("Finishing work, please wait..."); From 9256aa766b5af2b28c4b940e92b55c0fe79ce31b Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Tue, 14 Feb 2017 19:58:46 +0100 Subject: [PATCH 02/51] Lock file --- Cargo.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 7fc7cbc41..1b9be74ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -438,7 +438,6 @@ dependencies = [ "base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore 1.6.0", "ethcore-devtools 1.6.0", "ethcore-rpc 1.6.0", "ethcore-util 1.6.0", From 2ee2d2ea4520e28c001f9afa2667f7f19d92b345 Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Tue, 14 Feb 2017 20:03:25 +0100 Subject: [PATCH 03/51] Added missing GPL header --- ipfs/src/handler.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ipfs/src/handler.rs b/ipfs/src/handler.rs index e8a8549d6..0673826c2 100644 --- a/ipfs/src/handler.rs +++ b/ipfs/src/handler.rs @@ -1,3 +1,19 @@ +// Copyright 2015-2017 Parity Technologies (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 {rlp, multihash}; use error::{Error, Result}; use cid::{ToCid, Codec}; From eb327338e820a36a2e7aa59ec85a909c70203f85 Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Wed, 15 Feb 2017 18:07:30 +0100 Subject: [PATCH 04/51] Tests and grumbles --- ipfs/src/error.rs | 6 ++ ipfs/src/handler.rs | 183 ++++++++++++++++++++++++++++++++++---------- ipfs/src/lib.rs | 20 ++--- parity/run.rs | 1 - 4 files changed, 160 insertions(+), 50 deletions(-) diff --git a/ipfs/src/error.rs b/ipfs/src/error.rs index 6513c6da0..774763786 100644 --- a/ipfs/src/error.rs +++ b/ipfs/src/error.rs @@ -28,6 +28,7 @@ pub enum ServerError { Other(hyper::error::Error), } +#[derive(Debug, PartialEq)] pub enum Error { CidParsingFailed, UnsupportedHash, @@ -37,6 +38,8 @@ pub enum Error { StateRootNotFound, } +/// Convert Error into Out, handy when switching from Rust's Result-based +/// error handling to Hyper's request handling. impl From for Out { fn from(err: Error) -> Out { use self::Error::*; @@ -52,18 +55,21 @@ impl From for Out { } } +/// Convert Content ID errors. impl From for Error { fn from(_: cid::Error) -> Error { Error::CidParsingFailed } } +/// Convert multihash errors (multihash being part of CID). impl From for Error { fn from(_: multihash::Error) -> Error { Error::CidParsingFailed } } +/// Handle IO errors (ports taken when starting the server). impl From<::std::io::Error> for ServerError { fn from(err: ::std::io::Error) -> ServerError { ServerError::IoError(err) diff --git a/ipfs/src/handler.rs b/ipfs/src/handler.rs index 0673826c2..7dd83da47 100644 --- a/ipfs/src/handler.rs +++ b/ipfs/src/handler.rs @@ -19,7 +19,6 @@ use error::{Error, Result}; use cid::{ToCid, Codec}; use std::sync::Arc; -use std::ops::Deref; use multihash::Hash; use hyper::Next; use util::{Bytes, H256}; @@ -27,12 +26,15 @@ use ethcore::client::{BlockId, TransactionId, BlockChainClient}; type Reason = &'static str; +/// Keeps the state of the response to send out +#[derive(Debug, PartialEq)] pub enum Out { OctetStream(Bytes), NotFound(Reason), Bad(Reason), } +/// Request/response handler pub struct IpfsHandler { client: Arc, out: Out, @@ -42,34 +44,34 @@ impl IpfsHandler { pub fn new(client: Arc) -> Self { IpfsHandler { client: client, - out: Out::NotFound("Route not found") + out: Out::Bad("Invalid Request") } } + /// Exposes the outgoing state. The outgoing state should be immutable from the outside. pub fn out(&self) -> &Out { &self.out } + /// Route path + query string to a specialized method pub fn route(&mut self, path: &str, query: Option<&str>) -> Next { - let result = match path { - "/api/v0/block/get" => self.route_cid(query), - _ => return Next::write(), + self.out = match path { + "/api/v0/block/get" => { + let arg = query.and_then(|q| get_param(q, "arg")).unwrap_or(""); + + self.route_cid(arg).unwrap_or_else(Into::into) + }, + + _ => Out::NotFound("Route not found") }; - match result { - Ok(_) => Next::write(), - Err(err) => { - self.out = err.into(); - - Next::write() - } - } + Next::write() } - fn route_cid(&mut self, query: Option<&str>) -> Result<()> { - let query = query.unwrap_or(""); - - let cid = get_param(&query, "arg").ok_or(Error::CidParsingFailed)?.to_cid()?; + /// Attempt to read Content ID from `arg` query parameter, get a hash and + /// route further by the CID's codec. + fn route_cid(&self, cid: &str) -> Result { + let cid = cid.to_cid()?; let mh = multihash::decode(&cid.hash)?; @@ -78,51 +80,47 @@ impl IpfsHandler { let hash: H256 = mh.digest.into(); match cid.codec { - Codec::EthereumBlock => self.get_block(hash), - Codec::EthereumBlockList => self.get_block_list(hash), - Codec::EthereumTx => self.get_transaction(hash), - Codec::EthereumStateTrie => self.get_state_trie(hash), + Codec::EthereumBlock => self.block(hash), + Codec::EthereumBlockList => self.block_list(hash), + Codec::EthereumTx => self.transaction(hash), + Codec::EthereumStateTrie => self.state_trie(hash), _ => return Err(Error::UnsupportedCid), } } - fn get_block(&mut self, hash: H256) -> Result<()> { + /// Get block header by hash as raw binary. + fn block(&self, hash: H256) -> Result { let block_id = BlockId::Hash(hash); let block = self.client.block_header(block_id).ok_or(Error::BlockNotFound)?; - self.out = Out::OctetStream(block.into_inner()); - - Ok(()) + Ok(Out::OctetStream(block.into_inner())) } - fn get_block_list(&mut self, hash: H256) -> Result<()> { - let ommers = self.client.find_uncles(&hash).ok_or(Error::BlockNotFound)?; + /// Get list of block ommers by hash as raw binary. + fn block_list(&self, hash: H256) -> Result { + let uncles = self.client.find_uncles(&hash).ok_or(Error::BlockNotFound)?; - self.out = Out::OctetStream(rlp::encode(&ommers).to_vec()); - - Ok(()) + Ok(Out::OctetStream(rlp::encode(&uncles).to_vec())) } - fn get_transaction(&mut self, hash: H256) -> Result<()> { + /// Get transaction by hash and return as raw binary. + fn transaction(&self, hash: H256) -> Result { let tx_id = TransactionId::Hash(hash); let tx = self.client.transaction(tx_id).ok_or(Error::TransactionNotFound)?; - self.out = Out::OctetStream(rlp::encode(tx.deref()).to_vec()); - - Ok(()) + Ok(Out::OctetStream(rlp::encode(&*tx).to_vec())) } - fn get_state_trie(&mut self, hash: H256) -> Result<()> { + /// Get state trie node by hash and return as raw binary. + fn state_trie(&self, hash: H256) -> Result { let data = self.client.state_data(&hash).ok_or(Error::StateRootNotFound)?; - self.out = Out::OctetStream(data); - - Ok(()) + Ok(Out::OctetStream(data)) } } /// Get a query parameter's value by name. -pub fn get_param<'a>(query: &'a str, name: &str) -> Option<&'a str> { +fn get_param<'a>(query: &'a str, name: &str) -> Option<&'a str> { query.split('&') .find(|part| part.starts_with(name) && part[name.len()..].starts_with("=")) .map(|part| &part[name.len() + 1..]) @@ -131,8 +129,13 @@ pub fn get_param<'a>(query: &'a str, name: &str) -> Option<&'a str> { #[cfg(test)] mod tests { use super::*; + use ethcore::client::TestBlockChainClient; - #[test] + fn get_mocked_handler() -> IpfsHandler { + IpfsHandler::new(Arc::new(TestBlockChainClient::new())) + } + + #[test] fn test_get_param() { let query = "foo=100&bar=200&qux=300"; @@ -141,5 +144,105 @@ mod tests { assert_eq!(get_param(query, "qux"), Some("300")); assert_eq!(get_param(query, "bar="), None); assert_eq!(get_param(query, "200"), None); + assert_eq!(get_param("", "foo"), None); + assert_eq!(get_param("foo", "foo"), None); + assert_eq!(get_param("foo&bar", "foo"), None); + assert_eq!(get_param("bar&foo", "foo"), None); + } + + #[test] + fn cid_route_block() { + let handler = get_mocked_handler(); + + // `eth-block` with Keccak-256 + let cid = "z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM"; + + assert_eq!(Err(Error::BlockNotFound), handler.route_cid(cid)); + } + + #[test] + fn cid_route_block_list() { + let handler = get_mocked_handler(); + + // `eth-block-list` with Keccak-256 + let cid = "z43c7o7FsNxqdLJW8Ucj19tuCALtnmUb2EkDptj4W6xSkFVTqWs"; + + assert_eq!(Err(Error::BlockNotFound), handler.route_cid(cid)); + } + + #[test] + fn cid_route_tx() { + let handler = get_mocked_handler(); + + // `eth-tx` with Keccak-256 + let cid = "z44VCrqbpbPcb8SUBc8Tba4EaKuoDz2grdEoQXx4TP7WYh9ZGBu"; + + assert_eq!(Err(Error::TransactionNotFound), handler.route_cid(cid)); + } + + #[test] + fn cid_route_state_trie() { + let handler = get_mocked_handler(); + + // `eth-state-trie` with Keccak-256 + let cid = "z45oqTS7kR2n2peRGJQ4VCJEeaG9sorqcCyfmznZPJM7FMdhQCT"; + + assert_eq!(Err(Error::StateRootNotFound), handler.route_cid(&cid)); + } + + #[test] + fn cid_route_invalid_hash() { + let handler = get_mocked_handler(); + + // `eth-block` with SHA3-256 hash + let cid = "z43Aa9gr1MM7TENJh4Em9d9Ttr7p3UcfyMpNei6WLVeCmSEPu8F"; + + assert_eq!(Err(Error::UnsupportedHash), handler.route_cid(cid)); + } + + #[test] + fn cid_route_invalid_codec() { + let handler = get_mocked_handler(); + + // `bitcoin-block` with Keccak-256 + let cid = "z4HFyHvb8CarYARyxz4cCcPaciduXd49TFPCKLhYmvNxf7Auvwu"; + + assert_eq!(Err(Error::UnsupportedCid), handler.route_cid(&cid)); + } + + #[test] + fn route_block() { + let mut handler = get_mocked_handler(); + + let _ = handler.route("/api/v0/block/get", Some("arg=z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM")); + + assert_eq!(handler.out(), &Out::NotFound("Block not found")); + } + + #[test] + fn route_block_missing_query() { + let mut handler = get_mocked_handler(); + + let _ = handler.route("/api/v0/block/get", None); + + assert_eq!(handler.out(), &Out::Bad("CID parsing failed")); + } + + #[test] + fn route_block_invalid_query() { + let mut handler = get_mocked_handler(); + + let _ = handler.route("/api/v0/block/get", Some("arg=foobarz43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM")); + + assert_eq!(handler.out(), &Out::Bad("CID parsing failed")); + } + + #[test] + fn route_invalid_route() { + let mut handler = get_mocked_handler(); + + let _ = handler.route("/foo/bar/baz", Some("arg=z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM")); + + assert_eq!(handler.out(), &Out::NotFound("Route not found")); } } diff --git a/ipfs/src/lib.rs b/ipfs/src/lib.rs index 341d80ab8..2a427cd14 100644 --- a/ipfs/src/lib.rs +++ b/ipfs/src/lib.rs @@ -34,6 +34,7 @@ use hyper::header::{ContentLength, ContentType}; use hyper::{Next, Encoder, Decoder, Method, RequestUri, StatusCode}; use ethcore::client::BlockChainClient; +/// Implement Hyper's HTTP handler impl Handler for IpfsHandler { fn on_request(&mut self, req: Request) -> Next { if *req.method() != Method::Get { @@ -109,15 +110,16 @@ impl Handler for IpfsHandler { pub fn start_server(client: Arc) -> Result { let addr = "0.0.0.0:5001".parse().expect("can't fail on static input; qed"); - hyper::Server::http(&addr)? - .handle(move |_| IpfsHandler::new(client.clone())) - .map(|(listening, srv)| { + Ok( + hyper::Server::http(&addr)? + .handle(move |_| IpfsHandler::new(client.clone())) + .map(|(listening, srv)| { - ::std::thread::spawn(move || { - srv.run(); - }); + ::std::thread::spawn(move || { + srv.run(); + }); - listening - }) - .map_err(Into::into) + listening + })? + ) } diff --git a/parity/run.rs b/parity/run.rs index 728505068..ac6f0dcf9 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -421,7 +421,6 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R }; let signer_server = signer::start(cmd.signer_conf.clone(), signer_deps)?; - // the ipfs server let ipfs_server = ipfs::start_server(client.clone())?; From d005410e1ab11b4260574df04d756f416fdc0994 Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Wed, 15 Feb 2017 18:26:35 +0100 Subject: [PATCH 05/51] No .expect on mime types --- Cargo.lock | 1 + ipfs/Cargo.toml | 5 +++-- ipfs/src/lib.rs | 23 +++++++++++++++-------- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1b9be74ed..9bc78e975 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1603,6 +1603,7 @@ dependencies = [ "ethcore 1.6.0", "ethcore-util 1.6.0", "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", + "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "multihash 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.1.0", ] diff --git a/ipfs/Cargo.toml b/ipfs/Cargo.toml index 46a1dd3aa..d1798b425 100644 --- a/ipfs/Cargo.toml +++ b/ipfs/Cargo.toml @@ -9,6 +9,7 @@ authors = ["Parity Technologies "] ethcore = { path = "../ethcore" } ethcore-util = { path = "../util" } rlp = { path = "../util/rlp" } +mime = "0.2" hyper = { default-features = false, git = "https://github.com/ethcore/hyper" } -cid = "~0.2.0" -multihash = "~0.5.0" +cid = "0.2" +multihash = "0.5" diff --git a/ipfs/src/lib.rs b/ipfs/src/lib.rs index 2a427cd14..776dfe85d 100644 --- a/ipfs/src/lib.rs +++ b/ipfs/src/lib.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +#[macro_use] +extern crate mime; extern crate hyper; extern crate multihash; extern crate cid; @@ -58,11 +60,18 @@ impl Handler for IpfsHandler { match *self.out() { OctetStream(ref bytes) => { - let headers = res.headers_mut(); + use mime::{Mime, TopLevel, SubLevel}; - headers.set(ContentLength(bytes.len() as u64)); - headers.set(ContentType("application/octet-stream".parse() - .expect("Static content type; qed"))); + // `OctetStream` is not a valid variant, so need to construct + // the type manually. + let content_type = Mime( + TopLevel::Application, + SubLevel::Ext("octet-stream".into()), + vec![] + ); + + res.headers_mut().set(ContentLength(bytes.len() as u64)); + res.headers_mut().set(ContentType(content_type)); Next::write() }, @@ -70,8 +79,7 @@ impl Handler for IpfsHandler { res.set_status(StatusCode::NotFound); res.headers_mut().set(ContentLength(reason.len() as u64)); - res.headers_mut().set(ContentType("text/plain".parse() - .expect("Static content type; qed"))); + res.headers_mut().set(ContentType(mime!(Text/Plain))); Next::write() }, @@ -79,8 +87,7 @@ impl Handler for IpfsHandler { res.set_status(StatusCode::BadRequest); res.headers_mut().set(ContentLength(reason.len() as u64)); - res.headers_mut().set(ContentType("text/plain".parse() - .expect("Static content type; qed"))); + res.headers_mut().set(ContentType(mime!(Text/Plain))); Next::write() } From 9cfa27830cd9a99fe43e5861ca1030db12d2a1dc Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Wed, 15 Feb 2017 19:25:57 +0100 Subject: [PATCH 06/51] Write output as chunks --- ipfs/src/handler.rs | 24 ++++++------ ipfs/src/lib.rs | 91 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 91 insertions(+), 24 deletions(-) diff --git a/ipfs/src/handler.rs b/ipfs/src/handler.rs index 7dd83da47..5197e8a1a 100644 --- a/ipfs/src/handler.rs +++ b/ipfs/src/handler.rs @@ -36,23 +36,25 @@ pub enum Out { /// Request/response handler pub struct IpfsHandler { + /// Reference to the Blockchain Client client: Arc, - out: Out, + + /// Response to send out + pub out: Out, + + /// How many bytes from the response have been written + pub out_progress: usize, } impl IpfsHandler { pub fn new(client: Arc) -> Self { IpfsHandler { client: client, - out: Out::Bad("Invalid Request") + out: Out::Bad("Invalid Request"), + out_progress: 0, } } - /// Exposes the outgoing state. The outgoing state should be immutable from the outside. - pub fn out(&self) -> &Out { - &self.out - } - /// Route path + query string to a specialized method pub fn route(&mut self, path: &str, query: Option<&str>) -> Next { self.out = match path { @@ -216,7 +218,7 @@ mod tests { let _ = handler.route("/api/v0/block/get", Some("arg=z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM")); - assert_eq!(handler.out(), &Out::NotFound("Block not found")); + assert_eq!(handler.out, Out::NotFound("Block not found")); } #[test] @@ -225,7 +227,7 @@ mod tests { let _ = handler.route("/api/v0/block/get", None); - assert_eq!(handler.out(), &Out::Bad("CID parsing failed")); + assert_eq!(handler.out, Out::Bad("CID parsing failed")); } #[test] @@ -234,7 +236,7 @@ mod tests { let _ = handler.route("/api/v0/block/get", Some("arg=foobarz43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM")); - assert_eq!(handler.out(), &Out::Bad("CID parsing failed")); + assert_eq!(handler.out, Out::Bad("CID parsing failed")); } #[test] @@ -243,6 +245,6 @@ mod tests { let _ = handler.route("/foo/bar/baz", Some("arg=z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM")); - assert_eq!(handler.out(), &Out::NotFound("Route not found")); + assert_eq!(handler.out, Out::NotFound("Route not found")); } } diff --git a/ipfs/src/lib.rs b/ipfs/src/lib.rs index 776dfe85d..5c95fe88e 100644 --- a/ipfs/src/lib.rs +++ b/ipfs/src/lib.rs @@ -27,6 +27,7 @@ extern crate ethcore_util as util; mod error; mod handler; +use std::io::Write; use std::sync::Arc; use error::ServerError; use handler::{IpfsHandler, Out}; @@ -58,7 +59,7 @@ impl Handler for IpfsHandler { fn on_response(&mut self, res: &mut Response) -> Next { use Out::*; - match *self.out() { + match self.out { OctetStream(ref bytes) => { use mime::{Mime, TopLevel, SubLevel}; @@ -97,20 +98,33 @@ impl Handler for IpfsHandler { fn on_response_writable(&mut self, transport: &mut Encoder) -> Next { use Out::*; - match *self.out() { - OctetStream(ref bytes) => { - // Nothing to do here - let _ = transport.write(&bytes); + // Get the data to write as a byte slice + let data = match self.out { + OctetStream(ref bytes) => &bytes, + NotFound(reason) | Bad(reason) => reason.as_bytes(), + }; - Next::end() - }, - NotFound(reason) | Bad(reason) => { - // Nothing to do here - let _ = transport.write(reason.as_bytes()); + write_chunk(transport, &mut self.out_progress, data) + } +} - Next::end() - } - } +fn write_chunk(transport: &mut W, progress: &mut usize, data: &[u8]) -> Next { + // Skip any bytes that have already been written + let chunk = &data[*progress..]; + + // Write an get written count + let written = match transport.write(chunk) { + Ok(written) => written, + Err(_) => return Next::end(), + }; + + *progress += written; + + // Close the connection if the entire chunk has been written, otherwise increment progress + if written < chunk.len() { + Next::write() + } else { + Next::end() } } @@ -130,3 +144,54 @@ pub fn start_server(client: Arc) -> Result = Cursor::new(&mut buf); + let _ = write_chunk(&mut transport, &mut progress, b"foobar"); + } + + assert_eq!(*b"foo", buf); + assert_eq!(3, progress); + + { + let mut transport: Cursor<&mut [u8]> = Cursor::new(&mut buf); + let _ = write_chunk(&mut transport, &mut progress, b"foobar"); + } + + assert_eq!(*b"bar", buf); + assert_eq!(6, progress); + } +} From 451cf42452e28b25bc8f5535ad38b8cb2d36267c Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Wed, 15 Feb 2017 20:29:29 +0100 Subject: [PATCH 07/51] Adding CLI flags for IPFS --- parity/cli/config.full.toml | 4 ++++ parity/cli/config.toml | 4 ++++ parity/cli/mod.rs | 23 ++++++++++++++++++++++- parity/cli/usage.txt | 3 +++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index 5e6bc367a..b527a9b65 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -67,6 +67,10 @@ path = "$HOME/.parity/dapps" user = "test_user" pass = "test_pass" +[ipfs] +disable = true +port = 5001 + [mining] author = "0xdeadbeefcafe0000000000000000000000000001" engine_signer = "0xdeadbeefcafe0000000000000000000000000001" diff --git a/parity/cli/config.toml b/parity/cli/config.toml index 608999799..66886e71e 100644 --- a/parity/cli/config.toml +++ b/parity/cli/config.toml @@ -38,6 +38,10 @@ port = 8080 user = "username" pass = "password" +[ipfs] +disable = true +port = 5001 + [mining] author = "0xdeadbeefcafe0000000000000000000000000001" engine_signer = "0xdeadbeefcafe0000000000000000000000000001" diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 7ac7f8c8d..4cb7ff4e5 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -189,6 +189,12 @@ usage! { or |c: &Config| otry!(c.dapps).pass.clone().map(Some), flag_dapps_apis_all: bool = false, or |_| None, + // IPFS + flag_ipfs_off: bool = true, + or |c: &Config| otry!(c.ipfs).disable.clone(), + flag_ipfs_port: u16 = 5001u16, + or |c: &Config| otry!(c.ipfs).port.clone(), + // -- Sealing/Mining Options flag_author: Option = None, or |c: &Config| otry!(c.mining).author.clone().map(Some), @@ -321,6 +327,7 @@ struct Config { rpc: Option, ipc: Option, dapps: Option, + ipfs: Option, mining: Option, footprint: Option, snapshots: Option, @@ -409,6 +416,12 @@ struct Dapps { pass: Option, } +#[derive(Default, Debug, PartialEq, RustcDecodable)] +struct Ipfs { + disable: Option, + port: Option, +} + #[derive(Default, Debug, PartialEq, RustcDecodable)] struct Mining { author: Option, @@ -482,7 +495,7 @@ struct Misc { mod tests { use super::{ Args, ArgsError, - Config, Operating, Account, Ui, Network, Rpc, Ipc, Dapps, Mining, Footprint, Snapshots, VM, Misc + Config, Operating, Account, Ui, Network, Rpc, Ipc, Dapps, Ipfs, Mining, Footprint, Snapshots, VM, Misc }; use toml; @@ -637,6 +650,10 @@ mod tests { flag_dapps_pass: Some("test_pass".into()), flag_dapps_apis_all: false, + // IPFS + flag_ipfs_off: true, + flag_ipfs_port: 5001u16, + // -- Sealing/Mining Options flag_author: Some("0xdeadbeefcafe0000000000000000000000000001".into()), flag_engine_signer: Some("0xdeadbeefcafe0000000000000000000000000001".into()), @@ -822,6 +839,10 @@ mod tests { user: Some("username".into()), pass: Some("password".into()) }), + ipfs: Some(Ipfs { + disable: Some(true), + port: Some(5001) + }), mining: Some(Mining { author: Some("0xdeadbeefcafe0000000000000000000000000001".into()), engine_signer: Some("0xdeadbeefcafe0000000000000000000000000001".into()), diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index 90c207378..c13e0dee7 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -175,6 +175,9 @@ API and Console Options: --dapps-apis-all Expose all possible RPC APIs on Dapps port. WARNING: INSECURE. Used only for development. (default: {flag_dapps_apis_all}) + --no-ipfs Disable IPFS-compatible HTTP API. (default: {flag_ipfs_off}) + --ipfs-port PORT Configure on which port the IPFS HTTP API should listen. + (default: {flag_ipfs_port}) Sealing/Mining Options: --author ADDRESS Specify the block author (aka "coinbase") address From ad8e3f023064e7ae4ef827fde9fa96d966a61199 Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Thu, 16 Feb 2017 14:41:33 +0100 Subject: [PATCH 08/51] Added CLI flags --- ipfs/src/lib.rs | 5 +++-- parity/cli/config.full.toml | 2 +- parity/cli/config.toml | 2 +- parity/cli/mod.rs | 14 +++++++------- parity/cli/usage.txt | 6 +++--- parity/configuration.rs | 11 +++++++++++ parity/ipfs.rs | 16 ++++++++++++++++ parity/main.rs | 3 ++- parity/run.rs | 6 +++++- 9 files changed, 49 insertions(+), 16 deletions(-) create mode 100644 parity/ipfs.rs diff --git a/ipfs/src/lib.rs b/ipfs/src/lib.rs index 5c95fe88e..e497faed7 100644 --- a/ipfs/src/lib.rs +++ b/ipfs/src/lib.rs @@ -29,6 +29,7 @@ mod handler; use std::io::Write; use std::sync::Arc; +use std::net::{SocketAddr, IpAddr, Ipv4Addr}; use error::ServerError; use handler::{IpfsHandler, Out}; use hyper::server::{Listening, Handler, Request, Response}; @@ -128,8 +129,8 @@ fn write_chunk(transport: &mut W, progress: &mut usize, data: &[u8]) - } } -pub fn start_server(client: Arc) -> Result { - let addr = "0.0.0.0:5001".parse().expect("can't fail on static input; qed"); +pub fn start_server(port: u16, client: Arc) -> Result { + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), port); Ok( hyper::Server::http(&addr)? diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index b527a9b65..0983bf792 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -68,7 +68,7 @@ user = "test_user" pass = "test_pass" [ipfs] -disable = true +enable = false port = 5001 [mining] diff --git a/parity/cli/config.toml b/parity/cli/config.toml index 66886e71e..288f3b2ed 100644 --- a/parity/cli/config.toml +++ b/parity/cli/config.toml @@ -39,7 +39,7 @@ user = "username" pass = "password" [ipfs] -disable = true +enable = false port = 5001 [mining] diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 4cb7ff4e5..a416aa4ce 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -190,9 +190,9 @@ usage! { flag_dapps_apis_all: bool = false, or |_| None, // IPFS - flag_ipfs_off: bool = true, - or |c: &Config| otry!(c.ipfs).disable.clone(), - flag_ipfs_port: u16 = 5001u16, + flag_ipfs_api: bool = false, + or |c: &Config| otry!(c.ipfs).enable.clone(), + flag_ipfs_api_port: u16 = 5001u16, or |c: &Config| otry!(c.ipfs).port.clone(), // -- Sealing/Mining Options @@ -418,7 +418,7 @@ struct Dapps { #[derive(Default, Debug, PartialEq, RustcDecodable)] struct Ipfs { - disable: Option, + enable: Option, port: Option, } @@ -651,8 +651,8 @@ mod tests { flag_dapps_apis_all: false, // IPFS - flag_ipfs_off: true, - flag_ipfs_port: 5001u16, + flag_ipfs_api: false, + flag_ipfs_api_port: 5001u16, // -- Sealing/Mining Options flag_author: Some("0xdeadbeefcafe0000000000000000000000000001".into()), @@ -840,7 +840,7 @@ mod tests { pass: Some("password".into()) }), ipfs: Some(Ipfs { - disable: Some(true), + enable: Some(false), port: Some(5001) }), mining: Some(Mining { diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index c13e0dee7..fd19a8004 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -175,9 +175,9 @@ API and Console Options: --dapps-apis-all Expose all possible RPC APIs on Dapps port. WARNING: INSECURE. Used only for development. (default: {flag_dapps_apis_all}) - --no-ipfs Disable IPFS-compatible HTTP API. (default: {flag_ipfs_off}) - --ipfs-port PORT Configure on which port the IPFS HTTP API should listen. - (default: {flag_ipfs_port}) + --ipfs-api Enable IPFS-compatible HTTP API. (default: {flag_ipfs_api}) + --ipfs-api-port PORT Configure on which port the IPFS HTTP API should listen. + (default: {flag_ipfs_api_port}) Sealing/Mining Options: --author ADDRESS Specify the block author (aka "coinbase") address diff --git a/parity/configuration.rs b/parity/configuration.rs index 349e57679..34fea453d 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -37,6 +37,7 @@ use params::{ResealPolicy, AccountsConfig, GasPricerConfig, MinerExtras}; use ethcore_logger::Config as LogConfig; use dir::{self, Directories, default_hypervisor_path, default_local_path, default_data_path}; use dapps::Configuration as DappsConfiguration; +use ipfs::Configuration as IpfsConfiguration; use signer::{Configuration as SignerConfiguration}; use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack}; use run::RunCmd; @@ -118,6 +119,7 @@ impl Configuration { let geth_compatibility = self.args.flag_geth; let ui_address = self.ui_port().map(|port| (self.ui_interface(), port)); let dapps_conf = self.dapps_config(); + let ipfs_conf = self.ipfs_config(); let signer_conf = self.signer_config(); let format = self.format()?; @@ -342,6 +344,7 @@ impl Configuration { ui_address: ui_address, net_settings: self.network_settings(), dapps_conf: dapps_conf, + ipfs_conf: ipfs_conf, signer_conf: signer_conf, dapp: self.dapp_to_open()?, ui: self.args.cmd_ui, @@ -539,6 +542,13 @@ impl Configuration { } } + fn ipfs_config(&self) -> IpfsConfiguration { + IpfsConfiguration { + enabled: self.args.flag_ipfs_api, + port: self.args.flag_ipfs_api_port, + } + } + fn dapp_to_open(&self) -> Result, String> { if !self.args.cmd_dapp { return Ok(None); @@ -1101,6 +1111,7 @@ mod tests { ui_address: Some(("127.0.0.1".into(), 8180)), net_settings: Default::default(), dapps_conf: Default::default(), + ipfs_conf: Default::default(), signer_conf: Default::default(), ui: false, dapp: None, diff --git a/parity/ipfs.rs b/parity/ipfs.rs new file mode 100644 index 000000000..ed7b59450 --- /dev/null +++ b/parity/ipfs.rs @@ -0,0 +1,16 @@ +pub use parity_ipfs::start_server; + +#[derive(Debug, PartialEq, Clone)] +pub struct Configuration { + pub enabled: bool, + pub port: u16, +} + +impl Default for Configuration { + fn default() -> Self { + Configuration { + enabled: false, + port: 5001, + } + } +} diff --git a/parity/main.rs b/parity/main.rs index 199dff4b4..b9ec46136 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -56,7 +56,7 @@ extern crate ethcore_signer; extern crate ethcore_util as util; extern crate ethsync; extern crate parity_hash_fetch as hash_fetch; -extern crate parity_ipfs as ipfs; +extern crate parity_ipfs; extern crate parity_reactor; extern crate parity_updater as updater; extern crate rpc_cli; @@ -88,6 +88,7 @@ mod cache; mod cli; mod configuration; mod dapps; +mod ipfs; mod deprecated; mod dir; mod helpers; diff --git a/parity/run.rs b/parity/run.rs index ac6f0dcf9..df9fc7384 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -94,6 +94,7 @@ pub struct RunCmd { pub ui_address: Option<(String, u16)>, pub net_settings: NetworkSettings, pub dapps_conf: dapps::Configuration, + pub ipfs_conf: ipfs::Configuration, pub signer_conf: signer::Configuration, pub dapp: Option, pub ui: bool, @@ -422,7 +423,10 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R let signer_server = signer::start(cmd.signer_conf.clone(), signer_deps)?; // the ipfs server - let ipfs_server = ipfs::start_server(client.clone())?; + let ipfs_server = match cmd.ipfs_conf.enabled { + true => Some(ipfs::start_server(cmd.ipfs_conf.port, client.clone())?), + false => None, + }; // the informant let informant = Arc::new(Informant::new( From c4b4a22203cc1a92b164bbb63789a1228137312a Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Thu, 16 Feb 2017 14:51:33 +0100 Subject: [PATCH 09/51] Rename `parity-ipfs` to `parity-ipfs-api` --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- ipfs/Cargo.toml | 4 ++-- parity/ipfs.rs | 2 +- parity/main.rs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9bc78e975..a161d6ea0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,7 +33,7 @@ dependencies = [ "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "parity-hash-fetch 1.6.0", - "parity-ipfs 1.6.0", + "parity-ipfs-api 1.6.0", "parity-reactor 0.1.0", "parity-rpc-client 1.4.0", "parity-updater 1.6.0", @@ -1596,7 +1596,7 @@ dependencies = [ ] [[package]] -name = "parity-ipfs" +name = "parity-ipfs-api" version = "1.6.0" dependencies = [ "cid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 520a6c4c5..094c71c36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ rlp = { path = "util/rlp" } rpc-cli = { path = "rpc_cli" } parity-rpc-client = { path = "rpc_client" } parity-hash-fetch = { path = "hash-fetch" } -parity-ipfs = { path = "ipfs" } +parity-ipfs-api = { path = "ipfs" } parity-updater = { path = "updater" } parity-reactor = { path = "util/reactor" } ethcore-dapps = { path = "dapps", optional = true } diff --git a/ipfs/Cargo.toml b/ipfs/Cargo.toml index d1798b425..d7698ac74 100644 --- a/ipfs/Cargo.toml +++ b/ipfs/Cargo.toml @@ -1,6 +1,6 @@ [package] -description = "Parity IPFS crate" -name = "parity-ipfs" +description = "Parity IPFS-compatible API" +name = "parity-ipfs-api" version = "1.6.0" license = "GPL-3.0" authors = ["Parity Technologies "] diff --git a/parity/ipfs.rs b/parity/ipfs.rs index ed7b59450..c68ace3c1 100644 --- a/parity/ipfs.rs +++ b/parity/ipfs.rs @@ -1,4 +1,4 @@ -pub use parity_ipfs::start_server; +pub use parity_ipfs_api::start_server; #[derive(Debug, PartialEq, Clone)] pub struct Configuration { diff --git a/parity/main.rs b/parity/main.rs index b9ec46136..2d9d888d7 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -56,7 +56,7 @@ extern crate ethcore_signer; extern crate ethcore_util as util; extern crate ethsync; extern crate parity_hash_fetch as hash_fetch; -extern crate parity_ipfs; +extern crate parity_ipfs_api; extern crate parity_reactor; extern crate parity_updater as updater; extern crate rpc_cli; From 8d6275bf07a3eb454e2b93e3af2ee50da1533198 Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Thu, 16 Feb 2017 16:08:54 +0100 Subject: [PATCH 10/51] Only allow requests from Origin 127.0.0.1 --- ipfs/src/lib.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ipfs/src/lib.rs b/ipfs/src/lib.rs index e497faed7..37373344a 100644 --- a/ipfs/src/lib.rs +++ b/ipfs/src/lib.rs @@ -34,7 +34,7 @@ use error::ServerError; use handler::{IpfsHandler, Out}; use hyper::server::{Listening, Handler, Request, Response}; use hyper::net::HttpStream; -use hyper::header::{ContentLength, ContentType}; +use hyper::header::{ContentLength, ContentType, Origin}; use hyper::{Next, Encoder, Decoder, Method, RequestUri, StatusCode}; use ethcore::client::BlockChainClient; @@ -45,6 +45,13 @@ impl Handler for IpfsHandler { return Next::write(); } + // Reject requests if the Origin header isn't valid + if req.headers().get::().map(|o| "127.0.0.1" != &o.host.hostname).unwrap_or(false) { + self.out = Out::Bad("Illegal Origin"); + + return Next::write(); + } + let (path, query) = match *req.uri() { RequestUri::AbsolutePath { ref path, ref query } => (path, query.as_ref().map(AsRef::as_ref)), _ => return Next::write(), @@ -130,7 +137,7 @@ fn write_chunk(transport: &mut W, progress: &mut usize, data: &[u8]) - } pub fn start_server(port: u16, client: Arc) -> Result { - let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), port); + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port); Ok( hyper::Server::http(&addr)? From d4149b965e87e701da0334f7f15eb0e48707a5c3 Mon Sep 17 00:00:00 2001 From: NikVolf Date: Thu, 16 Feb 2017 18:20:24 +0300 Subject: [PATCH 11/51] files list separate fn, sha3 of the list --- ethstore/src/dir/disk.rs | 28 ++++++++++++++++------------ util/src/sha3.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index 4a4637850..023db1ac2 100755 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -90,11 +90,8 @@ impl DiskDirectory where T: KeyFileManager { } } - /// all accounts found in keys directory - fn files(&self) -> Result, Error> { - // it's not done using one iterator cause - // there is an issue with rustc and it takes tooo much time to compile - let paths = fs::read_dir(&self.path)? + fn files(&self) -> Result, Error> { + Ok(fs::read_dir(&self.path)? .flat_map(Result::ok) .filter(|entry| { let metadata = entry.metadata().ok(); @@ -102,14 +99,21 @@ impl DiskDirectory where T: KeyFileManager { let name = file_name.to_string_lossy(); // filter directories metadata.map_or(false, |m| !m.is_dir()) && - // hidden files - !name.starts_with(".") && - // other ignored files - !IGNORED_FILES.contains(&&*name) + // hidden files + !name.starts_with(".") && + // other ignored files + !IGNORED_FILES.contains(&&*name) }) .map(|entry| entry.path()) - .collect::>(); + .collect::>() + ) + } + /// all accounts found in keys directory + fn files_content(&self) -> Result, Error> { + // it's not done using one iterator cause + // there is an issue with rustc and it takes tooo much time to compile + let paths = self.files()?; Ok(paths .into_iter() .filter_map(|path| { @@ -166,7 +170,7 @@ impl DiskDirectory where T: KeyFileManager { impl KeyDirectory for DiskDirectory where T: KeyFileManager { fn load(&self) -> Result, Error> { - let accounts = self.files()? + let accounts = self.files_content()? .into_iter() .map(|(_, account)| account) .collect(); @@ -191,7 +195,7 @@ impl KeyDirectory for DiskDirectory where T: KeyFileManager { fn remove(&self, account: &SafeAccount) -> Result<(), Error> { // enumerate all entries in keystore // and find entry with given address - let to_remove = self.files()? + let to_remove = self.files_content()? .into_iter() .find(|&(_, ref acc)| acc.id == account.id && acc.address == account.address); diff --git a/util/src/sha3.rs b/util/src/sha3.rs index 45d6a34d5..4d3502313 100644 --- a/util/src/sha3.rs +++ b/util/src/sha3.rs @@ -68,6 +68,24 @@ impl Hashable for T where T: AsRef<[u8]> { } } +impl Hashable for [T] where T: Hashable { + fn sha3(&self) -> H256 { + use std::ops::BitXor; + + let mut sha3 = SHA3_EMPTY; + for t in self.iter() { + sha3 = sha3.bitxor(t.sha3()); + }; + + sha3 + } + // todo: optimize? + fn sha3_into(&self, dest: &mut [u8]) { + let sha3 = self.sha3(); + dest.copy_from_slice(&*sha3); + } +} + /// Calculate SHA3 of given stream. pub fn sha3(r: &mut io::BufRead) -> Result { let mut output = [0u8; 32]; @@ -120,4 +138,15 @@ mod tests { // then assert_eq!(format!("{:?}", hash), "68371d7e884c168ae2022c82bd837d51837718a7f7dfb7aa3f753074a35e1d87"); } + + #[test] + fn should_sha3_strs() { + let strs = vec!["abc".to_owned(), "gdc".to_owned()]; + let hash = strs.sha3(); + assert_eq!(hash, "b8f24d705171c55892a34c7b863c258f4d47e6864f7a7da45f84155597a3b338".parse().unwrap()); + + let strs = vec!["abc".to_owned(), "gdc_".to_owned()]; + let hash = strs.sha3(); + assert_eq!(hash, "41bd661b8e02faccad55cdbb28db974dd5c9ae41825b89907fcf25db793b8b09".parse().unwrap()); + } } From f2027c02ef8a2ca10a0ea6954ba8c6f2de1db439 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Thu, 16 Feb 2017 16:37:40 +0100 Subject: [PATCH 12/51] Fixed fonts URLs (#4579) --- js/assets/fonts/Roboto/font.css | 14 +++++++------- js/assets/fonts/RobotoMono/font.css | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/js/assets/fonts/Roboto/font.css b/js/assets/fonts/Roboto/font.css index 5eea2bfd1..75226e621 100644 --- a/js/assets/fonts/Roboto/font.css +++ b/js/assets/fonts/Roboto/font.css @@ -3,7 +3,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(v15/0eC6fl06luXEYWpBSJvXCIX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); + src: local('Roboto Light'), local('Roboto-Light'), url(./v15/0eC6fl06luXEYWpBSJvXCIX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; } /* cyrillic */ @@ -11,7 +11,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(v15/Fl4y0QdOxyyTHEGMXX8kcYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); + src: local('Roboto Light'), local('Roboto-Light'), url(./v15/Fl4y0QdOxyyTHEGMXX8kcYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* greek-ext */ @@ -19,7 +19,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(v15/-L14Jk06m6pUHB-5mXQQnYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); + src: local('Roboto Light'), local('Roboto-Light'), url(./v15/-L14Jk06m6pUHB-5mXQQnYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); unicode-range: U+1F00-1FFF; } /* greek */ @@ -27,7 +27,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(v15/I3S1wsgSg9YCurV6PUkTOYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); + src: local('Roboto Light'), local('Roboto-Light'), url(./v15/I3S1wsgSg9YCurV6PUkTOYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); unicode-range: U+0370-03FF; } /* vietnamese */ @@ -35,7 +35,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(v15/NYDWBdD4gIq26G5XYbHsFIX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); + src: local('Roboto Light'), local('Roboto-Light'), url(./v15/NYDWBdD4gIq26G5XYbHsFIX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB; } /* latin-ext */ @@ -43,7 +43,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(v15/Pru33qjShpZSmG3z6VYwnYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); + src: local('Roboto Light'), local('Roboto-Light'), url(./v15/Pru33qjShpZSmG3z6VYwnYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; } /* latin */ @@ -51,6 +51,6 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(v15/Hgo13k-tfSpn0qi1SFdUfZBw1xU1rKptJj_0jans920.woff2) format('woff2'); + src: local('Roboto Light'), local('Roboto-Light'), url(./v15/Hgo13k-tfSpn0qi1SFdUfZBw1xU1rKptJj_0jans920.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; } diff --git a/js/assets/fonts/RobotoMono/font.css b/js/assets/fonts/RobotoMono/font.css index 5e9ab5721..6a86af729 100644 --- a/js/assets/fonts/RobotoMono/font.css +++ b/js/assets/fonts/RobotoMono/font.css @@ -3,7 +3,7 @@ font-family: 'Roboto Mono'; font-style: normal; font-weight: 300; - src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(v4/N4duVc9C58uwPiY8_59Fz0ExlR2MysFCBK8OirNw2kM.woff2) format('woff2'); + src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz0ExlR2MysFCBK8OirNw2kM.woff2) format('woff2'); unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; } /* cyrillic */ @@ -11,7 +11,7 @@ font-family: 'Roboto Mono'; font-style: normal; font-weight: 300; - src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(v4/N4duVc9C58uwPiY8_59Fz2dsm03krrxlabhmVQFB99s.woff2) format('woff2'); + src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz2dsm03krrxlabhmVQFB99s.woff2) format('woff2'); unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* greek-ext */ @@ -19,7 +19,7 @@ font-family: 'Roboto Mono'; font-style: normal; font-weight: 300; - src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(v4/N4duVc9C58uwPiY8_59FzyJ0caWjaSBdV-xZbEgst_k.woff2) format('woff2'); + src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59FzyJ0caWjaSBdV-xZbEgst_k.woff2) format('woff2'); unicode-range: U+1F00-1FFF; } /* greek */ @@ -27,7 +27,7 @@ font-family: 'Roboto Mono'; font-style: normal; font-weight: 300; - src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(v4/N4duVc9C58uwPiY8_59Fz2MSHb9EAJwuSzGfuRChQzQ.woff2) format('woff2'); + src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz2MSHb9EAJwuSzGfuRChQzQ.woff2) format('woff2'); unicode-range: U+0370-03FF; } /* vietnamese */ @@ -35,7 +35,7 @@ font-family: 'Roboto Mono'; font-style: normal; font-weight: 300; - src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(v4/N4duVc9C58uwPiY8_59Fz-pRBTtN4E2_qSPBnw6AgMc.woff2) format('woff2'); + src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz-pRBTtN4E2_qSPBnw6AgMc.woff2) format('woff2'); unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB; } /* latin-ext */ @@ -43,7 +43,7 @@ font-family: 'Roboto Mono'; font-style: normal; font-weight: 300; - src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(v4/N4duVc9C58uwPiY8_59Fz9Dnm4qiMZlH5rhYv_7LI2Y.woff2) format('woff2'); + src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz9Dnm4qiMZlH5rhYv_7LI2Y.woff2) format('woff2'); unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; } /* latin */ @@ -51,6 +51,6 @@ font-family: 'Roboto Mono'; font-style: normal; font-weight: 300; - src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(v4/N4duVc9C58uwPiY8_59Fz9TIkQYohD4BpHvJ3NvbHoA.woff2) format('woff2'); + src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz9TIkQYohD4BpHvJ3NvbHoA.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; } From 43ce5bef7e156b9560d6e5ad6a24992e6deaf4cf Mon Sep 17 00:00:00 2001 From: NikVolf Date: Thu, 16 Feb 2017 18:47:58 +0300 Subject: [PATCH 13/51] file list hash and test --- ethstore/src/dir/disk.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index 023db1ac2..c8afcc910 100755 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -22,6 +22,7 @@ use {json, SafeAccount, Error}; use json::Uuid; use super::{KeyDirectory, VaultKeyDirectory, VaultKeyDirectoryProvider, VaultKey}; use super::vault::{VAULT_FILE_NAME, VaultDiskDirectory}; +use util::H256; const IGNORED_FILES: &'static [&'static str] = &[ "thumbs.db", @@ -109,6 +110,13 @@ impl DiskDirectory where T: KeyFileManager { ) } + pub fn files_hash(&self) -> Result { + use util::Hashable; + let files = self.files()?; + let file_strs: Vec<&str> = files.iter().map(|fp| fp.to_str().unwrap_or("")).collect(); + Ok(file_strs.sha3()) + } + /// all accounts found in keys directory fn files_content(&self) -> Result, Error> { // it's not done using one iterator cause @@ -283,7 +291,6 @@ mod test { let account = SafeAccount::create(&keypair, [0u8; 16], password, 1024, "Test".to_owned(), "{}".to_owned()); let res = directory.insert(account); - // then assert!(res.is_ok(), "Should save account succesfuly."); assert!(res.unwrap().filename.is_some(), "Filename has been assigned."); @@ -340,4 +347,25 @@ mod test { assert!(vaults.iter().any(|v| &*v == "vault1")); assert!(vaults.iter().any(|v| &*v == "vault2")); } + + #[test] + fn hash_of_files() { + let temp_path = RandomTempPath::new(); + let directory = RootDiskDirectory::create(&temp_path).unwrap(); + + let hash = directory.files_hash().expect("Files hash should be calculated ok"); + assert_eq!( + hash, + "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470".parse().unwrap() + ); + + let keypair = Random.generate().unwrap(); + let password = "test pass"; + let account = SafeAccount::create(&keypair, [0u8; 16], password, 1024, "Test".to_owned(), "{}".to_owned()); + directory.insert(account).expect("Account should be inserted ok"); + + let new_hash = directory.files_hash().expect("New files hash should be calculated ok"); + + assert!(new_hash != hash, "hash of the file list should change once directory content changed"); + } } From 5bd1cf352b97216d0b2c8e8898c466a7cc083ef2 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 16 Feb 2017 15:50:43 +0000 Subject: [PATCH 14/51] [ci skip] js-precompiled 20170216-154550 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c51c85ee0..4b21ca7ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1617,7 +1617,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#8cfa973a48243b57279f2f0b2cbe3f010c6c5a34" +source = "git+https://github.com/ethcore/js-precompiled.git#e1d75910126030f2d9980b3301297baf107ad2fe" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index cdc861194..33a560b05 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.89", + "version": "0.3.90", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 4cd4572417acbd617a0bf2487d2c8b215202a60f Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Thu, 16 Feb 2017 20:03:13 +0400 Subject: [PATCH 15/51] add tag ENV in Dockerfile --- docker/hub/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/hub/Dockerfile b/docker/hub/Dockerfile index f78bbd217..9e8dba9ef 100644 --- a/docker/hub/Dockerfile +++ b/docker/hub/Dockerfile @@ -57,12 +57,13 @@ g++ -v # build parity RUN git clone https://github.com/ethcore/parity && \ cd parity && \ + git checkout $CI_BUILD_REF_NAME git pull && \ cargo build --release --features final && \ ls /build/parity/target/release/parity && \ strip /build/parity/target/release/parity -RUN file /build/parity/target/release/parity&&cp /build/parity/target/release/parity /parity +RUN file /build/parity/target/release/parity&&mkdir -p /parity&&cp /build/parity/target/release/parity /parity #cleanup Docker image RUN rm -rf /root/.cargo&&rm -rf /root/.multirust&&rm -rf /root/.rustup&&rm -rf /build From aa83603af8177944223a898c585e432a34a0c7cd Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Thu, 16 Feb 2017 20:04:28 +0400 Subject: [PATCH 16/51] typo fix in Dockerfile --- docker/hub/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/hub/Dockerfile b/docker/hub/Dockerfile index 9e8dba9ef..7de63691b 100644 --- a/docker/hub/Dockerfile +++ b/docker/hub/Dockerfile @@ -57,10 +57,10 @@ g++ -v # build parity RUN git clone https://github.com/ethcore/parity && \ cd parity && \ - git checkout $CI_BUILD_REF_NAME + git checkout $CI_BUILD_REF_NAME && \ git pull && \ cargo build --release --features final && \ - ls /build/parity/target/release/parity && \ + ls /build/parity/target/release/parity && \ strip /build/parity/target/release/parity RUN file /build/parity/target/release/parity&&mkdir -p /parity&&cp /build/parity/target/release/parity /parity From 00c843afea33f25af9563804aa84a7806107a59d Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 16 Feb 2017 19:42:01 +0300 Subject: [PATCH 17/51] Added vaults support to `ethstore-cli` (#4532) * added vaults support to ethstore-cli * improved error message --- ethstore/README.md | 179 +++++++++++++++++++++++++++++++---- ethstore/src/bin/ethstore.rs | 126 +++++++++++++++++++----- 2 files changed, 263 insertions(+), 42 deletions(-) diff --git a/ethstore/README.md b/ethstore/README.md index 121515943..1986da72b 100644 --- a/ethstore/README.md +++ b/ethstore/README.md @@ -16,23 +16,33 @@ Ethereum key management. Copyright 2016, 2017 Parity Technologies (UK) Ltd Usage: - ethstore insert [--dir DIR] - ethstore change-pwd
[--dir DIR] - ethstore list [--dir DIR] + ethstore insert [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore change-pwd
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore list [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] ethstore import [--src DIR] [--dir DIR] - ethstore import-wallet [--dir DIR] - ethstore remove
[--dir DIR] - ethstore sign
[--dir DIR] + ethstore import-wallet [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore remove
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore sign
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore public
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore list-vaults [--dir DIR] + ethstore create-vault [--dir DIR] + ethstore change-vault-pwd [--dir DIR] + ethstore move-to-vault
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore move-from-vault
[--dir DIR] ethstore [-h | --help] Options: - -h, --help Display this message and exit. - --dir DIR Specify the secret store directory. It may be either - parity, parity-test, geth, geth-test - or a path [default: parity]. - --src DIR Specify import source. It may be either - parity, parity-test, get, geth-test - or a path [default: geth]. + -h, --help Display this message and exit. + --dir DIR Specify the secret store directory. It may be either + parity, parity-test, geth, geth-test + or a path [default: parity]. + --vault VAULT Specify vault to use in this operation. + --vault-pwd VAULTPWD Specify vault password to use in this operation. Please note + that this option is required when vault option is set. + Otherwise it is ignored. + --src DIR Specify import source. It may be either + parity, parity-test, get, geth-test + or a path [default: geth]. Commands: insert Save account with password. @@ -42,16 +52,24 @@ Commands: import-wallet Import presale wallet. remove Remove account. sign Sign message. + public Displays public key for an address. + list-vaults List vaults. + create-vault Create new vault. + change-vault-pwd Change vault password. + move-to-vault Move account to vault from another vault/root directory. + move-from-vault Move account to root directory from given vault or root. ``` ### Examples -#### `insert [--dir DIR]` +#### `insert [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` *Encrypt secret with a password and save it in secret store.* - `` - ethereum secret, 32 bytes long - `` - account password, file path - `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity +- `[--vault VAULT]` - vault to use in this operation +- `[--vault-pwd VAULTPWD]` - vault password to use in this operation, file path ``` ethstore insert 7d29fab185a33e2cd955812397354c472d2b84615b645aa135ff539f6b0d70d5 password.txt @@ -73,13 +91,15 @@ ethstore insert `ethkey generate random -s` "this is sparta" -- -#### `change-pwd
[--dir DIR]` +#### `change-pwd
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` *Change account password.* - `
` - ethereum address, 20 bytes long - `` - old account password, file path - `` - new account password, file path - `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity +- `[--vault VAULT]` - vault to use in this operation +- `[--vault-pwd VAULTPWD]` - vault password to use in this operation, file path ``` ethstore change-pwd a8fa5dd30a87bb9e3288d604eb74949c515ab66e old_pwd.txt new_pwd.txt @@ -91,10 +111,12 @@ true -- -#### `list [--dir DIR]` +#### `list [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` *List secret store accounts.* - `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity +- `[--vault VAULT]` - vault to use in this operation +- `[--vault-pwd VAULTPWD]` - vault password to use in this operation, file path ``` ethstore list @@ -125,12 +147,14 @@ ethstore import -- -#### `import-wallet [--dir DIR]` +#### `import-wallet [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` *Import account from presale wallet.* - `` - presale wallet path - `` - account password, file path - `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity +- `[--vault VAULT]` - vault to use in this operation +- `[--vault-pwd VAULTPWD]` - vault password to use in this operation, file path ``` ethstore import-wallet ethwallet.json password.txt @@ -142,12 +166,14 @@ e6a3d25a7cb7cd21cb720df5b5e8afd154af1bbb -- -#### `remove
[--dir DIR]` +#### `remove
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` *Remove account from secret store.* - `
` - ethereum address, 20 bytes long - `` - account password, file path - `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity +- `[--vault VAULT]` - vault to use in this operation +- `[--vault-pwd VAULTPWD]` - vault password to use in this operation, file path ``` ethstore remove a8fa5dd30a87bb9e3288d604eb74949c515ab66e password.txt @@ -159,13 +185,15 @@ true -- -#### `sign
[--dir DIR]` +#### `sign
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` *Sign message with account's secret.* - `
` - ethereum address, 20 bytes long - `` - account password, file path - `` - message to sign, 32 bytes long - `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity +- `[--vault VAULT]` - vault to use in this operation +- `[--vault-pwd VAULTPWD]` - vault password to use in this operation, file path ``` ethstore sign 24edfff680d536a5f6fe862d36df6f8f6f40f115 password.txt 7d29fab185a33e2cd955812397354c472d2b84615b645aa135ff539f6b0d70d5 @@ -177,6 +205,119 @@ c6649f9555232d90ff716d7e552a744c5af771574425a74860e12f763479eb1b708c1f3a7dc0a0a7 -- +#### `public
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` +*Displays public key for an address.* + +- `
` - ethereum address, 20 bytes long +- `` - account password, file path +- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity +- `[--vault VAULT]` - vault to use in this operation +- `[--vault-pwd VAULTPWD]` - vault password to use in this operation, file path + +``` +ethstore public 00e63fdb87ceb815ec96ae185b8f7381a0b4a5ea account_password.txt --vault vault_name --vault-pwd vault_password.txt +``` + +``` +0x84161d8c05a996a534efbec50f24485cfcc07458efaef749a1b22156d7836c903eeb39bf2df74676e702eacc4cfdde069e5fd86692b5ef6ef81ba906e9e77d82 +``` + +-- + +#### `list-vaults [--dir DIR]` +*List vaults.* + +- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity + +``` +ethstore list-vaults +``` + +``` +vault1 +vault2 +vault3 +``` + +-- + +#### `create-vault [--dir DIR]` +*Create new vault.* + +- `` - name of new vault. This can only contain letters, digits, whitespaces, dashes and underscores +- `` - vault password, file path +- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity + +``` +ethstore create-vault vault3 vault3_password.txt +``` + +``` +OK +``` + +-- + +#### `change-vault-pwd [--dir DIR]` +*Change vault password.* + +- `` - name of existing vault +- `` - old vault password, file path +- `` - new vault password, file path +- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity + +``` +ethstore change-vault-pwd vault3 vault3_password.txt new_vault3_password.txt +``` + +``` +OK +``` + +-- + +#### `move-to-vault
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` +*Move account to vault from another vault/root directory.* + +- `
` - ethereum address, 20 bytes long +- `` - name of existing vault to move account to +- `` - password of existing `` to move account to, file path +- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity +- `[--vault VAULT]` - current vault of the `
` argument, if set +- `[--vault-pwd VAULTPWD]` - password for the current vault of the `
` argument, if any. file path + + +``` +ethstore move-to-vault 00e63fdb87ceb815ec96ae185b8f7381a0b4a5ea vault3 vault3_password.txt +ethstore move-to-vault 00e63fdb87ceb815ec96ae185b8f7381a0b4a5ea vault1 vault1_password.txt --vault vault3 --vault-pwd vault3_password.txt +``` + +``` +OK +OK +``` + +-- + +#### `move-from-vault
[--dir DIR]` +*Move account to root directory from given vault.* + +- `
` - ethereum address, 20 bytes long +- `` - name of existing vault to move account to +- `` - password of existing `` to move account to, file path +- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity + + +``` +ethstore move-from-vault 00e63fdb87ceb815ec96ae185b8f7381a0b4a5ea vault1 vault1_password.txt +``` + +``` +OK +``` + +-- + # Ethcore toolchain *this project is a part of the ethcore toolchain* diff --git a/ethstore/src/bin/ethstore.rs b/ethstore/src/bin/ethstore.rs index 06a0b40a8..20411a629 100644 --- a/ethstore/src/bin/ethstore.rs +++ b/ethstore/src/bin/ethstore.rs @@ -31,24 +31,33 @@ Ethereum key management. Copyright 2016, 2017 Parity Technologies (UK) Ltd Usage: - ethstore insert [--dir DIR] - ethstore change-pwd
[--dir DIR] - ethstore list [--dir DIR] + ethstore insert [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore change-pwd
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore list [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] ethstore import [--src DIR] [--dir DIR] - ethstore import-wallet [--dir DIR] - ethstore remove
[--dir DIR] - ethstore sign
[--dir DIR] - ethstore public
+ ethstore import-wallet [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore remove
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore sign
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore public
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore list-vaults [--dir DIR] + ethstore create-vault [--dir DIR] + ethstore change-vault-pwd [--dir DIR] + ethstore move-to-vault
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore move-from-vault
[--dir DIR] ethstore [-h | --help] Options: - -h, --help Display this message and exit. - --dir DIR Specify the secret store directory. It may be either - parity, parity-test, geth, geth-test - or a path [default: parity]. - --src DIR Specify import source. It may be either - parity, parity-test, get, geth-test - or a path [default: geth]. + -h, --help Display this message and exit. + --dir DIR Specify the secret store directory. It may be either + parity, parity-test, geth, geth-test + or a path [default: parity]. + --vault VAULT Specify vault to use in this operation. + --vault-pwd VAULTPWD Specify vault password to use in this operation. Please note + that this option is required when vault option is set. + Otherwise it is ignored. + --src DIR Specify import source. It may be either + parity, parity-test, get, geth-test + or a path [default: geth]. Commands: insert Save account with password. @@ -59,6 +68,11 @@ Commands: remove Remove account. sign Sign message. public Displays public key for an address. + list-vaults List vaults. + create-vault Create new vault. + change-vault-pwd Change vault password. + move-to-vault Move account to vault from another vault/root directory. + move-from-vault Move account to root directory from given vault. "#; #[derive(Debug, RustcDecodable)] @@ -71,6 +85,11 @@ struct Args { cmd_remove: bool, cmd_sign: bool, cmd_public: bool, + cmd_list_vaults: bool, + cmd_create_vault: bool, + cmd_change_vault_pwd: bool, + cmd_move_to_vault: bool, + cmd_move_from_vault: bool, arg_secret: String, arg_password: String, arg_old_pwd: String, @@ -78,8 +97,11 @@ struct Args { arg_address: String, arg_message: String, arg_path: String, + arg_vault: String, flag_src: String, flag_dir: String, + flag_vault: String, + flag_vault_pwd: String, } fn main() { @@ -104,6 +126,23 @@ fn key_dir(location: &str) -> Result, Error> { Ok(dir) } +fn open_args_vault(store: &EthStore, args: &Args) -> Result { + if args.flag_vault.is_empty() { + return Ok(SecretVaultRef::Root); + } + + let vault_pwd = load_password(&args.flag_vault_pwd)?; + store.open_vault(&args.flag_vault, &vault_pwd)?; + Ok(SecretVaultRef::Vault(args.flag_vault.clone())) +} + +fn open_args_vault_account(store: &EthStore, address: Address, args: &Args) -> Result { + match open_args_vault(store, args)? { + SecretVaultRef::Root => Ok(StoreAccountRef::root(address)), + SecretVaultRef::Vault(name) => Ok(StoreAccountRef::vault(&name, address)), + } +} + fn format_accounts(accounts: &[Address]) -> String { accounts.iter() .enumerate() @@ -112,10 +151,14 @@ fn format_accounts(accounts: &[Address]) -> String { .join("\n") } +fn format_vaults(vaults: &[String]) -> String { + vaults.join("\n") +} + fn load_password(path: &str) -> Result { - let mut file = fs::File::open(path)?; + let mut file = fs::File::open(path).map_err(|e| Error::Custom(format!("Error opening password file {}: {}", path, e)))?; let mut password = String::new(); - file.read_to_string(&mut password)?; + file.read_to_string(&mut password).map_err(|e| Error::Custom(format!("Error reading password file {}: {}", path, e)))?; // drop EOF let _ = password.pop(); Ok(password) @@ -131,17 +174,24 @@ fn execute(command: I) -> Result where I: IntoIterator = accounts.into_iter().map(|a| a.address).collect(); + let accounts: Vec<_> = accounts + .into_iter() + .filter(|a| &a.vault == &vault_ref) + .map(|a| a.address) + .collect(); Ok(format_accounts(&accounts)) } else if args.cmd_import { let src = key_dir(&args.flag_src)?; @@ -152,24 +202,54 @@ fn execute(command: I) -> Result where I: IntoIterator Date: Thu, 16 Feb 2017 17:42:19 +0100 Subject: [PATCH 18/51] Better display of tags (#4564) * WIP * Update accounts on whitelist change in Parity Bar * Fix AccountCard width in Parity Bar (Signer) * Added AccountCard Example * Use horizontal tags * Better Tags display * Scrollable tags * Update PR Grumbles * Fix tests (add tags) * PR Grumble --- js/src/api/subscriptions/personal.js | 1 + js/src/playground/playground.css | 3 +- js/src/playground/playground.js | 2 + js/src/ui/AccountCard/accountCard.css | 46 ++++++-- js/src/ui/AccountCard/accountCard.example.js | 110 +++++++++++++++++++ js/src/ui/AccountCard/accountCard.js | 53 ++++++--- js/src/ui/AccountCard/accountCard.spec.js | 4 +- js/src/ui/Tags/tags.css | 31 +++++- js/src/ui/Tags/tags.js | 25 ++++- js/src/views/ParityBar/accountStore.js | 10 +- js/src/views/ParityBar/parityBar.css | 25 +++-- js/src/views/ParityBar/parityBar.js | 1 + 12 files changed, 262 insertions(+), 49 deletions(-) create mode 100644 js/src/ui/AccountCard/accountCard.example.js diff --git a/js/src/api/subscriptions/personal.js b/js/src/api/subscriptions/personal.js index 4f386b7c8..715f4cfe5 100644 --- a/js/src/api/subscriptions/personal.js +++ b/js/src/api/subscriptions/personal.js @@ -126,6 +126,7 @@ export default class Personal { case 'parity_setDappsAddresses': case 'parity_setNewDappsWhitelist': this._defaultAccount(true); + this._listAccounts(); return; } }); diff --git a/js/src/playground/playground.css b/js/src/playground/playground.css index f4e6e55d4..92556ae94 100644 --- a/js/src/playground/playground.css +++ b/js/src/playground/playground.css @@ -74,7 +74,7 @@ $codeColor: #93a1a1; flex: 1; overflow: auto; padding: 0.5em; - background-color: #$codeBackground; + background-color: $codeBackground; color: $codeColor; font-size: 0.75em; @@ -86,5 +86,6 @@ $codeColor: #93a1a1; .component { flex: 3; padding-left: 0.5em; + overflow: auto; } } diff --git a/js/src/playground/playground.js b/js/src/playground/playground.js index a1790ed32..9aa32f5c0 100644 --- a/js/src/playground/playground.js +++ b/js/src/playground/playground.js @@ -17,6 +17,7 @@ import { observer } from 'mobx-react'; import React, { Component } from 'react'; +import AccountCard from '~/ui/AccountCard/accountCard.example'; import CurrencySymbol from '~/ui/CurrencySymbol/currencySymbol.example'; import QrCode from '~/ui/QrCode/qrCode.example'; import SectionList from '~/ui/SectionList/sectionList.example'; @@ -25,6 +26,7 @@ import Portal from '~/ui/Portal/portal.example'; import PlaygroundStore from './store'; import styles from './playground.css'; +PlaygroundStore.register(); PlaygroundStore.register(); PlaygroundStore.register(); PlaygroundStore.register(); diff --git a/js/src/ui/AccountCard/accountCard.css b/js/src/ui/AccountCard/accountCard.css index fcf1522f5..4799b1310 100644 --- a/js/src/ui/AccountCard/accountCard.css +++ b/js/src/ui/AccountCard/accountCard.css @@ -16,18 +16,14 @@ */ .account { - padding: 1em; - margin: 0.5em 0; - - display: flex; - flex-direction: column; - align-items: flex-start; - + align-items: stretch; background-color: rgba(0, 0, 0, 0.8); - + display: flex; + flex-direction: row; + margin: 0.5em 0; + overflow: hidden; transition: transform ease-out 0.1s; transform: scale(1); - overflow: hidden; &.copied { animation-duration: 0.25s; @@ -53,6 +49,38 @@ } } +.mainContainer { + flex: 1 1 auto; + overflow: hidden; + padding: 1em; +} + +.tagsContainer { + flex: 0 0 auto; + position: relative; + width: 3em; +} + +.tags { + background-color: rgba(0, 0, 0, 0.4); + box-sizing: content-box; + height: calc(100% - 0.5em); + overflow-x: hidden; + overflow-y: scroll; + padding: 0.25em; + padding-right: 2em; + position: absolute; + right: -2.5em; + transition: background-color 0.2s ease-out; + width: calc(100% + 0.25em); + + &:hover { + background-color: rgba(0, 0, 0, 0.8); + padding-left: 0.5em; + width: auto; + } +} + .infoContainer { display: flex; flex-direction: row; diff --git a/js/src/ui/AccountCard/accountCard.example.js b/js/src/ui/AccountCard/accountCard.example.js new file mode 100644 index 000000000..8c4ea9a4b --- /dev/null +++ b/js/src/ui/AccountCard/accountCard.example.js @@ -0,0 +1,110 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import React, { Component } from 'react'; + +import PlaygroundExample from '~/playground/playgroundExample'; + +import AccountCard from './accountCard'; + +export default class AccountCardExample extends Component { + render () { + const account = { + address: '0x639ba260535db072a41115c472830846e4e9ad0f', + description: 'This is a description for the main account', + meta: { + tags: [ 'important', 'zargo' ] + }, + name: 'Main Account' + }; + + const balance = { + tokens: [ + { + value: 100000000000000000000, + token: { + tag: 'ETH' + } + } + ] + }; + + const accountManyTags = { + ...account, + meta: { tags: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((n) => `tag #${n}`) } + }; + + const accountNoTags = { + ...account, + meta: { tags: [] } + }; + + return ( +
+ + + + + +
+ +
+
+ + +
+ +
+
+ + +
+ +
+
+ + +
+
+ +
+
+ +
+
+
+
+ ); + } +} diff --git a/js/src/ui/AccountCard/accountCard.js b/js/src/ui/AccountCard/accountCard.js index 61d88a37f..9167f38e6 100644 --- a/js/src/ui/AccountCard/accountCard.js +++ b/js/src/ui/AccountCard/accountCard.js @@ -58,28 +58,45 @@ export default class AccountCard extends Component { onFocus={ this.onFocus } onKeyDown={ this.handleKeyDown } > -
- -
-
- +
+
+ +
+
+ +
+ { this.renderDescription(description) } + { this.renderAddress(address) }
- { this.renderDescription(description) } - { this.renderAddress(address) }
+ +
- - + { + tags && tags.length > 0 + ? ( +
+
+ +
+
+ ) : null + } +
); } diff --git a/js/src/ui/AccountCard/accountCard.spec.js b/js/src/ui/AccountCard/accountCard.spec.js index 7a5b68428..cecce7a89 100644 --- a/js/src/ui/AccountCard/accountCard.spec.js +++ b/js/src/ui/AccountCard/accountCard.spec.js @@ -33,7 +33,9 @@ function render (props = {}) { address: TEST_ADDRESS, description: 'testDescription', name: TEST_NAME, - meta: {} + meta: { + tags: [ 'tag 1', 'tag 2' ] + } }; } diff --git a/js/src/ui/Tags/tags.css b/js/src/ui/Tags/tags.css index 6e160efa3..9fbbaaace 100644 --- a/js/src/ui/Tags/tags.css +++ b/js/src/ui/Tags/tags.css @@ -18,16 +18,39 @@ .tags { display: flex; flex-wrap: wrap; - position: absolute; - right: 0.25rem; - top: 0; + + &.floating { + position: absolute; + right: 0.25rem; + top: 0; + } + + &.horizontal { + flex-direction: column; + } +} + +.floating .tag { + margin-top: 0.75em; +} + +.horizontal .tag { + margin: 0.25em 0; + + .text { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 150%; + } } .tag { font-size: 0.75rem; background: rgba(255, 255, 255, 0.07); border-radius: 16px; - margin: 0.75em 0.5em 0 0; + margin: 0 0.5em; padding: 0.25em 1em; opacity: 1; transition: opacity 0.2s ease-out; diff --git a/js/src/ui/Tags/tags.js b/js/src/ui/Tags/tags.js index 5c44127c4..13ce0c15f 100644 --- a/js/src/ui/Tags/tags.js +++ b/js/src/ui/Tags/tags.js @@ -22,20 +22,37 @@ import styles from './tags.css'; export default class Tags extends Component { static propTypes = { + floating: PropTypes.bool, + horizontal: PropTypes.bool, handleAddSearchToken: PropTypes.func, setRefs: PropTypes.func, tags: arrayOrObjectProptype() - } + }; + + static defaultProps = { + horizontal: false, + floating: true + }; render () { - const { tags } = this.props; + const { floating, horizontal, tags } = this.props; if (!tags || tags.length === 0) { return null; } + const classes = [ styles.tags ]; + + if (floating) { + classes.push(styles.floating); + } + + if (horizontal) { + classes.push(styles.horizontal); + } + return ( -
+
{ this.renderTags() }
); @@ -66,7 +83,7 @@ export default class Tags extends Component { onClick={ onClick } ref={ setRef } > - { tag } + { tag }
); }); diff --git a/js/src/views/ParityBar/accountStore.js b/js/src/views/ParityBar/accountStore.js index f1d704d50..bf854e194 100644 --- a/js/src/views/ParityBar/accountStore.js +++ b/js/src/views/ParityBar/accountStore.js @@ -111,10 +111,18 @@ export default class AccountStore { } subscribeDefaultAccount () { - return this._api.subscribe('parity_defaultAccount', (error, defaultAccount) => { + const promiseDefaultAccount = this._api.subscribe('parity_defaultAccount', (error, defaultAccount) => { if (!error) { this.setDefaultAccount(defaultAccount); } }); + + const promiseEthAccounts = this._api.subscribe('eth_accounts', (error) => { + if (!error) { + this.loadAccounts(); + } + }); + + return Promise.all([ promiseDefaultAccount, promiseEthAccounts ]); } } diff --git a/js/src/views/ParityBar/parityBar.css b/js/src/views/ParityBar/parityBar.css index 64b0b3521..265bf7894 100644 --- a/js/src/views/ParityBar/parityBar.css +++ b/js/src/views/ParityBar/parityBar.css @@ -84,7 +84,6 @@ $modalZ: 10001; .parityBg { position: fixed; - transition-property: left, top, right, bottom; transition-duration: 0.25s; transition-timing-function: ease; @@ -95,20 +94,25 @@ $modalZ: 10001; } } +.accountsSection { + padding: 0 1em; + width: 920px; +} + .expanded { border-radius: 4px 4px 0 0; display: flex; - flex-direction: column; + flex-direction: row; min-height: 30vh; max-height: 80vh; - max-width: calc(100vw - 1em); + max-width: calc(100vw - 2em); .content { flex: 1; - overflow-y: auto; - overflow-x: hidden; + overflow: auto; display: flex; background: rgba(0, 0, 0, 0.8); + max-width: calc(100vw - 2em); } } @@ -130,7 +134,8 @@ $modalZ: 10001; color: white !important; display: inline-block; - img, svg { + img, + svg { height: 24px !important; width: 24px !important; margin: 2px 0.5em 0 0; @@ -160,7 +165,7 @@ $modalZ: 10001; min-width: 2em !important; img { - margin: 6px 0.5em 0 0.5em; + margin: 6px 0.5em 0; } } @@ -177,7 +182,7 @@ $modalZ: 10001; .labelBubble { position: absolute; - top: 0px; + top: 0; right: -10px; } @@ -187,7 +192,7 @@ $modalZ: 10001; background: rgba(0, 0, 0, 0.25); margin-bottom: 0; - &:after { + &::after { clear: both; } } @@ -243,11 +248,9 @@ $modalZ: 10001; width: 1em; height: 1em; margin-left: 0.5em; - background-color: white; opacity: 0.25; border-radius: 50%; - transition-property: opacity; transition-duration: 0.1s; transition-timing-function: ease-in-out; diff --git a/js/src/views/ParityBar/parityBar.js b/js/src/views/ParityBar/parityBar.js index fe737a873..d9d584a34 100644 --- a/js/src/views/ParityBar/parityBar.js +++ b/js/src/views/ParityBar/parityBar.js @@ -328,6 +328,7 @@ class ParityBar extends Component { displayType === DISPLAY_ACCOUNTS ? ( Date: Thu, 16 Feb 2017 17:43:27 +0100 Subject: [PATCH 19/51] remove vertx from Webpack config (#4576) --- js/webpack/npm.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/webpack/npm.js b/js/webpack/npm.js index 3f440c698..b1f41d805 100644 --- a/js/webpack/npm.js +++ b/js/webpack/npm.js @@ -48,8 +48,7 @@ module.exports = { umdNamedDefine: true }, externals: { - 'node-fetch': 'node-fetch', - 'vertx': 'vertx' + 'node-fetch': 'node-fetch' }, module: { noParse: [ From 301a707eaa510dca86372b5692dbe30081f9ec26 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Thu, 16 Feb 2017 17:45:12 +0100 Subject: [PATCH 20/51] Ledger signing fixed (#4578) --- hw/src/ledger.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/src/ledger.rs b/hw/src/ledger.rs index 50a9ee5f3..255fa8515 100644 --- a/hw/src/ledger.rs +++ b/hw/src/ledger.rs @@ -230,7 +230,7 @@ impl Manager { if result.len() != 65 { return Err(Error::Protocol("Signature packet size mismatch")); } - let v = result[0]; + let v = (result[0] + 1) % 2; let r = H256::from_slice(&result[1..33]); let s = H256::from_slice(&result[33..65]); Ok(Signature::from_rsv(&r, &s, v)) @@ -289,7 +289,7 @@ impl Manager { let mut chunk: [u8; HID_PACKET_SIZE] = [0; HID_PACKET_SIZE]; let chunk_size = handle.read(&mut chunk)?; trace!("read {:?}", &chunk[..]); - if chunk_size < 5 || chunk[1] != 0x01 || chunk[1] != 0x01 || chunk[2] != APDU_TAG { + if chunk_size < 5 || chunk[0] != 0x01 || chunk[1] != 0x01 || chunk[2] != APDU_TAG { return Err(Error::Protocol("Unexpected chunk header")); } let seq = (chunk[3] as usize) << 8 | (chunk[4] as usize); From 062c55d51b2fffbe6eed08dbfb659700511b993a Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 16 Feb 2017 17:06:15 +0000 Subject: [PATCH 21/51] [ci skip] js-precompiled 20170216-165434 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b21ca7ef..35babcec3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1617,7 +1617,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#e1d75910126030f2d9980b3301297baf107ad2fe" +source = "git+https://github.com/ethcore/js-precompiled.git#e2c249df5ee41a8e67356b81fdbbc7237120837e" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 33a560b05..c52402912 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.90", + "version": "0.3.91", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From da696e4a1f7282e3c5a6e228e3121f3c41e5ba06 Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Thu, 16 Feb 2017 18:39:13 +0100 Subject: [PATCH 22/51] Added support for contract code by hash. This is done by requests sending CID with raw binary codec (0x55). Note: this functionality is exactly the same as fetching state-trie due to how db internals work in Parity atm. --- ipfs/src/error.rs | 2 ++ ipfs/src/handler.rs | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/ipfs/src/error.rs b/ipfs/src/error.rs index 774763786..f379f254b 100644 --- a/ipfs/src/error.rs +++ b/ipfs/src/error.rs @@ -36,6 +36,7 @@ pub enum Error { BlockNotFound, TransactionNotFound, StateRootNotFound, + ContractNotFound, } /// Convert Error into Out, handy when switching from Rust's Result-based @@ -51,6 +52,7 @@ impl From for Out { BlockNotFound => Out::NotFound("Block not found"), TransactionNotFound => Out::NotFound("Transaction not found"), StateRootNotFound => Out::NotFound("State root not found"), + ContractNotFound => Out::NotFound("Contract not found"), } } } diff --git a/ipfs/src/handler.rs b/ipfs/src/handler.rs index 5197e8a1a..543792fa5 100644 --- a/ipfs/src/handler.rs +++ b/ipfs/src/handler.rs @@ -86,6 +86,7 @@ impl IpfsHandler { Codec::EthereumBlockList => self.block_list(hash), Codec::EthereumTx => self.transaction(hash), Codec::EthereumStateTrie => self.state_trie(hash), + Codec::Raw => self.contract_code(hash), _ => return Err(Error::UnsupportedCid), } } @@ -119,6 +120,13 @@ impl IpfsHandler { Ok(Out::OctetStream(data)) } + + /// Get state trie node by hash and return as raw binary. + fn contract_code(&self, hash: H256) -> Result { + let data = self.client.state_data(&hash).ok_or(Error::ContractNotFound)?; + + Ok(Out::OctetStream(data)) + } } /// Get a query parameter's value by name. @@ -192,6 +200,16 @@ mod tests { assert_eq!(Err(Error::StateRootNotFound), handler.route_cid(&cid)); } + #[test] + fn cid_route_contract_code() { + let handler = get_mocked_handler(); + + // `raw` with Keccak-256 + let cid = "zb34WAp1Q5fhtLGZ3w3jhnTWaNbVV5ZZvGq4vuJQzERj6Pu3H"; + + assert_eq!(Err(Error::ContractNotFound), handler.route_cid(&cid)); + } + #[test] fn cid_route_invalid_hash() { let handler = get_mocked_handler(); From a9cc9ce1404efa85d70306a30571504a0a2c95e5 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Thu, 16 Feb 2017 22:58:41 +0400 Subject: [PATCH 23/51] docker build `no-cache` [ci-skip] --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 37df0bc05..50816582a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -486,7 +486,7 @@ docker-build: script: - cd docker/hub - docker login -u $Docker_Hub_User -p $Docker_Hub_Pass - - docker build --tag ethcore/parity:$CI_BUILD_REF_NAME . + - docker build --no-cache=true --tag ethcore/parity:$CI_BUILD_REF_NAME . - docker push ethcore/parity:$CI_BUILD_REF_NAME tags: - docker From 513cc6261ad3f9fa9337e6196c4f11c708d3cb82 Mon Sep 17 00:00:00 2001 From: NikVolf Date: Thu, 16 Feb 2017 22:10:29 +0300 Subject: [PATCH 24/51] plug to store --- ethstore/src/dir/disk.rs | 4 ++++ ethstore/src/dir/mod.rs | 3 +++ ethstore/src/ethstore.rs | 20 +++++++++++++++++--- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index c8afcc910..ccc84077b 100755 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -219,6 +219,10 @@ impl KeyDirectory for DiskDirectory where T: KeyFileManager { fn as_vault_provider(&self) -> Option<&VaultKeyDirectoryProvider> { Some(self) } + + fn hash(&self) -> Result { + self.files_hash() + } } impl VaultKeyDirectoryProvider for DiskDirectory where T: KeyFileManager { diff --git a/ethstore/src/dir/mod.rs b/ethstore/src/dir/mod.rs index 6e4326968..11a1a2f23 100755 --- a/ethstore/src/dir/mod.rs +++ b/ethstore/src/dir/mod.rs @@ -16,6 +16,7 @@ use std::path::{PathBuf}; use {SafeAccount, Error}; +use util::{H256, FixedHash}; mod disk; mod geth; @@ -62,6 +63,8 @@ pub trait KeyDirectory: Send + Sync { fn path(&self) -> Option<&PathBuf> { None } /// Return vault provider, if available fn as_vault_provider(&self) -> Option<&VaultKeyDirectoryProvider> { None } + /// Returns hash of the directory content, if supported + fn hash(&self) -> Result { Ok(H256::zero()) } } /// Vaults provider diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index 86b6dce46..eecc361e4 100755 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -27,6 +27,7 @@ use account::SafeAccount; use presale::PresaleWallet; use json::{self, Uuid}; use {import, Error, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation}; +use util::H256; pub struct EthStore { store: EthMultiStore, @@ -230,6 +231,7 @@ pub struct EthMultiStore { // order lock: cache, then vaults cache: RwLock>>, vaults: Mutex>>, + dir_hash: RwLock>, } impl EthMultiStore { @@ -244,11 +246,23 @@ impl EthMultiStore { vaults: Mutex::new(HashMap::new()), iterations: iterations, cache: Default::default(), + dir_hash: Default::default(), }; store.reload_accounts()?; Ok(store) } + fn reload_if_changed(&self) -> Result<(), Error> { + let mut last_dir_hash = self.dir_hash.write(); + let dir_hash = Some(self.dir.hash()?); + if *last_dir_hash == dir_hash { + return Ok(()) + } + self.reload_accounts()?; + *last_dir_hash = dir_hash; + Ok(()) + } + fn reload_accounts(&self) -> Result<(), Error> { let mut cache = self.cache.write(); @@ -284,7 +298,7 @@ impl EthMultiStore { } } - self.reload_accounts()?; + self.reload_if_changed()?; let cache = self.cache.read(); let accounts = cache.get(account).ok_or(Error::InvalidAccount)?; if accounts.is_empty() { @@ -431,7 +445,7 @@ impl SimpleSecretStore for EthMultiStore { } fn account_ref(&self, address: &Address) -> Result { - self.reload_accounts()?; + self.reload_if_changed()?; self.cache.read().keys() .find(|r| &r.address == address) .cloned() @@ -439,7 +453,7 @@ impl SimpleSecretStore for EthMultiStore { } fn accounts(&self) -> Result, Error> { - self.reload_accounts()?; + self.reload_if_changed()?; Ok(self.cache.read().keys().cloned().collect()) } From 444065e2942e0963b5ee8f3b584066b08c843d98 Mon Sep 17 00:00:00 2001 From: NikVolf Date: Thu, 16 Feb 2017 22:53:58 +0300 Subject: [PATCH 25/51] refactor hashing --- ethstore/src/dir/disk.rs | 19 ++++++++++++------- ethstore/src/dir/mod.rs | 3 +-- ethstore/src/ethstore.rs | 5 ++--- util/src/sha3.rs | 29 ----------------------------- 4 files changed, 15 insertions(+), 41 deletions(-) diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index ccc84077b..f8d8345e7 100755 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -22,7 +22,6 @@ use {json, SafeAccount, Error}; use json::Uuid; use super::{KeyDirectory, VaultKeyDirectory, VaultKeyDirectoryProvider, VaultKey}; use super::vault::{VAULT_FILE_NAME, VaultDiskDirectory}; -use util::H256; const IGNORED_FILES: &'static [&'static str] = &[ "thumbs.db", @@ -110,11 +109,17 @@ impl DiskDirectory where T: KeyFileManager { ) } - pub fn files_hash(&self) -> Result { - use util::Hashable; + pub fn files_hash(&self) -> Result { + use std::collections::hash_map::DefaultHasher; + use std::hash::Hasher; + + let mut hasher = DefaultHasher::new(); let files = self.files()?; - let file_strs: Vec<&str> = files.iter().map(|fp| fp.to_str().unwrap_or("")).collect(); - Ok(file_strs.sha3()) + for file in files { + hasher.write(file.to_str().unwrap_or("").as_bytes()) + } + + Ok(hasher.finish()) } /// all accounts found in keys directory @@ -220,7 +225,7 @@ impl KeyDirectory for DiskDirectory where T: KeyFileManager { Some(self) } - fn hash(&self) -> Result { + fn hash(&self) -> Result { self.files_hash() } } @@ -360,7 +365,7 @@ mod test { let hash = directory.files_hash().expect("Files hash should be calculated ok"); assert_eq!( hash, - "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470".parse().unwrap() + 15130871412783076140 ); let keypair = Random.generate().unwrap(); diff --git a/ethstore/src/dir/mod.rs b/ethstore/src/dir/mod.rs index 11a1a2f23..5fbf5fb8b 100755 --- a/ethstore/src/dir/mod.rs +++ b/ethstore/src/dir/mod.rs @@ -16,7 +16,6 @@ use std::path::{PathBuf}; use {SafeAccount, Error}; -use util::{H256, FixedHash}; mod disk; mod geth; @@ -64,7 +63,7 @@ pub trait KeyDirectory: Send + Sync { /// Return vault provider, if available fn as_vault_provider(&self) -> Option<&VaultKeyDirectoryProvider> { None } /// Returns hash of the directory content, if supported - fn hash(&self) -> Result { Ok(H256::zero()) } + fn hash(&self) -> Result { Ok(0u64) } } /// Vaults provider diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index eecc361e4..401630ba8 100755 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -27,7 +27,6 @@ use account::SafeAccount; use presale::PresaleWallet; use json::{self, Uuid}; use {import, Error, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation}; -use util::H256; pub struct EthStore { store: EthMultiStore, @@ -231,7 +230,7 @@ pub struct EthMultiStore { // order lock: cache, then vaults cache: RwLock>>, vaults: Mutex>>, - dir_hash: RwLock>, + dir_hash: Mutex>, } impl EthMultiStore { @@ -253,7 +252,7 @@ impl EthMultiStore { } fn reload_if_changed(&self) -> Result<(), Error> { - let mut last_dir_hash = self.dir_hash.write(); + let mut last_dir_hash = self.dir_hash.lock(); let dir_hash = Some(self.dir.hash()?); if *last_dir_hash == dir_hash { return Ok(()) diff --git a/util/src/sha3.rs b/util/src/sha3.rs index 4d3502313..45d6a34d5 100644 --- a/util/src/sha3.rs +++ b/util/src/sha3.rs @@ -68,24 +68,6 @@ impl Hashable for T where T: AsRef<[u8]> { } } -impl Hashable for [T] where T: Hashable { - fn sha3(&self) -> H256 { - use std::ops::BitXor; - - let mut sha3 = SHA3_EMPTY; - for t in self.iter() { - sha3 = sha3.bitxor(t.sha3()); - }; - - sha3 - } - // todo: optimize? - fn sha3_into(&self, dest: &mut [u8]) { - let sha3 = self.sha3(); - dest.copy_from_slice(&*sha3); - } -} - /// Calculate SHA3 of given stream. pub fn sha3(r: &mut io::BufRead) -> Result { let mut output = [0u8; 32]; @@ -138,15 +120,4 @@ mod tests { // then assert_eq!(format!("{:?}", hash), "68371d7e884c168ae2022c82bd837d51837718a7f7dfb7aa3f753074a35e1d87"); } - - #[test] - fn should_sha3_strs() { - let strs = vec!["abc".to_owned(), "gdc".to_owned()]; - let hash = strs.sha3(); - assert_eq!(hash, "b8f24d705171c55892a34c7b863c258f4d47e6864f7a7da45f84155597a3b338".parse().unwrap()); - - let strs = vec!["abc".to_owned(), "gdc_".to_owned()]; - let hash = strs.sha3(); - assert_eq!(hash, "41bd661b8e02faccad55cdbb28db974dd5c9ae41825b89907fcf25db793b8b09".parse().unwrap()); - } } From 92d8edc1a6b06886cb0ed8eee274de158cfa772f Mon Sep 17 00:00:00 2001 From: NikVolf Date: Thu, 16 Feb 2017 23:04:39 +0300 Subject: [PATCH 26/51] unique_repr, no default impl --- ethstore/src/dir/disk.rs | 2 +- ethstore/src/dir/geth.rs | 4 ++++ ethstore/src/dir/memory.rs | 7 +++++++ ethstore/src/dir/mod.rs | 4 ++-- ethstore/src/dir/parity.rs | 4 ++++ ethstore/src/ethstore.rs | 2 +- ethstore/tests/util/transient_dir.rs | 4 ++++ 7 files changed, 23 insertions(+), 4 deletions(-) diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index f8d8345e7..afca0955d 100755 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -225,7 +225,7 @@ impl KeyDirectory for DiskDirectory where T: KeyFileManager { Some(self) } - fn hash(&self) -> Result { + fn unique_repr(&self) -> Result { self.files_hash() } } diff --git a/ethstore/src/dir/geth.rs b/ethstore/src/dir/geth.rs index 1058e433f..0dfa2e6b2 100755 --- a/ethstore/src/dir/geth.rs +++ b/ethstore/src/dir/geth.rs @@ -95,4 +95,8 @@ impl KeyDirectory for GethDirectory { fn remove(&self, account: &SafeAccount) -> Result<(), Error> { self.dir.remove(account) } + + fn unique_repr(&self) -> Result { + self.dir.unique_repr() + } } diff --git a/ethstore/src/dir/memory.rs b/ethstore/src/dir/memory.rs index 941795efc..87d12794e 100644 --- a/ethstore/src/dir/memory.rs +++ b/ethstore/src/dir/memory.rs @@ -63,5 +63,12 @@ impl KeyDirectory for MemoryDirectory { } Ok(()) } + + fn unique_repr(&self) -> Result { + let mut val = 0u64; + let accounts = self.accounts.read(); + for acc in accounts.keys() { val = val ^ ::util::FixedHash::low_u64(acc) } + Ok(val) + } } diff --git a/ethstore/src/dir/mod.rs b/ethstore/src/dir/mod.rs index 5fbf5fb8b..83e978707 100755 --- a/ethstore/src/dir/mod.rs +++ b/ethstore/src/dir/mod.rs @@ -62,8 +62,8 @@ pub trait KeyDirectory: Send + Sync { fn path(&self) -> Option<&PathBuf> { None } /// Return vault provider, if available fn as_vault_provider(&self) -> Option<&VaultKeyDirectoryProvider> { None } - /// Returns hash of the directory content, if supported - fn hash(&self) -> Result { Ok(0u64) } + /// Unique representation of directory account collection + fn unique_repr(&self) -> Result; } /// Vaults provider diff --git a/ethstore/src/dir/parity.rs b/ethstore/src/dir/parity.rs index 198e50165..df03260d3 100755 --- a/ethstore/src/dir/parity.rs +++ b/ethstore/src/dir/parity.rs @@ -74,4 +74,8 @@ impl KeyDirectory for ParityDirectory { fn remove(&self, account: &SafeAccount) -> Result<(), Error> { self.dir.remove(account) } + + fn unique_repr(&self) -> Result { + self.dir.unique_repr() + } } diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index 401630ba8..3cdf0e643 100755 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -253,7 +253,7 @@ impl EthMultiStore { fn reload_if_changed(&self) -> Result<(), Error> { let mut last_dir_hash = self.dir_hash.lock(); - let dir_hash = Some(self.dir.hash()?); + let dir_hash = Some(self.dir.unique_repr()?); if *last_dir_hash == dir_hash { return Ok(()) } diff --git a/ethstore/tests/util/transient_dir.rs b/ethstore/tests/util/transient_dir.rs index 150ae8108..45e2aab09 100755 --- a/ethstore/tests/util/transient_dir.rs +++ b/ethstore/tests/util/transient_dir.rs @@ -74,4 +74,8 @@ impl KeyDirectory for TransientDir { fn remove(&self, account: &SafeAccount) -> Result<(), Error> { self.dir.remove(account) } + + fn unique_repr(&self) -> Result { + self.dir.unique_repr() + } } From 164f1cef81954a950c52c7f55914302be9e031ab Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Fri, 17 Feb 2017 03:56:09 +0400 Subject: [PATCH 27/51] remove `git pul` from hub Docker [ci-skip] --- docker/hub/Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/docker/hub/Dockerfile b/docker/hub/Dockerfile index 7de63691b..ec19f5e0d 100644 --- a/docker/hub/Dockerfile +++ b/docker/hub/Dockerfile @@ -58,7 +58,6 @@ g++ -v RUN git clone https://github.com/ethcore/parity && \ cd parity && \ git checkout $CI_BUILD_REF_NAME && \ - git pull && \ cargo build --release --features final && \ ls /build/parity/target/release/parity && \ strip /build/parity/target/release/parity From aca808b021e1a51e43417fbafd9279cd73aed793 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Fri, 17 Feb 2017 13:48:37 +0400 Subject: [PATCH 28/51] update Dockerfile ENTRYPOINT ["/parity/parity"] --- docker/hub/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/hub/Dockerfile b/docker/hub/Dockerfile index ec19f5e0d..211749279 100644 --- a/docker/hub/Dockerfile +++ b/docker/hub/Dockerfile @@ -67,4 +67,4 @@ RUN file /build/parity/target/release/parity&&mkdir -p /parity&&cp /build/parity RUN rm -rf /root/.cargo&&rm -rf /root/.multirust&&rm -rf /root/.rustup&&rm -rf /build EXPOSE 8080 8545 8180 -ENTRYPOINT ["/parity"] +ENTRYPOINT ["/parity/parity"] From 54c48d14eceeae96e5120a1dda42161972a0be3f Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 17 Feb 2017 12:00:33 +0100 Subject: [PATCH 29/51] Deprecate eth_compile* RPCs (#4577) * Deprecate eth_compile* RPCs * Add deprecation doc comments --- rpc/src/v1/helpers/errors.rs | 18 ++++++------- rpc/src/v1/impls/eth.rs | 48 +++++----------------------------- rpc/src/v1/tests/mocked/eth.rs | 8 +++--- rpc/src/v1/traits/eth.rs | 4 +++ 4 files changed, 23 insertions(+), 55 deletions(-) diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index b58999f84..93d23b1aa 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -46,10 +46,10 @@ mod codes { pub const REQUEST_REJECTED: i64 = -32040; pub const REQUEST_REJECTED_LIMIT: i64 = -32041; pub const REQUEST_NOT_FOUND: i64 = -32042; - pub const COMPILATION_ERROR: i64 = -32050; pub const ENCRYPTION_ERROR: i64 = -32055; pub const FETCH_ERROR: i64 = -32060; pub const NO_LIGHT_PEERS: i64 = -32065; + pub const DEPRECATED: i64 = -32070; } pub fn unimplemented(details: Option) -> Error { @@ -92,14 +92,6 @@ pub fn account(error: &str, details: T) -> Error { } } -pub fn compilation(error: T) -> Error { - Error { - code: ErrorCode::ServerError(codes::COMPILATION_ERROR), - message: "Error while compiling code.".into(), - data: Some(Value::String(format!("{:?}", error))), - } -} - pub fn internal(error: &str, data: T) -> Error { Error { code: ErrorCode::InternalError, @@ -317,3 +309,11 @@ pub fn no_light_peers() -> Error { data: None, } } + +pub fn deprecated>>(message: T) -> Error { + Error { + code: ErrorCode::ServerError(codes::DEPRECATED), + message: "Method deprecated".into(), + data: message.into().map(Value::String), + } +} diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index d763db836..9ad8196fe 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -16,8 +16,6 @@ //! Eth rpc implementation. -use std::io::{Write}; -use std::process::{Command, Stdio}; use std::thread; use std::time::{Instant, Duration}; use std::sync::{Arc, Weak}; @@ -27,7 +25,7 @@ use rlp::{self, UntrustedRlp, View}; use time::get_time; use util::{H160, H256, Address, FixedHash, U256, H64, Uint}; use util::sha3::Hashable; -use util::{FromHex, Mutex}; +use util::Mutex; use ethash::SeedHashCompute; use ethcore::account_provider::{AccountProvider, DappId}; @@ -258,12 +256,6 @@ fn check_known(client: &C, number: BlockNumber) -> Result<(), Error> where C: const MAX_QUEUE_SIZE_TO_MINE_ON: usize = 4; // because uncles go back 6. -#[cfg(windows)] -static SOLC: &'static str = "solc.exe"; - -#[cfg(not(windows))] -static SOLC: &'static str = "solc"; - impl Eth for EthClient where C: MiningBlockChainClient + 'static, SN: SnapshotService + 'static, @@ -509,12 +501,7 @@ impl Eth for EthClient where } fn compilers(&self) -> Result, Error> { - let mut compilers = vec![]; - if Command::new(SOLC).output().is_ok() { - compilers.push("solidity".to_owned()) - } - - Ok(compilers) + Err(errors::deprecated("Compilation functionality is deprecated.".to_string())) } fn logs(&self, filter: Filter) -> Result, Error> { @@ -642,37 +629,14 @@ impl Eth for EthClient where } fn compile_lll(&self, _: String) -> Result { - rpc_unimplemented!() + Err(errors::deprecated("Compilation of LLL via RPC is deprecated".to_string())) } fn compile_serpent(&self, _: String) -> Result { - rpc_unimplemented!() + Err(errors::deprecated("Compilation of Serpent via RPC is deprecated".to_string())) } - fn compile_solidity(&self, code: String) -> Result { - let maybe_child = Command::new(SOLC) - .arg("--bin") - .arg("--optimize") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::null()) - .spawn(); - - maybe_child - .map_err(errors::compilation) - .and_then(|mut child| { - child.stdin.as_mut() - .expect("we called child.stdin(Stdio::piped()) before spawn; qed") - .write_all(code.as_bytes()) - .map_err(errors::compilation)?; - let output = child.wait_with_output().map_err(errors::compilation)?; - - let s = String::from_utf8_lossy(&output.stdout); - if let Some(hex) = s.lines().skip_while(|ref l| !l.contains("Binary")).skip(1).next() { - Ok(Bytes::new(hex.from_hex().unwrap_or(vec![]))) - } else { - Err(errors::compilation("Unexpected output.")) - } - }) + fn compile_solidity(&self, _: String) -> Result { + Err(errors::deprecated("Compilation of Solidity via RPC is deprecated".to_string())) } } diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index 3b668b030..40ae8c38c 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -1020,7 +1020,7 @@ fn rpc_eth_transaction_receipt_null() { #[test] fn rpc_eth_compilers() { let request = r#"{"jsonrpc": "2.0", "method": "eth_getCompilers", "params": [], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","result":[],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","error":{"code":-32070,"message":"Method deprecated","data":"Compilation functionality is deprecated."},"id":1}"#; assert_eq!(EthTester::default().io.handle_request_sync(request), Some(response.to_owned())); } @@ -1029,7 +1029,7 @@ fn rpc_eth_compilers() { #[test] fn rpc_eth_compile_lll() { let request = r#"{"jsonrpc": "2.0", "method": "eth_compileLLL", "params": [], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","error":{"code":-32603,"message":"Internal error","data":null},"id":1}"#; + let response = r#"{"jsonrpc":"2.0","error":{"code":-32070,"message":"Method deprecated","data":"Compilation of LLL via RPC is deprecated"},"id":1}"#; assert_eq!(EthTester::default().io.handle_request_sync(request), Some(response.to_owned())); } @@ -1038,7 +1038,7 @@ fn rpc_eth_compile_lll() { #[test] fn rpc_eth_compile_solidity() { let request = r#"{"jsonrpc": "2.0", "method": "eth_compileSolidity", "params": [], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","error":{"code":-32603,"message":"Internal error","data":null},"id":1}"#; + let response = r#"{"jsonrpc":"2.0","error":{"code":-32070,"message":"Method deprecated","data":"Compilation of Solidity via RPC is deprecated"},"id":1}"#; assert_eq!(EthTester::default().io.handle_request_sync(request), Some(response.to_owned())); } @@ -1047,7 +1047,7 @@ fn rpc_eth_compile_solidity() { #[test] fn rpc_eth_compile_serpent() { let request = r#"{"jsonrpc": "2.0", "method": "eth_compileSerpent", "params": [], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","error":{"code":-32603,"message":"Internal error","data":null},"id":1}"#; + let response = r#"{"jsonrpc":"2.0","error":{"code":-32070,"message":"Method deprecated","data":"Compilation of Serpent via RPC is deprecated"},"id":1}"#; assert_eq!(EthTester::default().io.handle_request_sync(request), Some(response.to_owned())); } diff --git a/rpc/src/v1/traits/eth.rs b/rpc/src/v1/traits/eth.rs index a50188bf0..eaf608c60 100644 --- a/rpc/src/v1/traits/eth.rs +++ b/rpc/src/v1/traits/eth.rs @@ -142,18 +142,22 @@ build_rpc_trait! { fn uncle_by_block_number_and_index(&self, BlockNumber, Index) -> Result, Error>; /// Returns available compilers. + /// @deprecated #[rpc(name = "eth_getCompilers")] fn compilers(&self) -> Result, Error>; /// Compiles lll code. + /// @deprecated #[rpc(name = "eth_compileLLL")] fn compile_lll(&self, String) -> Result; /// Compiles solidity. + /// @deprecated #[rpc(name = "eth_compileSolidity")] fn compile_solidity(&self, String) -> Result; /// Compiles serpent. + /// @deprecated #[rpc(name = "eth_compileSerpent")] fn compile_serpent(&self, String) -> Result; From c2540dc2153857aa84140dc1ee249f4787941762 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Fri, 17 Feb 2017 12:20:25 +0100 Subject: [PATCH 30/51] Check for matching session before deregistering (#4563) --- util/network/src/connection.rs | 5 ++++- util/network/src/error.rs | 3 +++ util/network/src/host.rs | 11 ++++++++--- util/network/src/session.rs | 4 ++-- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/util/network/src/connection.rs b/util/network/src/connection.rs index 5cc31d6bd..c54c951fa 100644 --- a/util/network/src/connection.rs +++ b/util/network/src/connection.rs @@ -354,7 +354,10 @@ impl EncryptedConnection { /// Send a packet pub fn send_packet(&mut self, io: &IoContext, payload: &[u8]) -> Result<(), NetworkError> where Message: Send + Clone + Sync + 'static { let mut header = RlpStream::new(); - let len = payload.len() as usize; + let len = payload.len(); + if len >= (1 << 24) { + return Err(NetworkError::OversizedPacket); + } header.append_raw(&[(len >> 16) as u8, (len >> 8) as u8, len as u8], 1); header.append_raw(&[0xc2u8, 0x80u8, 0x80u8], 1); //TODO: ger rid of vectors here diff --git a/util/network/src/error.rs b/util/network/src/error.rs index afb2e8ba8..11cf8fdf0 100644 --- a/util/network/src/error.rs +++ b/util/network/src/error.rs @@ -106,6 +106,8 @@ pub enum NetworkError { AddressResolve(Option<::std::io::Error>), /// Error concerning the Rust standard library's IO subsystem. StdIo(::std::io::Error), + /// Packet size is over the protocol limit. + OversizedPacket, } impl fmt::Display for NetworkError { @@ -124,6 +126,7 @@ impl fmt::Display for NetworkError { AddressResolve(_) => "Failed to resolve network address.".into(), StdIo(ref err) => format!("{}", err), Util(ref err) => format!("{}", err), + OversizedPacket => "Packet is too large".into(), }; f.write_fmt(format_args!("Network error ({})", msg)) diff --git a/util/network/src/host.rs b/util/network/src/host.rs index c3f5ab8cf..b64301369 100644 --- a/util/network/src/host.rs +++ b/util/network/src/host.rs @@ -844,7 +844,8 @@ impl Host { // only proceed if the connecting peer is reserved. if !self.reserved_nodes.read().contains(&id) { s.disconnect(io, DisconnectReason::TooManyPeers); - return; + kill = true; + break; } } ready_id = Some(id); @@ -895,6 +896,7 @@ impl Host { if duplicate { trace!(target: "network", "Rejected duplicate connection: {}", token); session.lock().disconnect(io, DisconnectReason::DuplicatePeer); + self.kill_connection(token, io, false); return; } for p in ready_data { @@ -1159,8 +1161,11 @@ impl IoHandler for Host { FIRST_SESSION ... LAST_SESSION => { let mut connections = self.sessions.write(); if let Some(connection) = connections.get(stream).cloned() { - connection.lock().deregister_socket(event_loop).expect("Error deregistering socket"); - connections.remove(stream); + let c = connection.lock(); + if c.expired() { // make sure it is the same connection that the event was generated for + c.deregister_socket(event_loop).expect("Error deregistering socket"); + connections.remove(stream); + } } } DISCOVERY => (), diff --git a/util/network/src/session.rs b/util/network/src/session.rs index 4c4001015..dc4a5464c 100644 --- a/util/network/src/session.rs +++ b/util/network/src/session.rs @@ -241,7 +241,7 @@ impl Session { /// Check if this session is expired. pub fn expired(&self) -> bool { match self.state { - State::Handshake(ref h) => h.expired(), + State::Handshake(ref h) => self.expired || h.expired(), _ => self.expired, } } @@ -407,7 +407,7 @@ impl Session { let rlp = UntrustedRlp::new(&packet.data[1..]); let reason: u8 = rlp.val_at(0)?; if self.had_hello { - debug!("Disconnected: {}: {:?}", self.token(), DisconnectReason::from_u8(reason)); + debug!(target:"network", "Disconnected: {}: {:?}", self.token(), DisconnectReason::from_u8(reason)); } Err(From::from(NetworkError::Disconnect(DisconnectReason::from_u8(reason)))) } From b35e852cb6fa89f6500394f859b794fd56ca1ad0 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Fri, 17 Feb 2017 15:46:43 +0400 Subject: [PATCH 31/51] cargo fix --- .gitlab-ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 50816582a..23840c0c7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,7 +22,9 @@ linux-stable: - triggers script: - cargo build -j $(nproc) --release --features final $CARGOFLAGS - - cargo build -j $(nproc) --release -p evmbin ethstore ethkey + - cargo build -j $(nproc) --release -p evmbin + - cargo build -j $(nproc) --release -p ethstore + - cargo build -j $(nproc) --release -p ethkey - strip target/release/parity - strip target/release/evmbin - strip target/release/ethstore From a3bbdce61324c0cdeabd35669c2fae42534da03a Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Fri, 17 Feb 2017 19:03:40 +0400 Subject: [PATCH 32/51] update Dockerfile for hub refactoring + small fix --- docker/hub/Dockerfile | 69 ++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/docker/hub/Dockerfile b/docker/hub/Dockerfile index 211749279..8973ed63f 100644 --- a/docker/hub/Dockerfile +++ b/docker/hub/Dockerfile @@ -1,4 +1,5 @@ FROM ubuntu:14.04 +MAINTAINER Parity Technologies WORKDIR /build # install tools and dependencies RUN apt-get update && \ @@ -25,46 +26,54 @@ RUN apt-get update && \ # evmjit dependencies zlib1g-dev \ libedit-dev \ - libudev-dev - -# cmake and llvm ppas. then update ppas -RUN add-apt-repository -y "ppa:george-edison55/cmake-3.x" && \ + libudev-dev &&\ +# cmake and llvm ppa's. then update ppa's + add-apt-repository -y "ppa:george-edison55/cmake-3.x" && \ add-apt-repository "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.7 main" && \ apt-get update && \ - apt-get install -y --force-yes cmake llvm-3.7-dev - + apt-get install -y --force-yes cmake llvm-3.7-dev && \ # install evmjit -RUN git clone https://github.com/debris/evmjit && \ + git clone https://github.com/debris/evmjit && \ cd evmjit && \ mkdir build && cd build && \ - cmake .. && make && make install && cd - + cmake .. && make && make install && cd && \ # install rustup -RUN curl https://sh.rustup.rs -sSf | sh -s -- -y - + curl https://sh.rustup.rs -sSf | sh -s -- -y && \ # rustup directory -ENV PATH /root/.cargo/bin:$PATH - + PATH=/root/.cargo/bin:$PATH && \ # show backtraces -ENV RUST_BACKTRACE 1 - -# show tools -RUN rustc -vV && \ -cargo -V && \ -gcc -v &&\ -g++ -v - + RUST_BACKTRACE=1 && \ # build parity -RUN git clone https://github.com/ethcore/parity && \ + cd /build&&git clone https://github.com/ethcore/parity && \ cd parity && \ - git checkout $CI_BUILD_REF_NAME && \ - cargo build --release --features final && \ - ls /build/parity/target/release/parity && \ - strip /build/parity/target/release/parity - -RUN file /build/parity/target/release/parity&&mkdir -p /parity&&cp /build/parity/target/release/parity /parity + git pull&& \ + git checkout $CI_BUILD_REF_NAME && \ + cargo build --verbose --release --features final && \ + #ls /build/parity/target/release/parity && \ + strip /build/parity/target/release/parity && \ + file /build/parity/target/release/parity&&mkdir -p /parity&& cp /build/parity/target/release/parity /parity&&\ #cleanup Docker image -RUN rm -rf /root/.cargo&&rm -rf /root/.multirust&&rm -rf /root/.rustup&&rm -rf /build - + rm -rf /root/.cargo&&rm -rf /root/.multirust&&rm -rf /root/.rustup&&rm -rf /build&&\ + apt-get purge -y \ + # make + build-essential \ + # add-apt-repository + software-properties-common \ + make \ + curl \ + wget \ + git \ + g++ \ + gcc \ + binutils \ + file \ + pkg-config \ + dpkg-dev \ + # evmjit dependencies + zlib1g-dev \ + libedit-dev \ + cmake llvm-3.7-dev&&\ + rm -rf /var/lib/apt/lists/* +# setup ENTRYPOINT EXPOSE 8080 8545 8180 ENTRYPOINT ["/parity/parity"] From 04f3ee90d82b2dc085b1a34bd9340f3b37ca9810 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Fri, 17 Feb 2017 18:11:10 +0100 Subject: [PATCH 33/51] Revert Double Click (#4590) --- js/src/views/ParityBar/parityBar.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/js/src/views/ParityBar/parityBar.js b/js/src/views/ParityBar/parityBar.js index d9d584a34..690a6792d 100644 --- a/js/src/views/ParityBar/parityBar.js +++ b/js/src/views/ParityBar/parityBar.js @@ -345,21 +345,16 @@ class ParityBar extends Component { renderAccount = (account) => { const makeDefaultAccount = () => { + this.toggleAccountsDisplay(); return this.accountStore .makeDefaultAccount(account.address) .then(() => this.accountStore.loadAccounts()); }; - const onDoubleClick = () => { - this.toggleAccountsDisplay(); - makeDefaultAccount(); - }; - return (
Date: Fri, 17 Feb 2017 17:24:47 +0000 Subject: [PATCH 34/51] [ci skip] js-precompiled 20170217-171830 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 957ea8270..308364fca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1663,7 +1663,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#e2c249df5ee41a8e67356b81fdbbc7237120837e" +source = "git+https://github.com/ethcore/js-precompiled.git#b0b680999ceab7a8aa67f6977220070ccdf044e3" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index c52402912..55ca4ad42 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.91", + "version": "0.3.92", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 163e2af5682f66ceb8444b77602f3206e8d49f14 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Fri, 17 Feb 2017 22:11:35 +0400 Subject: [PATCH 35/51] update Dockerfile for hub nightly->latest --- .gitlab-ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 23840c0c7..3747a6896 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -487,9 +487,10 @@ docker-build: - docker info script: - cd docker/hub + - if [ "$CI_BUILD_REF_NAME" == "nightly" ]; then DOCKER_TAG="latest"; else DOCKER_TAG=$CI_BUILD_REF_NAME; fi - docker login -u $Docker_Hub_User -p $Docker_Hub_Pass - - docker build --no-cache=true --tag ethcore/parity:$CI_BUILD_REF_NAME . - - docker push ethcore/parity:$CI_BUILD_REF_NAME + - docker build --no-cache=true --tag ethcore/parity:$DOCKER_TAG . + - docker push ethcore/parity:$DOCKER_TAG tags: - docker test-darwin: From 32023f1ea089fb3dffab4d5cf05311558f092e1a Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Fri, 17 Feb 2017 23:33:14 +0400 Subject: [PATCH 36/51] fix build path to `tools` --- .gitlab-ci.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3747a6896..d79818f1d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -26,16 +26,16 @@ linux-stable: - cargo build -j $(nproc) --release -p ethstore - cargo build -j $(nproc) --release -p ethkey - strip target/release/parity - - strip target/release/evmbin - - strip target/release/ethstore - - strip target/release/ethkey + - strip target/release/parity/evmbin + - strip target/release/parity/ethstore + - strip target/release/parity/ethkey - export SHA3=$(target/release/parity tools hash target/release/parity) - md5sum target/release/parity > parity.md5 - sh scripts/deb-build.sh amd64 - cp target/release/parity deb/usr/bin/parity - - cp target/release/evmbin deb/usr/bin/evmbin - - cp target/release/ethstore deb/usr/bin/ethstore - - cp target/release/ethkey deb/usr/bin/ethkey + - cp target/release/parity/evmbin deb/usr/bin/evmbin + - cp target/release/parity/ethstore deb/usr/bin/ethstore + - cp target/release/parity/ethkey deb/usr/bin/ethkey - export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n") - dpkg-deb -b deb "parity_"$VER"_amd64.deb" - md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5" @@ -55,6 +55,9 @@ linux-stable: artifacts: paths: - target/release/parity + - target/release/parity/evmbin + - target/release/parity/ethstore + - target/release/parity/ethkey name: "stable-x86_64-unknown-linux-gnu_parity" linux-stable-debian: stage: build From e86837b8787604f43d049a87d945063c4e092ed5 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Mon, 20 Feb 2017 15:25:53 +0400 Subject: [PATCH 37/51] target `evmbin` -> `evm` --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d79818f1d..11eb4dfb6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -26,14 +26,14 @@ linux-stable: - cargo build -j $(nproc) --release -p ethstore - cargo build -j $(nproc) --release -p ethkey - strip target/release/parity - - strip target/release/parity/evmbin + - strip target/release/parity/evm - strip target/release/parity/ethstore - strip target/release/parity/ethkey - export SHA3=$(target/release/parity tools hash target/release/parity) - md5sum target/release/parity > parity.md5 - sh scripts/deb-build.sh amd64 - cp target/release/parity deb/usr/bin/parity - - cp target/release/parity/evmbin deb/usr/bin/evmbin + - cp target/release/parity/evm deb/usr/bin/evm - cp target/release/parity/ethstore deb/usr/bin/ethstore - cp target/release/parity/ethkey deb/usr/bin/ethkey - export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n") From 3fc29b9ae4aaef02dbcbf7ece29cb07c78df1d27 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Mon, 20 Feb 2017 13:34:33 +0100 Subject: [PATCH 38/51] Handle invalid ABI retrieved from address_book gracefully (#4606) * Handle invalid ABI gracefully * Also include failed abi in log --- js/src/ui/MethodDecoding/methodDecodingStore.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/js/src/ui/MethodDecoding/methodDecodingStore.js b/js/src/ui/MethodDecoding/methodDecodingStore.js index 379617f34..da23f916c 100644 --- a/js/src/ui/MethodDecoding/methodDecodingStore.js +++ b/js/src/ui/MethodDecoding/methodDecodingStore.js @@ -54,9 +54,19 @@ export default class MethodDecodingStore { } loadFromAbi (_abi, contractAddress) { - const abi = new Abi(_abi); + let abi; - if (contractAddress && abi) { + try { + abi = new Abi(_abi); + } catch (error) { + console.warn('loadFromAbi', error, _abi); + } + + if (!abi) { + return; + } + + if (contractAddress) { this._contractsAbi[contractAddress] = abi; } From 31f5a1d2055ff3c204489feb9fdf49ad60e772bf Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Mon, 20 Feb 2017 12:48:32 +0000 Subject: [PATCH 39/51] [ci skip] js-precompiled 20170220-124210 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 308364fca..9ededba65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1663,7 +1663,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#b0b680999ceab7a8aa67f6977220070ccdf044e3" +source = "git+https://github.com/ethcore/js-precompiled.git#2de3aee113f561b3d8aa00ddcaf82fedf079172c" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 55ca4ad42..4139c6f3f 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.92", + "version": "0.3.93", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 68a25d9e1434406832f9a1c3bdd642ceb2594e90 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Mon, 20 Feb 2017 17:16:11 +0400 Subject: [PATCH 40/51] fix path to `tools` --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 11eb4dfb6..b0e4ad009 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -26,9 +26,9 @@ linux-stable: - cargo build -j $(nproc) --release -p ethstore - cargo build -j $(nproc) --release -p ethkey - strip target/release/parity - - strip target/release/parity/evm - - strip target/release/parity/ethstore - - strip target/release/parity/ethkey + - strip target/release/evm + - strip target/release/ethstore + - strip target/release/ethkey - export SHA3=$(target/release/parity tools hash target/release/parity) - md5sum target/release/parity > parity.md5 - sh scripts/deb-build.sh amd64 From b9665c7cfee0c8c327f3ab140edd4eda8e5daa36 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 20 Feb 2017 18:13:21 +0300 Subject: [PATCH 41/51] Secret store - initial version (#4567) * initial secret store commit * various fixes * license * (sstore, secstore) -> secretstore * marked KeyServer trait as IPC-ready * fixed style * ignore requests with Origin header * fixed tests * fixed Origin header check --- Cargo.lock | 18 +++ Cargo.toml | 2 + parity/blockchain.rs | 4 +- parity/cli/config.full.toml | 6 + parity/cli/config.toml | 3 + parity/cli/mod.rs | 33 +++++- parity/cli/usage.txt | 10 ++ parity/configuration.rs | 32 +++++- parity/dir.rs | 8 +- parity/main.rs | 5 + parity/run.rs | 10 +- parity/secretstore.rs | 105 ++++++++++++++++++ secret_store/Cargo.toml | 25 +++++ secret_store/build.rs | 22 ++++ secret_store/src/acl_storage.rs | 51 +++++++++ secret_store/src/http_listener.rs | 176 ++++++++++++++++++++++++++++++ secret_store/src/key_server.rs | 124 +++++++++++++++++++++ secret_store/src/key_storage.rs | 115 +++++++++++++++++++ secret_store/src/lib.rs | 52 +++++++++ secret_store/src/traits.rs | 24 ++++ secret_store/src/types/all.rs | 77 +++++++++++++ secret_store/src/types/mod.rs | 20 ++++ secret_store/src/types/mod.rs.in | 17 +++ 23 files changed, 931 insertions(+), 8 deletions(-) create mode 100644 parity/secretstore.rs create mode 100644 secret_store/Cargo.toml create mode 100644 secret_store/build.rs create mode 100644 secret_store/src/acl_storage.rs create mode 100644 secret_store/src/http_listener.rs create mode 100644 secret_store/src/key_server.rs create mode 100644 secret_store/src/key_storage.rs create mode 100644 secret_store/src/lib.rs create mode 100644 secret_store/src/traits.rs create mode 100644 secret_store/src/types/all.rs create mode 100644 secret_store/src/types/mod.rs create mode 100644 secret_store/src/types/mod.rs.in diff --git a/Cargo.lock b/Cargo.lock index 9ededba65..44989c92c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,7 @@ dependencies = [ "ethcore-light 1.6.0", "ethcore-logger 1.6.0", "ethcore-rpc 1.6.0", + "ethcore-secretstore 1.0.0", "ethcore-signer 1.6.0", "ethcore-stratum 1.6.0", "ethcore-util 1.6.0", @@ -635,6 +636,23 @@ dependencies = [ "transient-hashmap 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ethcore-secretstore" +version = "1.0.0" +dependencies = [ + "ethcore-devtools 1.6.0", + "ethcore-ipc 1.6.0", + "ethcore-ipc-codegen 1.6.0", + "ethcore-ipc-nano 1.6.0", + "ethcore-util 1.6.0", + "ethcrypto 0.1.0", + "ethkey 0.2.0", + "hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ethcore-signer" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index 45179ef10..3b6bd7e97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ parity-updater = { path = "updater" } parity-reactor = { path = "util/reactor" } ethcore-dapps = { path = "dapps", optional = true } clippy = { version = "0.0.103", optional = true} +ethcore-secretstore = { path = "secret_store", optional = true } [dev-dependencies] ethcore-ipc-tests = { path = "ipc/tests" } @@ -83,6 +84,7 @@ evm-debug = ["ethcore/evm-debug"] evm-debug-tests = ["ethcore/evm-debug-tests"] slow-blocks = ["ethcore/slow-blocks"] final = ["ethcore-util/final"] +secretstore = ["ethcore-secretstore"] [[bin]] path = "parity/main.rs" diff --git a/parity/blockchain.rs b/parity/blockchain.rs index 59cfd0a59..1eeb3d71e 100644 --- a/parity/blockchain.rs +++ b/parity/blockchain.rs @@ -185,7 +185,7 @@ fn execute_import(cmd: ImportBlockchain) -> Result<(), String> { execute_upgrades(&cmd.dirs.base, &db_dirs, algorithm, cmd.compaction.compaction_profile(db_dirs.db_root_path().as_path()))?; // create dirs used by parity - cmd.dirs.create_dirs(false, false)?; + cmd.dirs.create_dirs(false, false, false)?; // prepare client config let mut client_config = to_client_config( @@ -356,7 +356,7 @@ fn start_client( execute_upgrades(&dirs.base, &db_dirs, algorithm, compaction.compaction_profile(db_dirs.db_root_path().as_path()))?; // create dirs used by parity - dirs.create_dirs(false, false)?; + dirs.create_dirs(false, false, false)?; // prepare client config let client_config = to_client_config( diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index 0983bf792..a8b3c4fc1 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -67,6 +67,12 @@ path = "$HOME/.parity/dapps" user = "test_user" pass = "test_pass" +[secretstore] +disable = false +port = 8082 +interface = "local" +path = "$HOME/.parity/secretstore" + [ipfs] enable = false port = 5001 diff --git a/parity/cli/config.toml b/parity/cli/config.toml index 288f3b2ed..9b356a811 100644 --- a/parity/cli/config.toml +++ b/parity/cli/config.toml @@ -38,6 +38,9 @@ port = 8080 user = "username" pass = "password" +[secretstore] +port = 8082 + [ipfs] enable = false port = 5001 diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index a416aa4ce..f105c9c12 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -189,6 +189,16 @@ usage! { or |c: &Config| otry!(c.dapps).pass.clone().map(Some), flag_dapps_apis_all: bool = false, or |_| None, + // Secret Store + flag_no_secretstore: bool = false, + or |c: &Config| otry!(c.secretstore).disable.clone(), + flag_secretstore_port: u16 = 8082u16, + or |c: &Config| otry!(c.secretstore).port.clone(), + flag_secretstore_interface: String = "local", + or |c: &Config| otry!(c.secretstore).interface.clone(), + flag_secretstore_path: String = "$BASE/secretstore", + or |c: &Config| otry!(c.secretstore).path.clone(), + // IPFS flag_ipfs_api: bool = false, or |c: &Config| otry!(c.ipfs).enable.clone(), @@ -327,6 +337,7 @@ struct Config { rpc: Option, ipc: Option, dapps: Option, + secretstore: Option, ipfs: Option, mining: Option, footprint: Option, @@ -416,6 +427,14 @@ struct Dapps { pass: Option, } +#[derive(Default, Debug, PartialEq, RustcDecodable)] +struct SecretStore { + disable: Option, + port: Option, + interface: Option, + path: Option, +} + #[derive(Default, Debug, PartialEq, RustcDecodable)] struct Ipfs { enable: Option, @@ -495,7 +514,8 @@ struct Misc { mod tests { use super::{ Args, ArgsError, - Config, Operating, Account, Ui, Network, Rpc, Ipc, Dapps, Ipfs, Mining, Footprint, Snapshots, VM, Misc + Config, Operating, Account, Ui, Network, Rpc, Ipc, Dapps, Ipfs, Mining, Footprint, + Snapshots, VM, Misc, SecretStore, }; use toml; @@ -650,6 +670,11 @@ mod tests { flag_dapps_pass: Some("test_pass".into()), flag_dapps_apis_all: false, + flag_no_secretstore: false, + flag_secretstore_port: 8082u16, + flag_secretstore_interface: "local".into(), + flag_secretstore_path: "$HOME/.parity/secretstore".into(), + // IPFS flag_ipfs_api: false, flag_ipfs_api_port: 5001u16, @@ -839,6 +864,12 @@ mod tests { user: Some("username".into()), pass: Some("password".into()) }), + secretstore: Some(SecretStore { + disable: None, + port: Some(8082), + interface: None, + path: None, + }), ipfs: Some(Ipfs { enable: Some(false), port: Some(5001) diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index fd19a8004..430154752 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -179,6 +179,16 @@ API and Console Options: --ipfs-api-port PORT Configure on which port the IPFS HTTP API should listen. (default: {flag_ipfs_api_port}) +Secret Store Options: + --no-secretstore Disable Secret Store functionality. (default: {flag_no_secretstore}) + --secretstore-port PORT Specify the port portion for Secret Store Key Server + (default: {flag_secretstore_port}). + --secretstore-interface IP Specify the hostname portion for Secret Store Key Server, IP + should be an interface's IP address, or local + (default: {flag_secretstore_interface}). + --secretstore-path PATH Specify directory where Secret Store should save its data. + (default: {flag_secretstore_path}) + Sealing/Mining Options: --author ADDRESS Specify the block author (aka "coinbase") address for sending block rewards from sealed blocks. diff --git a/parity/configuration.rs b/parity/configuration.rs index 34fea453d..dd1b8546c 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -39,6 +39,7 @@ use dir::{self, Directories, default_hypervisor_path, default_local_path, defaul use dapps::Configuration as DappsConfiguration; use ipfs::Configuration as IpfsConfiguration; use signer::{Configuration as SignerConfiguration}; +use secretstore::Configuration as SecretStoreConfiguration; use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack}; use run::RunCmd; use blockchain::{BlockchainCmd, ImportBlockchain, ExportBlockchain, KillBlockchain, ExportState, DataFormat}; @@ -121,6 +122,7 @@ impl Configuration { let dapps_conf = self.dapps_config(); let ipfs_conf = self.ipfs_config(); let signer_conf = self.signer_config(); + let secretstore_conf = self.secretstore_config(); let format = self.format()?; let cmd = if self.args.flag_version { @@ -346,6 +348,7 @@ impl Configuration { dapps_conf: dapps_conf, ipfs_conf: ipfs_conf, signer_conf: signer_conf, + secretstore_conf: secretstore_conf, dapp: self.dapp_to_open()?, ui: self.args.cmd_ui, name: self.args.flag_identity, @@ -542,6 +545,15 @@ impl Configuration { } } + fn secretstore_config(&self) -> SecretStoreConfiguration { + SecretStoreConfiguration { + enabled: self.secretstore_enabled(), + interface: self.secretstore_interface(), + port: self.args.flag_secretstore_port, + data_path: self.directories().secretstore, + } + } + fn ipfs_config(&self) -> IpfsConfiguration { IpfsConfiguration { enabled: self.args.flag_ipfs_api, @@ -787,6 +799,7 @@ impl Configuration { let db_path = replace_home_for_db(&data_path, &local_path, &base_db_path); let keys_path = replace_home(&data_path, &self.args.flag_keys_path); let dapps_path = replace_home(&data_path, &self.args.flag_dapps_path); + let secretstore_path = replace_home(&data_path, &self.args.flag_secretstore_path); let ui_path = replace_home(&data_path, &self.args.flag_ui_path); if self.args.flag_geth && !cfg!(windows) { @@ -810,6 +823,7 @@ impl Configuration { db: db_path, dapps: dapps_path, signer: ui_path, + secretstore: secretstore_path, } } @@ -851,6 +865,13 @@ impl Configuration { }.into() } + fn secretstore_interface(&self) -> String { + match self.args.flag_secretstore_interface.as_str() { + "local" => "127.0.0.1", + x => x, + }.into() + } + fn stratum_interface(&self) -> String { match self.args.flag_stratum_interface.as_str() { "local" => "127.0.0.1", @@ -863,6 +884,10 @@ impl Configuration { !self.args.flag_dapps_off && !self.args.flag_no_dapps && cfg!(feature = "dapps") } + fn secretstore_enabled(&self) -> bool { + !self.args.flag_no_secretstore && cfg!(feature = "secretstore") + } + fn ui_enabled(&self) -> bool { if self.args.flag_force_ui { return true; @@ -1083,7 +1108,7 @@ mod tests { fn test_run_cmd() { let args = vec!["parity"]; let conf = parse(&args); - assert_eq!(conf.into_command().unwrap().cmd, Cmd::Run(RunCmd { + let mut expected = RunCmd { cache_config: Default::default(), dirs: Default::default(), spec: Default::default(), @@ -1113,6 +1138,7 @@ mod tests { dapps_conf: Default::default(), ipfs_conf: Default::default(), signer_conf: Default::default(), + secretstore_conf: Default::default(), ui: false, dapp: None, name: "".into(), @@ -1123,7 +1149,9 @@ mod tests { check_seal: true, download_old_blocks: true, verifier_settings: Default::default(), - })); + }; + expected.secretstore_conf.enabled = cfg!(feature = "secretstore"); + assert_eq!(conf.into_command().unwrap().cmd, Cmd::Run(expected)); } #[test] diff --git a/parity/dir.rs b/parity/dir.rs index f48d052a4..434254774 100644 --- a/parity/dir.rs +++ b/parity/dir.rs @@ -45,6 +45,7 @@ pub struct Directories { pub keys: String, pub signer: String, pub dapps: String, + pub secretstore: String, } impl Default for Directories { @@ -57,12 +58,13 @@ impl Default for Directories { keys: replace_home(&data_dir, "$BASE/keys"), signer: replace_home(&data_dir, "$BASE/signer"), dapps: replace_home(&data_dir, "$BASE/dapps"), + secretstore: replace_home(&data_dir, "$BASE/secretstore"), } } } impl Directories { - pub fn create_dirs(&self, dapps_enabled: bool, signer_enabled: bool) -> Result<(), String> { + pub fn create_dirs(&self, dapps_enabled: bool, signer_enabled: bool, secretstore_enabled: bool) -> Result<(), String> { fs::create_dir_all(&self.base).map_err(|e| e.to_string())?; fs::create_dir_all(&self.db).map_err(|e| e.to_string())?; fs::create_dir_all(&self.keys).map_err(|e| e.to_string())?; @@ -72,6 +74,9 @@ impl Directories { if dapps_enabled { fs::create_dir_all(&self.dapps).map_err(|e| e.to_string())?; } + if secretstore_enabled { + fs::create_dir_all(&self.secretstore).map_err(|e| e.to_string())?; + } Ok(()) } @@ -241,6 +246,7 @@ mod tests { keys: replace_home(&data_dir, "$BASE/keys"), signer: replace_home(&data_dir, "$BASE/signer"), dapps: replace_home(&data_dir, "$BASE/dapps"), + secretstore: replace_home(&data_dir, "$BASE/secretstore"), }; assert_eq!(expected, Directories::default()); } diff --git a/parity/main.rs b/parity/main.rs index bd4afca6c..eefd42a94 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -66,6 +66,10 @@ extern crate log as rlog; #[cfg(feature="stratum")] extern crate ethcore_stratum; + +#[cfg(feature="secretstore")] +extern crate ethcore_secretstore; + #[cfg(feature = "dapps")] extern crate ethcore_dapps; @@ -101,6 +105,7 @@ mod rpc_apis; mod run; mod signer; mod snapshot; +mod secretstore; mod upgrade; mod url; mod user_defaults; diff --git a/parity/run.rs b/parity/run.rs index df9fc7384..e91a8716b 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -49,6 +49,7 @@ use user_defaults::UserDefaults; use dapps; use ipfs; use signer; +use secretstore; use modules; use rpc_apis; use rpc; @@ -96,6 +97,7 @@ pub struct RunCmd { pub dapps_conf: dapps::Configuration, pub ipfs_conf: ipfs::Configuration, pub signer_conf: signer::Configuration, + pub secretstore_conf: secretstore::Configuration, pub dapp: Option, pub ui: bool, pub name: String, @@ -190,7 +192,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R execute_upgrades(&cmd.dirs.base, &db_dirs, algorithm, cmd.compaction.compaction_profile(db_dirs.db_root_path().as_path()))?; // create dirs used by parity - cmd.dirs.create_dirs(cmd.dapps_conf.enabled, cmd.signer_conf.enabled)?; + cmd.dirs.create_dirs(cmd.dapps_conf.enabled, cmd.signer_conf.enabled, cmd.secretstore_conf.enabled)?; // run in daemon mode if let Some(pid_file) = cmd.daemon { @@ -422,6 +424,10 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R }; let signer_server = signer::start(cmd.signer_conf.clone(), signer_deps)?; + // secret store key server + let secretstore_deps = secretstore::Dependencies { }; + let secretstore_key_server = secretstore::start(cmd.secretstore_conf.clone(), secretstore_deps); + // the ipfs server let ipfs_server = match cmd.ipfs_conf.enabled { true => Some(ipfs::start_server(cmd.ipfs_conf.port, client.clone())?), @@ -484,7 +490,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R let restart = wait_for_exit(panic_handler, Some(updater), can_restart); // drop this stuff as soon as exit detected. - drop((http_server, ipc_server, dapps_server, signer_server, ipfs_server, event_loop)); + drop((http_server, ipc_server, dapps_server, signer_server, secretstore_key_server, ipfs_server, event_loop)); info!("Finishing work, please wait..."); diff --git a/parity/secretstore.rs b/parity/secretstore.rs new file mode 100644 index 000000000..79a209504 --- /dev/null +++ b/parity/secretstore.rs @@ -0,0 +1,105 @@ +// Copyright 2015-2017 Parity Technologies (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 dir::default_data_path; +use helpers::replace_home; + +#[derive(Debug, PartialEq, Clone)] +/// Secret store configuration +pub struct Configuration { + /// Is secret store functionality enabled? + pub enabled: bool, + /// Interface to listen to + pub interface: String, + /// Port to listen to + pub port: u16, + /// Data directory path for secret store + pub data_path: String, +} + +#[derive(Debug, PartialEq, Clone)] +/// Secret store dependencies +pub struct Dependencies { + // the only dependency will be BlockChainClient +} + +#[cfg(not(feature = "secretstore"))] +mod server { + use super::{Configuration, Dependencies}; + + /// Noop key server implementation + pub struct KeyServer; + + impl KeyServer { + /// Create new noop key server + pub fn new(_conf: Configuration, _deps: Dependencies) -> Result { + Ok(KeyServer) + } + } +} + +#[cfg(feature="secretstore")] +mod server { + use ethcore_secretstore; + use super::{Configuration, Dependencies}; + + /// Key server + pub struct KeyServer { + _key_server: Box, + } + + impl KeyServer { + /// Create new key server + pub fn new(conf: Configuration, _deps: Dependencies) -> Result { + let conf = ethcore_secretstore::ServiceConfiguration { + listener_addr: conf.interface, + listener_port: conf.port, + data_path: conf.data_path, + }; + + let key_server = ethcore_secretstore::start(conf) + .map_err(Into::::into)?; + + Ok(KeyServer { + _key_server: key_server, + }) + } + } +} + +pub use self::server::KeyServer; + +impl Default for Configuration { + fn default() -> Self { + let data_dir = default_data_path(); + Configuration { + enabled: true, + interface: "127.0.0.1".to_owned(), + port: 8082, + data_path: replace_home(&data_dir, "$BASE/secretstore"), + } + } +} + +/// Start secret store-related functionality +pub fn start(conf: Configuration, deps: Dependencies) -> Result, String> { + if !conf.enabled { + return Ok(None); + } + + KeyServer::new(conf, deps) + .map(|s| Some(s)) +} diff --git a/secret_store/Cargo.toml b/secret_store/Cargo.toml new file mode 100644 index 000000000..111ff5aff --- /dev/null +++ b/secret_store/Cargo.toml @@ -0,0 +1,25 @@ +[package] +description = "Ethcore Secret Store" +name = "ethcore-secretstore" +version = "1.0.0" +license = "GPL-3.0" +authors = ["Parity Technologies "] +build = "build.rs" + +[build-dependencies] +ethcore-ipc-codegen = { path = "../ipc/codegen" } + +[dependencies] +log = "0.3" +parking_lot = "0.3" +hyper = { version = "0.9", default-features = false } +url = "1.0" +ethcore-devtools = { path = "../devtools" } +ethcore-util = { path = "../util" } +ethcore-ipc = { path = "../ipc/rpc" } +ethcore-ipc-nano = { path = "../ipc/nano" } +ethcrypto = { path = "../ethcrypto" } +ethkey = { path = "../ethkey" } + +[profile.release] +debug = true diff --git a/secret_store/build.rs b/secret_store/build.rs new file mode 100644 index 000000000..b2b27ea1e --- /dev/null +++ b/secret_store/build.rs @@ -0,0 +1,22 @@ +// Copyright 2015-2017 Parity Technologies (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 . + +extern crate ethcore_ipc_codegen; + +fn main() { + ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap(); + ethcore_ipc_codegen::derive_ipc_cond("src/traits.rs", cfg!(feature="ipc")).unwrap(); +} diff --git a/secret_store/src/acl_storage.rs b/secret_store/src/acl_storage.rs new file mode 100644 index 000000000..47ec3d44a --- /dev/null +++ b/secret_store/src/acl_storage.rs @@ -0,0 +1,51 @@ +// Copyright 2015-2017 Parity Technologies (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::{HashMap, HashSet}; +use parking_lot::RwLock; +use types::all::{Error, DocumentAddress, Public}; + +/// ACL storage of Secret Store +pub trait AclStorage: Send + Sync { + /// Check if requestor with `public` key can access document with hash `document` + fn check(&self, public: &Public, document: &DocumentAddress) -> Result; +} + +/// Dummy ACL storage implementation +#[derive(Default, Debug)] +pub struct DummyAclStorage { + prohibited: RwLock>>, +} + +impl DummyAclStorage { + #[cfg(test)] + /// Prohibit given requestor access to given document + pub fn prohibit(&self, public: Public, document: DocumentAddress) { + self.prohibited.write() + .entry(public) + .or_insert_with(Default::default) + .insert(document); + } +} + +impl AclStorage for DummyAclStorage { + fn check(&self, public: &Public, document: &DocumentAddress) -> Result { + Ok(self.prohibited.read() + .get(public) + .map(|docs| !docs.contains(document)) + .unwrap_or(true)) + } +} diff --git a/secret_store/src/http_listener.rs b/secret_store/src/http_listener.rs new file mode 100644 index 000000000..92799d221 --- /dev/null +++ b/secret_store/src/http_listener.rs @@ -0,0 +1,176 @@ +// Copyright 2015-2017 Parity Technologies (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::str::FromStr; +use std::sync::Arc; +use hyper::header; +use hyper::uri::RequestUri; +use hyper::method::Method as HttpMethod; +use hyper::status::StatusCode as HttpStatusCode; +use hyper::server::{Server as HttpServer, Request as HttpRequest, Response as HttpResponse, Handler as HttpHandler, + Listening as HttpListening}; +use url::percent_encoding::percent_decode; + +use util::ToPretty; +use traits::KeyServer; +use types::all::{Error, ServiceConfiguration, RequestSignature, DocumentAddress, DocumentEncryptedKey}; + +/// Key server http-requests listener +pub struct KeyServerHttpListener { + _http_server: HttpListening, + handler: Arc>, +} + +/// Parsed http request +#[derive(Debug, Clone, PartialEq)] +enum Request { + /// Invalid request + Invalid, + /// Request encryption key of given document for given requestor + GetDocumentKey(DocumentAddress, RequestSignature), +} + +/// Cloneable http handler +struct KeyServerHttpHandler { + handler: Arc>, +} + +/// Shared http handler +struct KeyServerSharedHttpHandler { + key_server: T, +} + +impl KeyServerHttpListener where T: KeyServer + 'static { + /// Start KeyServer http listener + pub fn start(config: ServiceConfiguration, key_server: T) -> Result { + let shared_handler = Arc::new(KeyServerSharedHttpHandler { + key_server: key_server, + }); + let handler = KeyServerHttpHandler { + handler: shared_handler.clone(), + }; + + let listener_addr: &str = &format!("{}:{}", config.listener_addr, config.listener_port); + let http_server = HttpServer::http(&listener_addr).unwrap(); + let http_server = http_server.handle(handler).unwrap(); + let listener = KeyServerHttpListener { + _http_server: http_server, + handler: shared_handler, + }; + Ok(listener) + } +} + +impl KeyServer for KeyServerHttpListener where T: KeyServer + 'static { + fn document_key(&self, signature: &RequestSignature, document: &DocumentAddress) -> Result { + self.handler.key_server.document_key(signature, document) + } +} + +impl HttpHandler for KeyServerHttpHandler where T: KeyServer + 'static { + fn handle(&self, req: HttpRequest, mut res: HttpResponse) { + if req.method != HttpMethod::Get { + warn!(target: "secretstore", "Ignoring {}-request {}", req.method, req.uri); + *res.status_mut() = HttpStatusCode::NotFound; + return; + } + + if req.headers.has::() { + warn!(target: "secretstore", "Ignoring {}-request {} with Origin header", req.method, req.uri); + *res.status_mut() = HttpStatusCode::NotFound; + return; + } + + match req.uri { + RequestUri::AbsolutePath(ref path) => match parse_request(&path) { + Request::GetDocumentKey(document, signature) => { + let document_key = self.handler.key_server.document_key(&signature, &document) + .map_err(|err| { + warn!(target: "secretstore", "GetDocumentKey request {} has failed with: {}", req.uri, err); + err + }); + match document_key { + Ok(document_key) => { + let document_key = document_key.to_hex().into_bytes(); + res.headers_mut().set(header::ContentType::plaintext()); + if let Err(err) = res.send(&document_key) { + // nothing to do, but log error + warn!(target: "secretstore", "GetDocumentKey request {} response has failed with: {}", req.uri, err); + } + }, + Err(Error::BadSignature) => *res.status_mut() = HttpStatusCode::BadRequest, + Err(Error::AccessDenied) => *res.status_mut() = HttpStatusCode::Forbidden, + Err(Error::DocumentNotFound) => *res.status_mut() = HttpStatusCode::NotFound, + Err(Error::Database(_)) => *res.status_mut() = HttpStatusCode::InternalServerError, + Err(Error::Internal(_)) => *res.status_mut() = HttpStatusCode::InternalServerError, + } + }, + Request::Invalid => { + warn!(target: "secretstore", "Ignoring invalid {}-request {}", req.method, req.uri); + *res.status_mut() = HttpStatusCode::BadRequest; + }, + }, + _ => { + warn!(target: "secretstore", "Ignoring invalid {}-request {}", req.method, req.uri); + *res.status_mut() = HttpStatusCode::NotFound; + }, + }; + } +} + +fn parse_request(uri_path: &str) -> Request { + let uri_path = match percent_decode(uri_path.as_bytes()).decode_utf8() { + Ok(path) => path, + Err(_) => return Request::Invalid, + }; + + let path: Vec = uri_path.trim_left_matches('/').split('/').map(Into::into).collect(); + if path.len() != 2 || path[0].is_empty() || path[1].is_empty() { + return Request::Invalid; + } + + let document = DocumentAddress::from_str(&path[0]); + let signature = RequestSignature::from_str(&path[1]); + match (document, signature) { + (Ok(document), Ok(signature)) => Request::GetDocumentKey(document, signature), + _ => Request::Invalid, + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + use super::super::RequestSignature; + use super::{parse_request, Request}; + + #[test] + fn parse_request_successful() { + assert_eq!(parse_request("/0000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01"), + Request::GetDocumentKey("0000000000000000000000000000000000000000000000000000000000000001".into(), + RequestSignature::from_str("a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01").unwrap())); + assert_eq!(parse_request("/%30000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01"), + Request::GetDocumentKey("0000000000000000000000000000000000000000000000000000000000000001".into(), + RequestSignature::from_str("a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01").unwrap())); + } + + #[test] + fn parse_request_failed() { + assert_eq!(parse_request("/0000000000000000000000000000000000000000000000000000000000000001"), Request::Invalid); + assert_eq!(parse_request("/0000000000000000000000000000000000000000000000000000000000000001/"), Request::Invalid); + assert_eq!(parse_request("/a/b"), Request::Invalid); + assert_eq!(parse_request("/0000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01/0000000000000000000000000000000000000000000000000000000000000002"), Request::Invalid); + } +} diff --git a/secret_store/src/key_server.rs b/secret_store/src/key_server.rs new file mode 100644 index 000000000..32ac48031 --- /dev/null +++ b/secret_store/src/key_server.rs @@ -0,0 +1,124 @@ +// Copyright 2015-2017 Parity Technologies (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 ethcrypto; +use ethkey; +use super::acl_storage::AclStorage; +use super::key_storage::KeyStorage; +use traits::KeyServer; +use types::all::{Error, RequestSignature, DocumentAddress, DocumentEncryptedKey}; + +/// Secret store key server implementation +pub struct KeyServerImpl { + acl_storage: T, + key_storage: U, +} + +impl KeyServerImpl where T: AclStorage, U: KeyStorage { + /// Create new key server instance + pub fn new(acl_storage: T, key_storage: U) -> Self { + KeyServerImpl { + acl_storage: acl_storage, + key_storage: key_storage, + } + } +} + +impl KeyServer for KeyServerImpl where T: AclStorage, U: KeyStorage { + fn document_key(&self, signature: &RequestSignature, document: &DocumentAddress) -> Result { + // recover requestor' public key from signature + let public = ethkey::recover(signature, document) + .map_err(|_| Error::BadSignature)?; + + // check that requestor has access to the document + if !self.acl_storage.check(&public, document)? { + return Err(Error::AccessDenied); + } + + // read unencrypted document key + let document_key = self.key_storage.get(document)?; + // encrypt document key with requestor public key + let document_key = ethcrypto::ecies::encrypt_single_message(&public, &document_key) + .map_err(|err| Error::Internal(format!("Error encrypting document key: {}", err)))?; + Ok(document_key) + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + use ethcrypto; + use ethkey::{self, Secret}; + use acl_storage::DummyAclStorage; + use key_storage::KeyStorage; + use key_storage::tests::DummyKeyStorage; + use super::super::{Error, RequestSignature, DocumentAddress}; + use super::{KeyServer, KeyServerImpl}; + + const DOCUMENT1: &'static str = "0000000000000000000000000000000000000000000000000000000000000001"; + const DOCUMENT2: &'static str = "0000000000000000000000000000000000000000000000000000000000000002"; + const KEY1: &'static str = "key1"; + const PRIVATE1: &'static str = "03055e18a8434dcc9061cc1b81c4ef84dc7cf4574d755e52cdcf0c8898b25b11"; + const PUBLIC2: &'static str = "dfe62f56bb05fbd85b485bac749f3410309e24b352bac082468ce151e9ddb94fa7b5b730027fe1c7c5f3d5927621d269f91aceb5caa3c7fe944677a22f88a318"; + const PRIVATE2: &'static str = "0eb3816f4f705fa0fd952fb27b71b8c0606f09f4743b5b65cbc375bd569632f2"; + + fn create_key_server() -> KeyServerImpl { + let acl_storage = DummyAclStorage::default(); + let key_storage = DummyKeyStorage::default(); + key_storage.insert(DOCUMENT1.into(), KEY1.into()).unwrap(); + acl_storage.prohibit(PUBLIC2.into(), DOCUMENT1.into()); + KeyServerImpl::new(acl_storage, key_storage) + } + + fn make_signature(secret: &str, document: &'static str) -> RequestSignature { + let secret = Secret::from_str(secret).unwrap(); + let document: DocumentAddress = document.into(); + ethkey::sign(&secret, &document).unwrap() + } + + #[test] + fn document_key_succeeds() { + let key_server = create_key_server(); + let signature = make_signature(PRIVATE1, DOCUMENT1); + let document_key = key_server.document_key(&signature, &DOCUMENT1.into()).unwrap(); + let document_key = ethcrypto::ecies::decrypt_single_message(&Secret::from_str(PRIVATE1).unwrap(), &document_key); + assert_eq!(document_key, Ok(KEY1.into())); + } + + #[test] + fn document_key_fails_when_bad_signature() { + let key_server = create_key_server(); + let signature = RequestSignature::default(); + let document_key = key_server.document_key(&signature, &DOCUMENT1.into()); + assert_eq!(document_key, Err(Error::BadSignature)); + } + + #[test] + fn document_key_fails_when_acl_check_fails() { + let key_server = create_key_server(); + let signature = make_signature(PRIVATE2, DOCUMENT1); + let document_key = key_server.document_key(&signature, &DOCUMENT1.into()); + assert_eq!(document_key, Err(Error::AccessDenied)); + } + + #[test] + fn document_key_fails_when_document_not_found() { + let key_server = create_key_server(); + let signature = make_signature(PRIVATE1, DOCUMENT2); + let document_key = key_server.document_key(&signature, &DOCUMENT2.into()); + assert_eq!(document_key, Err(Error::DocumentNotFound)); + } +} diff --git a/secret_store/src/key_storage.rs b/secret_store/src/key_storage.rs new file mode 100644 index 000000000..fe7777410 --- /dev/null +++ b/secret_store/src/key_storage.rs @@ -0,0 +1,115 @@ +// Copyright 2015-2017 Parity Technologies (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::path::PathBuf; +use util::Database; +use types::all::{Error, ServiceConfiguration, DocumentAddress, DocumentKey}; + +/// Document encryption keys storage +pub trait KeyStorage: Send + Sync { + /// Insert document encryption key + fn insert(&self, document: DocumentAddress, key: DocumentKey) -> Result<(), Error>; + /// Get document encryption key + fn get(&self, document: &DocumentAddress) -> Result; +} + +/// Persistent document encryption keys storage +pub struct PersistentKeyStorage { + db: Database, +} + +impl PersistentKeyStorage { + /// Create new persistent document encryption keys storage + pub fn new(config: &ServiceConfiguration) -> Result { + let mut db_path = PathBuf::from(&config.data_path); + db_path.push("db"); + let db_path = db_path.to_str().ok_or(Error::Database("Invalid secretstore path".to_owned()))?; + + Ok(PersistentKeyStorage { + db: Database::open_default(&db_path).map_err(Error::Database)?, + }) + } +} + +impl KeyStorage for PersistentKeyStorage { + fn insert(&self, document: DocumentAddress, key: DocumentKey) -> Result<(), Error> { + let mut batch = self.db.transaction(); + batch.put(None, &document, &key); + self.db.write(batch).map_err(Error::Database) + } + + fn get(&self, document: &DocumentAddress) -> Result { + self.db.get(None, document) + .map_err(Error::Database)? + .ok_or(Error::DocumentNotFound) + .map(|key| key.to_vec()) + } +} + +#[cfg(test)] +pub mod tests { + use std::collections::HashMap; + use parking_lot::RwLock; + use devtools::RandomTempPath; + use super::super::types::all::{Error, ServiceConfiguration, DocumentAddress, DocumentKey}; + use super::{KeyStorage, PersistentKeyStorage}; + + #[derive(Default)] + /// In-memory document encryption keys storage + pub struct DummyKeyStorage { + keys: RwLock>, + } + + impl KeyStorage for DummyKeyStorage { + fn insert(&self, document: DocumentAddress, key: DocumentKey) -> Result<(), Error> { + self.keys.write().insert(document, key); + Ok(()) + } + + fn get(&self, document: &DocumentAddress) -> Result { + self.keys.read().get(document).cloned().ok_or(Error::DocumentNotFound) + } + } + + #[test] + fn persistent_key_storage() { + let path = RandomTempPath::create_dir(); + let config = ServiceConfiguration { + listener_addr: "0.0.0.0".to_owned(), + listener_port: 8082, + data_path: path.as_str().to_owned(), + }; + + let key1 = DocumentAddress::from(1); + let value1: DocumentKey = vec![0x77, 0x88]; + let key2 = DocumentAddress::from(2); + let value2: DocumentKey = vec![0x11, 0x22]; + let key3 = DocumentAddress::from(3); + + let key_storage = PersistentKeyStorage::new(&config).unwrap(); + key_storage.insert(key1.clone(), value1.clone()).unwrap(); + key_storage.insert(key2.clone(), value2.clone()).unwrap(); + assert_eq!(key_storage.get(&key1), Ok(value1.clone())); + assert_eq!(key_storage.get(&key2), Ok(value2.clone())); + assert_eq!(key_storage.get(&key3), Err(Error::DocumentNotFound)); + drop(key_storage); + + let key_storage = PersistentKeyStorage::new(&config).unwrap(); + assert_eq!(key_storage.get(&key1), Ok(value1)); + assert_eq!(key_storage.get(&key2), Ok(value2)); + assert_eq!(key_storage.get(&key3), Err(Error::DocumentNotFound)); + } +} diff --git a/secret_store/src/lib.rs b/secret_store/src/lib.rs new file mode 100644 index 000000000..390ae1e5e --- /dev/null +++ b/secret_store/src/lib.rs @@ -0,0 +1,52 @@ +// Copyright 2015-2017 Parity Technologies (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 . + +#[macro_use] +extern crate log; +extern crate hyper; +extern crate parking_lot; +extern crate url; + +extern crate ethcore_devtools as devtools; +extern crate ethcore_util as util; +extern crate ethcore_ipc as ipc; +extern crate ethcrypto; +extern crate ethkey; + +mod types; + +mod traits { + #![allow(dead_code, unused_assignments, unused_variables, missing_docs)] // codegen issues + include!(concat!(env!("OUT_DIR"), "/traits.rs")); +} + +mod acl_storage; +mod http_listener; +mod key_server; +mod key_storage; + +pub use types::all::{DocumentAddress, DocumentKey, DocumentEncryptedKey, RequestSignature, Public, + Error, ServiceConfiguration}; +pub use traits::{KeyServer}; + +/// Start new key server instance +pub fn start(config: ServiceConfiguration) -> Result, Error> { + let acl_storage = acl_storage::DummyAclStorage::default(); + let key_storage = key_storage::PersistentKeyStorage::new(&config)?; + let key_server = key_server::KeyServerImpl::new(acl_storage, key_storage); + let listener = http_listener::KeyServerHttpListener::start(config, key_server)?; + Ok(Box::new(listener)) +} diff --git a/secret_store/src/traits.rs b/secret_store/src/traits.rs new file mode 100644 index 000000000..9a68e9c4d --- /dev/null +++ b/secret_store/src/traits.rs @@ -0,0 +1,24 @@ +// Copyright 2015-2017 Parity Technologies (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 types::all::{Error, RequestSignature, DocumentAddress, DocumentEncryptedKey}; + +#[ipc(client_ident="RemoteKeyServer")] +/// Secret store key server +pub trait KeyServer: Send + Sync { + /// Request encryption key of given document for given requestor + fn document_key(&self, signature: &RequestSignature, document: &DocumentAddress) -> Result; +} diff --git a/secret_store/src/types/all.rs b/secret_store/src/types/all.rs new file mode 100644 index 000000000..f318e6543 --- /dev/null +++ b/secret_store/src/types/all.rs @@ -0,0 +1,77 @@ +// Copyright 2015-2017 Parity Technologies (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::fmt; + +use ethkey; +use util; + +/// Document address type. +pub type DocumentAddress = util::H256; +/// Document key type. +pub type DocumentKey = util::Bytes; +/// Encrypted key type. +pub type DocumentEncryptedKey = util::Bytes; +/// Request signature type. +pub type RequestSignature = ethkey::Signature; +/// Public key type. +pub use ethkey::Public; + +#[derive(Debug, Clone, PartialEq)] +#[binary] +/// Secret store error +pub enum Error { + /// Bad signature is passed + BadSignature, + /// Access to resource is denied + AccessDenied, + /// Requested document not found + DocumentNotFound, + /// Database-related error + Database(String), + /// Internal error + Internal(String), +} + +#[derive(Debug)] +#[binary] +/// Secret store configuration +pub struct ServiceConfiguration { + /// Interface to listen to + pub listener_addr: String, + /// Port to listen to + pub listener_port: u16, + /// Data directory path for secret store + pub data_path: String, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match *self { + Error::BadSignature => write!(f, "Bad signature"), + Error::AccessDenied => write!(f, "Access dened"), + Error::DocumentNotFound => write!(f, "Document not found"), + Error::Database(ref msg) => write!(f, "Database error: {}", msg), + Error::Internal(ref msg) => write!(f, "Internal error: {}", msg), + } + } +} + +impl Into for Error { + fn into(self) -> String { + format!("{}", self) + } +} diff --git a/secret_store/src/types/mod.rs b/secret_store/src/types/mod.rs new file mode 100644 index 000000000..584e78f30 --- /dev/null +++ b/secret_store/src/types/mod.rs @@ -0,0 +1,20 @@ +// Copyright 2015-2017 Parity Technologies (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 . + +//! Types used in the public api + +#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues +include!(concat!(env!("OUT_DIR"), "/mod.rs.in")); diff --git a/secret_store/src/types/mod.rs.in b/secret_store/src/types/mod.rs.in new file mode 100644 index 000000000..0681e2884 --- /dev/null +++ b/secret_store/src/types/mod.rs.in @@ -0,0 +1,17 @@ +// Copyright 2015-2017 Parity Technologies (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 . + +pub mod all; From 0aad8a87ae2e16d78525c6ae89dee68f80263d17 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Mon, 20 Feb 2017 16:19:43 +0100 Subject: [PATCH 42/51] Added pending transaction info to eth_getTransactionByHash (#4570) * Return condition info for pending transactions * Fixed warnings --- ethcore/src/miner/miner.rs | 6 +++--- ethcore/src/miner/mod.rs | 2 +- ethcore/src/miner/transaction_queue.rs | 4 ++-- rpc/src/v1/tests/helpers/miner_service.rs | 4 ++-- util/bigint/src/hash.rs | 1 - util/network/src/discovery.rs | 1 - 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 06949f4bd..b4a7f2327 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -984,7 +984,7 @@ impl MinerService for Miner { } } - fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option { + fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option { let queue = self.transaction_queue.lock(); match self.options.pending_set { PendingSet::AlwaysQueue => queue.find(hash), @@ -992,14 +992,14 @@ impl MinerService for Miner { self.from_pending_block( best_block, || queue.find(hash), - |sealing| sealing.transactions().iter().find(|t| &t.hash() == hash).cloned() + |sealing| sealing.transactions().iter().find(|t| &t.hash() == hash).cloned().map(Into::into) ) }, PendingSet::AlwaysSealing => { self.from_pending_block( best_block, || None, - |sealing| sealing.transactions().iter().find(|t| &t.hash() == hash).cloned() + |sealing| sealing.transactions().iter().find(|t| &t.hash() == hash).cloned().map(Into::into) ) }, } diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index d88a261f3..eee9d1c5c 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -148,7 +148,7 @@ pub trait MinerService : Send + Sync { where F: FnOnce(&ClosedBlock) -> T, Self: Sized; /// Query pending transactions for hash. - fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option; + fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option; /// Get a list of all pending transactions in the queue. fn pending_transactions(&self) -> Vec; diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index ef7094a90..d937c15fc 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -1137,8 +1137,8 @@ impl TransactionQueue { } /// Finds transaction in the queue by hash (if any) - pub fn find(&self, hash: &H256) -> Option { - self.by_hash.get(hash).map(|tx| tx.transaction.clone()) + pub fn find(&self, hash: &H256) -> Option { + self.by_hash.get(hash).map(|tx| PendingTransaction { transaction: tx.transaction.clone(), condition: tx.condition.clone() }) } /// Removes all elements (in any state) from the queue diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 82b776d00..75ca928b4 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -200,8 +200,8 @@ impl MinerService for TestMinerService { Some(f(&open_block.close())) } - fn transaction(&self, _best_block: BlockNumber, hash: &H256) -> Option { - self.pending_transactions.lock().get(hash).cloned() + fn transaction(&self, _best_block: BlockNumber, hash: &H256) -> Option { + self.pending_transactions.lock().get(hash).cloned().map(Into::into) } fn pending_transactions(&self) -> Vec { diff --git a/util/bigint/src/hash.rs b/util/bigint/src/hash.rs index 1eca9860c..cf74ea0cb 100644 --- a/util/bigint/src/hash.rs +++ b/util/bigint/src/hash.rs @@ -504,7 +504,6 @@ pub type H256FastSet = HashSet>; #[cfg(test)] mod tests { use hash::*; - use bigint::*; use std::str::FromStr; #[test] diff --git a/util/network/src/discovery.rs b/util/network/src/discovery.rs index 5cbe3f09e..04ad0b7ce 100644 --- a/util/network/src/discovery.rs +++ b/util/network/src/discovery.rs @@ -556,7 +556,6 @@ impl Discovery { mod tests { use super::*; use std::net::{SocketAddr}; - use util::sha3::Hashable; use util::FixedHash; use node_table::{Node, NodeId, NodeEndpoint}; From 1949d44d0c44a550f16fb93f3ee3ce0e6b7b70f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 20 Feb 2017 16:30:14 +0100 Subject: [PATCH 43/51] Hash-fetch errors in case upstream returns non-200 (#4599) --- hash-fetch/src/client.rs | 139 ++++++++++++++++++++++++++++++++++++++ hash-fetch/src/urlhint.rs | 10 +-- util/fetch/src/client.rs | 33 +++++++++ util/fetch/src/lib.rs | 2 + 4 files changed, 179 insertions(+), 5 deletions(-) diff --git a/hash-fetch/src/client.rs b/hash-fetch/src/client.rs index 453f8925a..cffc10e63 100644 --- a/hash-fetch/src/client.rs +++ b/hash-fetch/src/client.rs @@ -50,12 +50,31 @@ pub enum Error { /// Computed hash got: H256, }, + /// Server didn't respond with OK status. + InvalidStatus, /// IO Error while validating hash. IO(io::Error), /// Error during fetch. Fetch(FetchError), } +#[cfg(test)] +impl PartialEq for Error { + fn eq(&self, other: &Self) -> bool { + use Error::*; + match (self, other) { + (&HashMismatch { expected, got }, &HashMismatch { expected: e, got: g }) => { + expected == e && got == g + }, + (&NoResolution, &NoResolution) => true, + (&InvalidStatus, &InvalidStatus) => true, + (&IO(_), &IO(_)) => true, + (&Fetch(_), &Fetch(_)) => true, + _ => false, + } + } +} + impl From for Error { fn from(error: FetchError) -> Self { Error::Fetch(error) @@ -115,6 +134,10 @@ impl HashFetch for Client { let future = self.fetch.fetch(&url).then(move |result| { fn validate_hash(path: PathBuf, hash: H256, result: Result) -> Result { let response = result?; + if !response.is_success() { + return Err(Error::InvalidStatus); + } + // Read the response let mut reader = io::BufReader::new(response); let mut writer = io::BufWriter::new(fs::File::create(&path)?); @@ -160,3 +183,119 @@ fn random_temp_path() -> PathBuf { path.push(file); path } + +#[cfg(test)] +mod tests { + use std::sync::{Arc, mpsc}; + use util::{Mutex, FromHex}; + use futures::future; + use fetch::{self, Fetch}; + use parity_reactor::Remote; + use urlhint::tests::{FakeRegistrar, URLHINT}; + use super::{Error, Client, HashFetch}; + + + #[derive(Clone)] + struct FakeFetch { + return_success: bool + } + + impl Fetch for FakeFetch { + type Result = future::Ok; + + fn new() -> Result where Self: Sized { + Ok(FakeFetch { return_success: true }) + } + + fn fetch_with_abort(&self, url: &str, _abort: fetch::Abort) -> Self::Result { + assert_eq!(url, "https://ethcore.io/assets/images/ethcore-black-horizontal.png"); + future::ok(if self.return_success { + let cursor = ::std::io::Cursor::new(b"result"); + fetch::Response::from_reader(cursor) + } else { + fetch::Response::not_found() + }) + } + } + + fn registrar() -> FakeRegistrar { + let mut registrar = FakeRegistrar::new(); + registrar.responses = Mutex::new(vec![ + Ok(format!("000000000000000000000000{}", URLHINT).from_hex().unwrap()), + Ok("00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000deadcafebeefbeefcafedeaddeedfeedffffffff000000000000000000000000000000000000000000000000000000000000003d68747470733a2f2f657468636f72652e696f2f6173736574732f696d616765732f657468636f72652d626c61636b2d686f72697a6f6e74616c2e706e67000000".from_hex().unwrap()), + ]); + registrar + } + + #[test] + fn should_return_error_if_hash_not_found() { + // given + let contract = Arc::new(FakeRegistrar::new()); + let fetch = FakeFetch { return_success: false }; + let client = Client::with_fetch(contract.clone(), fetch, Remote::new_sync()); + + // when + let (tx, rx) = mpsc::channel(); + client.fetch(2.into(), Box::new(move |result| { + tx.send(result).unwrap(); + })); + + // then + let result = rx.recv().unwrap(); + assert_eq!(result.unwrap_err(), Error::NoResolution); + } + + #[test] + fn should_return_error_if_response_is_not_successful() { + // given + let registrar = Arc::new(registrar()); + let fetch = FakeFetch { return_success: false }; + let client = Client::with_fetch(registrar.clone(), fetch, Remote::new_sync()); + + // when + let (tx, rx) = mpsc::channel(); + client.fetch(2.into(), Box::new(move |result| { + tx.send(result).unwrap(); + })); + + // then + let result = rx.recv().unwrap(); + assert_eq!(result.unwrap_err(), Error::InvalidStatus); + } + #[test] + fn should_return_hash_mismatch() { + // given + let registrar = Arc::new(registrar()); + let fetch = FakeFetch { return_success: true }; + let client = Client::with_fetch(registrar.clone(), fetch, Remote::new_sync()); + + // when + let (tx, rx) = mpsc::channel(); + client.fetch(2.into(), Box::new(move |result| { + tx.send(result).unwrap(); + })); + + // then + let result = rx.recv().unwrap(); + let hash = "0x06b0a4f426f6713234b2d4b2468640bc4e0bb72657a920ad24c5087153c593c8".into(); + assert_eq!(result.unwrap_err(), Error::HashMismatch { expected: 2.into(), got: hash }); + } + + #[test] + fn should_return_path_if_hash_matches() { + // given + let registrar = Arc::new(registrar()); + let fetch = FakeFetch { return_success: true }; + let client = Client::with_fetch(registrar.clone(), fetch, Remote::new_sync()); + + // when + let (tx, rx) = mpsc::channel(); + client.fetch("0x06b0a4f426f6713234b2d4b2468640bc4e0bb72657a920ad24c5087153c593c8".into(), Box::new(move |result| { + tx.send(result).unwrap(); + })); + + // then + let result = rx.recv().unwrap(); + assert!(result.is_ok(), "Should return path, got: {:?}", result); + } +} diff --git a/hash-fetch/src/urlhint.rs b/hash-fetch/src/urlhint.rs index 227f24dc3..1588b5482 100644 --- a/hash-fetch/src/urlhint.rs +++ b/hash-fetch/src/urlhint.rs @@ -264,7 +264,7 @@ fn as_string(e: T) -> String { } #[cfg(test)] -mod tests { +pub mod tests { use std::sync::Arc; use std::str::FromStr; use rustc_serialize::hex::FromHex; @@ -273,16 +273,16 @@ mod tests { use super::guess_mime_type; use util::{Bytes, Address, Mutex, ToPretty}; - struct FakeRegistrar { + pub struct FakeRegistrar { pub calls: Arc>>, pub responses: Mutex>>, } - const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2"; - const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000"; + pub const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2"; + pub const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000"; impl FakeRegistrar { - fn new() -> Self { + pub fn new() -> Self { FakeRegistrar { calls: Arc::new(Mutex::new(Vec::new())), responses: Mutex::new( diff --git a/util/fetch/src/client.rs b/util/fetch/src/client.rs index 09fe4741b..18c8d87d9 100644 --- a/util/fetch/src/client.rs +++ b/util/fetch/src/client.rs @@ -26,10 +26,12 @@ use mime::{self, Mime}; use parking_lot::RwLock; use reqwest; +/// Fetch abort control #[derive(Default, Debug, Clone)] pub struct Abort(Arc); impl Abort { + /// Returns `true` if request is aborted. pub fn is_aborted(&self) -> bool { self.0.load(atomic::Ordering::SeqCst) } @@ -41,9 +43,12 @@ impl From> for Abort { } } +/// Fetch pub trait Fetch: Clone + Send + Sync + 'static { + /// Result type type Result: Future + Send + 'static; + /// Creates new Fetch object. fn new() -> Result where Self: Sized; /// Spawn the future in context of this `Fetch` thread pool. @@ -76,6 +81,7 @@ pub trait Fetch: Clone + Send + Sync + 'static { const CLIENT_TIMEOUT_SECONDS: u64 = 5; +/// Fetch client pub struct Client { client: RwLock<(time::Instant, Arc)>, pool: CpuPool, @@ -189,9 +195,12 @@ impl Future for FetchTask { } } +/// Fetch Error #[derive(Debug)] pub enum Error { + /// Internal fetch error Fetch(reqwest::Error), + /// Request aborted Aborted, } @@ -204,17 +213,20 @@ impl From for Error { enum ResponseInner { Response(reqwest::Response), Reader(Box), + NotFound, } impl fmt::Debug for ResponseInner { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { ResponseInner::Response(ref response) => response.fmt(f), + ResponseInner::NotFound => write!(f, "Not found"), ResponseInner::Reader(_) => write!(f, "io Reader"), } } } +/// A fetch response type. #[derive(Debug)] pub struct Response { inner: ResponseInner, @@ -224,6 +236,7 @@ pub struct Response { } impl Response { + /// Creates new successfuly response reading from a file. pub fn from_reader(reader: R) -> Self { Response { inner: ResponseInner::Reader(Box::new(reader)), @@ -233,13 +246,31 @@ impl Response { } } + /// Creates 404 response (useful for tests) + pub fn not_found() -> Self { + Response { + inner: ResponseInner::NotFound, + abort: Abort::default(), + limit: None, + read: 0, + } + } + + /// Returns status code of this response. pub fn status(&self) -> reqwest::StatusCode { match self.inner { ResponseInner::Response(ref r) => *r.status(), + ResponseInner::NotFound => reqwest::StatusCode::NotFound, _ => reqwest::StatusCode::Ok, } } + /// Returns `true` if response status code is successful. + pub fn is_success(&self) -> bool { + self.status() == reqwest::StatusCode::Ok + } + + /// Returns `true` if content type of this response is `text/html` pub fn is_html(&self) -> bool { match self.content_type() { Some(Mime(mime::TopLevel::Text, mime::SubLevel::Html, _)) => true, @@ -247,6 +278,7 @@ impl Response { } } + /// Returns content type of this response (if present) pub fn content_type(&self) -> Option { match self.inner { ResponseInner::Response(ref r) => { @@ -266,6 +298,7 @@ impl io::Read for Response { let res = match self.inner { ResponseInner::Response(ref mut response) => response.read(buf), + ResponseInner::NotFound => return Ok(0), ResponseInner::Reader(ref mut reader) => reader.read(buf), }; diff --git a/util/fetch/src/lib.rs b/util/fetch/src/lib.rs index 34091f4bc..21905c532 100644 --- a/util/fetch/src/lib.rs +++ b/util/fetch/src/lib.rs @@ -16,6 +16,8 @@ //! A service to fetch any HTTP / HTTPS content. +#![warn(missing_docs)] + #[macro_use] extern crate log; From 72998d3ce333dcbdd4848a3a6e56e2b7d9db30d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 20 Feb 2017 16:33:12 +0100 Subject: [PATCH 44/51] Proper default accounts RPCs (#4580) * Default accounts setting - account provider * RPC support for default accounts * Updating JS code * Rename whitelist to addresses --- ethcore/src/account_provider/mod.rs | 234 ++++++++++++++++---- ethcore/src/account_provider/stores.rs | 46 +++- js/src/api/rpc/parity/parity.js | 38 +++- js/src/api/subscriptions/personal.js | 6 +- js/src/jsonrpc/interfaces/parity.js | 81 ++++++- js/src/modals/DappPermissions/store.js | 4 +- js/src/modals/DappPermissions/store.spec.js | 12 +- js/src/views/ParityBar/accountStore.js | 4 +- js/src/views/ParityBar/accountStore.spec.js | 8 +- js/src/views/ParityBar/parityBar.test.js | 4 +- json/src/misc/dapps_settings.rs | 9 +- rpc/src/v1/impls/eth.rs | 7 +- rpc/src/v1/impls/light/eth.rs | 2 +- rpc/src/v1/impls/parity.rs | 19 +- rpc/src/v1/impls/parity_accounts.rs | 62 ++++-- rpc/src/v1/impls/personal.rs | 2 +- rpc/src/v1/impls/signing.rs | 2 +- rpc/src/v1/impls/signing_unsafe.rs | 2 +- rpc/src/v1/tests/mocked/eth.rs | 6 +- rpc/src/v1/tests/mocked/parity.rs | 10 +- rpc/src/v1/tests/mocked/parity_accounts.rs | 59 ++++- rpc/src/v1/traits/parity_accounts.rs | 55 +++-- 22 files changed, 511 insertions(+), 161 deletions(-) diff --git a/ethcore/src/account_provider/mod.rs b/ethcore/src/account_provider/mod.rs index 568cbd4e3..2f6d49e75 100755 --- a/ethcore/src/account_provider/mod.rs +++ b/ethcore/src/account_provider/mod.rs @@ -23,7 +23,7 @@ use self::stores::{AddressBook, DappsSettingsStore, NewDappsPolicy}; use std::fmt; use std::collections::{HashMap, HashSet}; use std::time::{Instant, Duration}; -use util::RwLock; +use util::{FixedHash, RwLock}; use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore, random_string, SecretVaultRef, StoreAccountRef}; use ethstore::dir::MemoryDirectory; @@ -241,25 +241,88 @@ impl AccountProvider { Ok(accounts.into_iter().map(|a| a.address).collect()) } - /// Sets a whitelist of accounts exposed for unknown dapps. + /// Sets addresses of accounts exposed for unknown dapps. /// `None` means that all accounts will be visible. - pub fn set_new_dapps_whitelist(&self, accounts: Option>) -> Result<(), Error> { + /// If not `None` or empty it will also override default account. + pub fn set_new_dapps_addresses(&self, accounts: Option>) -> Result<(), Error> { + let current_default = self.new_dapps_default_address()?; + self.dapps_settings.write().set_policy(match accounts { - None => NewDappsPolicy::AllAccounts, + None => NewDappsPolicy::AllAccounts { + default: current_default, + }, Some(accounts) => NewDappsPolicy::Whitelist(accounts), }); Ok(()) } - /// Gets a whitelist of accounts exposed for unknown dapps. + /// Gets addresses of accounts exposed for unknown dapps. /// `None` means that all accounts will be visible. - pub fn new_dapps_whitelist(&self) -> Result>, Error> { + pub fn new_dapps_addresses(&self) -> Result>, Error> { Ok(match self.dapps_settings.read().policy() { - NewDappsPolicy::AllAccounts => None, + NewDappsPolicy::AllAccounts { .. } => None, NewDappsPolicy::Whitelist(accounts) => Some(accounts), }) } + /// Sets a default account for unknown dapps. + /// This account will always be returned as the first one. + pub fn set_new_dapps_default_address(&self, address: Address) -> Result<(), Error> { + if !self.valid_addresses()?.contains(&address) { + return Err(SSError::InvalidAccount.into()); + } + + let mut settings = self.dapps_settings.write(); + let new_policy = match settings.policy() { + NewDappsPolicy::AllAccounts { .. } => NewDappsPolicy::AllAccounts { default: address }, + NewDappsPolicy::Whitelist(list) => NewDappsPolicy::Whitelist(Self::insert_default(list, address)), + }; + settings.set_policy(new_policy); + + Ok(()) + } + + /// Inserts given address as first in the vector, preventing duplicates. + fn insert_default(mut addresses: Vec
, default: Address) -> Vec
{ + if let Some(position) = addresses.iter().position(|address| address == &default) { + addresses.swap(0, position); + } else { + addresses.insert(0, default); + } + + addresses + } + + /// Returns a list of accounts that new dapp should see. + /// First account is always the default account. + fn new_dapps_addresses_list(&self) -> Result, Error> { + match self.dapps_settings.read().policy() { + NewDappsPolicy::AllAccounts { default } => if default.is_zero() { + self.accounts() + } else { + Ok(Self::insert_default(self.accounts()?, default)) + }, + NewDappsPolicy::Whitelist(accounts) => { + let addresses = self.filter_addresses(accounts)?; + if addresses.is_empty() { + Ok(vec![self.accounts()?.get(0).cloned().unwrap_or(0.into())]) + } else { + Ok(addresses) + } + }, + } + } + + /// Gets a default account for new dapps + /// Will return zero address in case the default is not set and there are no accounts configured. + pub fn new_dapps_default_address(&self) -> Result { + Ok(self.new_dapps_addresses_list()? + .get(0) + .cloned() + .unwrap_or(0.into()) + ) + } + /// Gets a list of dapps recently requesting accounts. pub fn recent_dapps(&self) -> Result, Error> { Ok(self.dapps_settings.read().recent_dapps()) @@ -272,41 +335,74 @@ impl AccountProvider { Ok(()) } - /// Gets addresses visile for dapp. - pub fn dapps_addresses(&self, dapp: DappId) -> Result, Error> { - let dapps = self.dapps_settings.read(); + /// Gets addresses visible for given dapp. + pub fn dapp_addresses(&self, dapp: DappId) -> Result, Error> { + let accounts = self.dapps_settings.read().settings().get(&dapp).map(|settings| { + (settings.accounts.clone(), settings.default.clone()) + }); - let accounts = dapps.settings().get(&dapp).map(|settings| settings.accounts.clone()); match accounts { - Some(accounts) => Ok(accounts), - None => match dapps.policy() { - NewDappsPolicy::AllAccounts => self.accounts(), - NewDappsPolicy::Whitelist(accounts) => self.filter_addresses(accounts), - } + Some((Some(accounts), Some(default))) => self.filter_addresses(Self::insert_default(accounts, default)), + Some((Some(accounts), None)) => self.filter_addresses(accounts), + Some((None, Some(default))) => self.filter_addresses(Self::insert_default(self.new_dapps_addresses_list()?, default)), + _ => self.new_dapps_addresses_list(), } } /// Returns default account for particular dapp falling back to other allowed accounts if necessary. - pub fn default_address(&self, dapp: DappId) -> Result { - self.dapps_addresses(dapp)? + pub fn dapp_default_address(&self, dapp: DappId) -> Result { + let dapp_default = self.dapp_addresses(dapp)? .get(0) - .cloned() - .ok_or(SSError::InvalidAccount) + .cloned(); + + match dapp_default { + Some(default) => Ok(default), + None => self.new_dapps_default_address(), + } } - /// Sets addresses visile for dapp. - pub fn set_dapps_addresses(&self, dapp: DappId, addresses: Vec
) -> Result<(), Error> { - let addresses = self.filter_addresses(addresses)?; - self.dapps_settings.write().set_accounts(dapp, addresses); + /// Sets default address for given dapp. + /// Does not alter dapp addresses, but this account will always be returned as the first one. + pub fn set_dapp_default_address(&self, dapp: DappId, address: Address) -> Result<(), Error> { + if !self.valid_addresses()?.contains(&address) { + return Err(SSError::InvalidAccount.into()); + } + + self.dapps_settings.write().set_default(dapp, address); Ok(()) } + /// Sets addresses visible for given dapp. + /// If `None` - falls back to dapps addresses + /// If not `None` and not empty it will also override default account. + pub fn set_dapp_addresses(&self, dapp: DappId, addresses: Option>) -> Result<(), Error> { + let (addresses, default) = match addresses { + Some(addresses) => { + let addresses = self.filter_addresses(addresses)?; + let default = addresses.get(0).cloned(); + (Some(addresses), default) + }, + None => (None, None), + }; + + let mut settings = self.dapps_settings.write(); + if let Some(default) = default { + settings.set_default(dapp.clone(), default); + } + settings.set_accounts(dapp, addresses); + Ok(()) + } + + fn valid_addresses(&self) -> Result, Error> { + Ok(self.addresses_info().into_iter() + .map(|(address, _)| address) + .chain(self.accounts()?) + .collect()) + } + /// Removes addresses that are neither accounts nor in address book. fn filter_addresses(&self, addresses: Vec
) -> Result, Error> { - let valid = self.addresses_info().into_iter() - .map(|(address, _)| address) - .chain(self.accounts()?) - .collect::>(); + let valid = self.valid_addresses()?; Ok(addresses.into_iter() .filter(|a| valid.contains(&a)) @@ -743,44 +839,92 @@ mod tests { } #[test] - fn should_set_dapps_addresses() { + fn should_reset_dapp_addresses_to_default() { + // given + let ap = AccountProvider::transient_provider(); + let app = DappId("app1".into()); + // add accounts to address book + ap.set_address_name(1.into(), "1".into()); + ap.set_address_name(2.into(), "2".into()); + // set `AllAccounts` policy + ap.set_new_dapps_addresses(Some(vec![1.into(), 2.into()])).unwrap(); + assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]); + + // Alter and check + ap.set_dapp_addresses(app.clone(), Some(vec![1.into(), 3.into()])).unwrap(); + assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into()]); + + // Reset back to default + ap.set_dapp_addresses(app.clone(), None).unwrap(); + assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]); + } + + #[test] + fn should_set_dapps_default_address() { // given let ap = AccountProvider::transient_provider(); let app = DappId("app1".into()); // set `AllAccounts` policy - ap.set_new_dapps_whitelist(None).unwrap(); + ap.set_new_dapps_addresses(None).unwrap(); // add accounts to address book ap.set_address_name(1.into(), "1".into()); ap.set_address_name(2.into(), "2".into()); - // when - ap.set_dapps_addresses(app.clone(), vec![1.into(), 2.into(), 3.into()]).unwrap(); + ap.set_dapp_addresses(app.clone(), Some(vec![1.into(), 2.into(), 3.into()])).unwrap(); + assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]); + assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), 1.into()); - // then - assert_eq!(ap.dapps_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]); + // when setting empty list + ap.set_dapp_addresses(app.clone(), Some(vec![])).unwrap(); + + // then default account is intact + assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into()]); + assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), 1.into()); + + // alter default account + ap.set_dapp_default_address("app1".into(), 2.into()).unwrap(); + assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![2.into()]); + assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), 2.into()); } #[test] - fn should_set_dapps_policy() { + fn should_set_dapps_policy_and_default_account() { // given let ap = AccountProvider::transient_provider(); + + // default_account should be always available + assert_eq!(ap.new_dapps_default_address().unwrap(), 0.into()); + let address = ap.new_account("test").unwrap(); ap.set_address_name(1.into(), "1".into()); - // When returning nothing - ap.set_new_dapps_whitelist(Some(vec![])).unwrap(); - assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![]); + // Default account set to first account by default + assert_eq!(ap.new_dapps_default_address().unwrap(), address); + assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), address); + + // Even when returning nothing + ap.set_new_dapps_addresses(Some(vec![])).unwrap(); + // Default account is still returned + assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![address]); // change to all - ap.set_new_dapps_whitelist(None).unwrap(); - assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![address]); + ap.set_new_dapps_addresses(None).unwrap(); + assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![address]); // change to non-existent account - ap.set_new_dapps_whitelist(Some(vec![2.into()])).unwrap(); - assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![]); + ap.set_new_dapps_addresses(Some(vec![2.into()])).unwrap(); + assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![address]); - // change to a whitelist - ap.set_new_dapps_whitelist(Some(vec![1.into()])).unwrap(); - assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![1.into()]); + // change to a addresses + ap.set_new_dapps_addresses(Some(vec![1.into()])).unwrap(); + assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![1.into()]); + + // it overrides default account + assert_eq!(ap.new_dapps_default_address().unwrap(), 1.into()); + assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), 1.into()); + + ap.set_new_dapps_default_address(address).unwrap(); + assert_eq!(ap.new_dapps_default_address().unwrap(), address); + assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), address); } } diff --git a/ethcore/src/account_provider/stores.rs b/ethcore/src/account_provider/stores.rs index e4bd7e1b9..72bc04da6 100644 --- a/ethcore/src/account_provider/stores.rs +++ b/ethcore/src/account_provider/stores.rs @@ -92,13 +92,16 @@ impl AddressBook { #[derive(Debug, Default, Clone, Eq, PartialEq)] pub struct DappsSettings { /// A list of visible accounts - pub accounts: Vec
, + pub accounts: Option>, + /// Default account + pub default: Option
, } impl From for DappsSettings { fn from(s: JsonSettings) -> Self { DappsSettings { - accounts: s.accounts.into_iter().map(Into::into).collect(), + accounts: s.accounts.map(|accounts| accounts.into_iter().map(Into::into).collect()), + default: s.default.map(Into::into), } } } @@ -106,7 +109,8 @@ impl From for DappsSettings { impl From for JsonSettings { fn from(s: DappsSettings) -> Self { JsonSettings { - accounts: s.accounts.into_iter().map(Into::into).collect(), + accounts: s.accounts.map(|accounts| accounts.into_iter().map(Into::into).collect()), + default: s.default.map(Into::into), } } } @@ -114,14 +118,18 @@ impl From for JsonSettings { /// Dapps user settings #[derive(Debug, Clone, Eq, PartialEq)] pub enum NewDappsPolicy { - AllAccounts, + AllAccounts { + default: Address, + }, Whitelist(Vec
), } impl From for NewDappsPolicy { fn from(s: JsonNewDappsPolicy) -> Self { match s { - JsonNewDappsPolicy::AllAccounts => NewDappsPolicy::AllAccounts, + JsonNewDappsPolicy::AllAccounts { default } => NewDappsPolicy::AllAccounts { + default: default.into(), + }, JsonNewDappsPolicy::Whitelist(accounts) => NewDappsPolicy::Whitelist( accounts.into_iter().map(Into::into).collect() ), @@ -132,7 +140,9 @@ impl From for NewDappsPolicy { impl From for JsonNewDappsPolicy { fn from(s: NewDappsPolicy) -> Self { match s { - NewDappsPolicy::AllAccounts => JsonNewDappsPolicy::AllAccounts, + NewDappsPolicy::AllAccounts { default } => JsonNewDappsPolicy::AllAccounts { + default: default.into(), + }, NewDappsPolicy::Whitelist(accounts) => JsonNewDappsPolicy::Whitelist( accounts.into_iter().map(Into::into).collect() ), @@ -230,7 +240,9 @@ impl DappsSettingsStore { /// Returns current new dapps policy pub fn policy(&self) -> NewDappsPolicy { - self.policy.get("default").cloned().unwrap_or(NewDappsPolicy::AllAccounts) + self.policy.get("default").cloned().unwrap_or(NewDappsPolicy::AllAccounts { + default: 0.into(), + }) } /// Returns recent dapps with last accessed timestamp @@ -266,13 +278,22 @@ impl DappsSettingsStore { } /// Sets accounts for specific dapp. - pub fn set_accounts(&mut self, id: DappId, accounts: Vec
) { + pub fn set_accounts(&mut self, id: DappId, accounts: Option>) { { let mut settings = self.settings.entry(id).or_insert_with(DappsSettings::default); settings.accounts = accounts; } self.settings.save(JsonSettings::write); } + + /// Sets a default account for specific dapp. + pub fn set_default(&mut self, id: DappId, default: Address) { + { + let mut settings = self.settings.entry(id).or_insert_with(DappsSettings::default); + settings.default = Some(default); + } + self.settings.save(JsonSettings::write); + } } /// Disk-serializable HashMap @@ -385,13 +406,14 @@ mod tests { let mut b = DappsSettingsStore::new(&path); // when - b.set_accounts("dappOne".into(), vec![1.into(), 2.into()]); + b.set_accounts("dappOne".into(), Some(vec![1.into(), 2.into()])); // then let b = DappsSettingsStore::new(&path); assert_eq!(b.settings(), hash_map![ "dappOne".into() => DappsSettings { - accounts: vec![1.into(), 2.into()], + accounts: Some(vec![1.into(), 2.into()]), + default: None, } ]); } @@ -422,7 +444,9 @@ mod tests { let mut store = DappsSettingsStore::new(&path); // Test default policy - assert_eq!(store.policy(), NewDappsPolicy::AllAccounts); + assert_eq!(store.policy(), NewDappsPolicy::AllAccounts { + default: 0.into(), + }); // when store.set_policy(NewDappsPolicy::Whitelist(vec![1.into(), 2.into()])); diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index d7278d13a..0f1bd492c 100644 --- a/js/src/api/rpc/parity/parity.js +++ b/js/src/api/rpc/parity/parity.js @@ -170,18 +170,30 @@ export default class Parity { .execute('parity_generateSecretPhrase'); } - getDappsAddresses (dappId) { + getDappAddresses (dappId) { return this._transport - .execute('parity_getDappsAddresses', dappId) + .execute('parity_getDappAddresses', dappId) .then(outAddresses); } - getNewDappsWhitelist () { + getDappDefaultAddress (dappId) { return this._transport - .execute('parity_getNewDappsWhitelist') + .execute('parity_getDappDefaultAddress', dappId) + .then(outAddress); + } + + getNewDappsAddresses () { + return this._transport + .execute('parity_getNewDappsAddresses') .then((addresses) => addresses ? addresses.map(outAddress) : null); } + getNewDappsDefaultAddress () { + return this._transport + .execute('parity_getNewDappsDefaultAddress') + .then(outAddress); + } + getVaultMeta (vaultName) { return this._transport .execute('parity_getVaultMeta', vaultName) @@ -391,9 +403,14 @@ export default class Parity { .execute('parity_setAuthor', inAddress(address)); } - setDappsAddresses (dappId, addresses) { + setDappAddresses (dappId, addresses) { return this._transport - .execute('parity_setDappsAddresses', dappId, inAddresses(addresses)); + .execute('parity_setDappAddresses', dappId, inAddresses(addresses)); + } + + setDappDefaultAddress (dappId, address) { + return this._transport + .execute('parity_setDappDefaultAddress', dappId, address ? inAddress(address) : null); } setEngineSigner (address, password) { @@ -431,9 +448,14 @@ export default class Parity { .execute('parity_setMode', mode); } - setNewDappsWhitelist (addresses) { + setNewDappsAddresses (addresses) { return this._transport - .execute('parity_setNewDappsWhitelist', addresses ? inAddresses(addresses) : null); + .execute('parity_setNewDappsAddresses', addresses ? inAddresses(addresses) : null); + } + + setNewDappsDefaultAddress (address) { + return this._transport + .execute('parity_setNewDappsDefaultAddress', inAddress(address)); } setTransactionsLimit (quantity) { diff --git a/js/src/api/subscriptions/personal.js b/js/src/api/subscriptions/personal.js index 715f4cfe5..c1f070262 100644 --- a/js/src/api/subscriptions/personal.js +++ b/js/src/api/subscriptions/personal.js @@ -123,8 +123,10 @@ export default class Personal { this._accountsInfo(); return; - case 'parity_setDappsAddresses': - case 'parity_setNewDappsWhitelist': + case 'parity_setDappAddresses': + case 'parity_setDappDefaultAddress': + case 'parity_setNewDappsAddresses': + case 'parity_setNewDappsDefaultAddress': this._defaultAccount(true); this._listAccounts(); return; diff --git a/js/src/jsonrpc/interfaces/parity.js b/js/src/jsonrpc/interfaces/parity.js index 978c77f88..3d84ce4b4 100644 --- a/js/src/jsonrpc/interfaces/parity.js +++ b/js/src/jsonrpc/interfaces/parity.js @@ -1186,9 +1186,9 @@ export default { } }, - setDappsAddresses: { + setDappAddresses: { subdoc: SUBDOC_ACCOUNTS, - desc: 'Sets the available addresses for a dapp.', + desc: 'Sets the available addresses for a dapp. When provided with non-empty list changes the default account as well.', params: [ { type: String, @@ -1197,7 +1197,7 @@ export default { }, { type: Array, - desc: 'Array of available accounts available to the dapp.', + desc: 'Array of available accounts available to the dapp or `null` for default list.', example: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1'] } ], @@ -1208,7 +1208,7 @@ export default { } }, - getDappsAddresses: { + getDappAddresses: { subdoc: SUBDOC_ACCOUNTS, desc: 'Returns the list of accounts available to a specific dapp.', params: [ @@ -1225,13 +1225,52 @@ export default { } }, - setNewDappsWhitelist: { + setDappDefaultAddress: { + subdoc: SUBDOC_ACCOUNTS, + desc: 'Changes dapp default address. Does not affect other accounts exposed for this dapp, but default account will always be retured as the first one.', + params: [ + { + type: String, + desc: 'Dapp Id.', + example: 'web' + }, + { + type: Address, + desc: 'Default Address.', + example: '0x407d73d8a49eeb85d32cf465507dd71d507100c1' + } + ], + returns: { + type: Boolean, + desc: '`true` if the call was successful', + example: true + } + }, + + getDappDefaultAddress: { + subdoc: SUBDOC_ACCOUNTS, + desc: 'Returns a default account available to a specific dapp.', + params: [ + { + type: String, + desc: 'Dapp Id.', + example: 'web' + } + ], + returns: { + type: Address, + desc: 'Default Address', + example: '0x407d73d8a49eeb85d32cf465507dd71d507100c1' + } + }, + + setNewDappsAddresses: { subdoc: SUBDOC_ACCOUNTS, desc: 'Sets the list of accounts available to new dapps.', params: [ { type: Array, - desc: 'List of accounts available by default.', + desc: 'List of accounts available by default or `null` for all accounts.', example: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1'] } ], @@ -1242,7 +1281,7 @@ export default { } }, - getNewDappsWhitelist: { + getNewDappsAddresses: { subdoc: SUBDOC_ACCOUNTS, desc: 'Returns the list of accounts available to a new dapps.', params: [], @@ -1253,6 +1292,34 @@ export default { } }, + setNewDappsDefaultAddress: { + subdoc: SUBDOC_ACCOUNTS, + desc: 'Changes global default address. This setting may be overriden for a specific dapp.', + params: [ + { + type: Address, + desc: 'Default Address.', + example: '0x407d73d8a49eeb85d32cf465507dd71d507100c1' + } + ], + returns: { + type: Boolean, + desc: '`true` if the call was successful', + example: true + } + }, + + getNewDappsDefaultAddress: { + subdoc: SUBDOC_ACCOUNTS, + desc: 'Returns a default account available to dapps.', + params: [], + returns: { + type: Address, + desc: 'Default Address', + example: '0x407d73d8a49eeb85d32cf465507dd71d507100c1' + } + }, + listRecentDapps: { subdoc: SUBDOC_ACCOUNTS, desc: 'Returns a list of the most recent active dapps.', diff --git a/js/src/modals/DappPermissions/store.js b/js/src/modals/DappPermissions/store.js index 67f3de2e7..3239343c5 100644 --- a/js/src/modals/DappPermissions/store.js +++ b/js/src/modals/DappPermissions/store.js @@ -102,7 +102,7 @@ export default class Store { loadWhitelist () { return this._api.parity - .getNewDappsWhitelist() + .getNewDappsAddresses() .then((whitelist) => { this.setWhitelist(whitelist); }) @@ -113,7 +113,7 @@ export default class Store { updateWhitelist (whitelist) { return this._api.parity - .setNewDappsWhitelist(whitelist) + .setNewDappsAddresses(whitelist) .then(() => { this.setWhitelist(whitelist); }) diff --git a/js/src/modals/DappPermissions/store.spec.js b/js/src/modals/DappPermissions/store.spec.js index 1266bd057..20d927484 100644 --- a/js/src/modals/DappPermissions/store.spec.js +++ b/js/src/modals/DappPermissions/store.spec.js @@ -31,8 +31,8 @@ let store; function create () { api = { parity: { - getNewDappsWhitelist: sinon.stub().resolves(WHITELIST), - setNewDappsWhitelist: sinon.stub().resolves(true) + getNewDappsAddresses: sinon.stub().resolves(WHITELIST), + setNewDappsAddresses: sinon.stub().resolves(true) } }; @@ -46,7 +46,7 @@ describe('modals/DappPermissions/store', () => { describe('constructor', () => { it('retrieves the whitelist via api', () => { - expect(api.parity.getNewDappsWhitelist).to.be.calledOnce; + expect(api.parity.getNewDappsAddresses).to.be.calledOnce; }); it('sets the retrieved whitelist', () => { @@ -79,12 +79,12 @@ describe('modals/DappPermissions/store', () => { store.closeModal(); }); - it('calls setNewDappsWhitelist', () => { - expect(api.parity.setNewDappsWhitelist).to.have.been.calledOnce; + it('calls setNewDappsAddresses', () => { + expect(api.parity.setNewDappsAddresses).to.have.been.calledOnce; }); it('has the default account in first position', () => { - expect(api.parity.setNewDappsWhitelist).to.have.been.calledWith(['789', '456']); + expect(api.parity.setNewDappsAddresses).to.have.been.calledWith(['789', '456']); }); }); diff --git a/js/src/views/ParityBar/accountStore.js b/js/src/views/ParityBar/accountStore.js index bf854e194..fef00a142 100644 --- a/js/src/views/ParityBar/accountStore.js +++ b/js/src/views/ParityBar/accountStore.js @@ -61,7 +61,7 @@ export default class AccountStore { this.setDefaultAccount(address); return this._api.parity - .setNewDappsWhitelist(accounts) + .setNewDappsAddresses(accounts) .catch((error) => { console.warn('makeDefaultAccount', error); }); @@ -78,7 +78,7 @@ export default class AccountStore { return Promise .all([ - this._api.parity.getNewDappsWhitelist(), + this._api.parity.getNewDappsAddresses(), this._api.parity.allAccountsInfo() ]) .then(([whitelist, accounts]) => { diff --git a/js/src/views/ParityBar/accountStore.spec.js b/js/src/views/ParityBar/accountStore.spec.js index c13c62aa9..8b2a9a41a 100644 --- a/js/src/views/ParityBar/accountStore.spec.js +++ b/js/src/views/ParityBar/accountStore.spec.js @@ -76,8 +76,8 @@ describe('views/ParityBar/AccountStore', () => { store.setAccounts.restore(); }); - it('calls into parity_getNewDappsWhitelist', () => { - expect(api.parity.getNewDappsWhitelist).to.have.been.called; + it('calls into parity_getNewDappsAddresses', () => { + expect(api.parity.getNewDappsAddresses).to.have.been.called; }); it('calls into parity_allAccountsInfo', () => { @@ -104,8 +104,8 @@ describe('views/ParityBar/AccountStore', () => { return store.makeDefaultAccount(ACCOUNT_NEW); }); - it('calls into parity_setNewDappsWhitelist (with ordering)', () => { - expect(api.parity.setNewDappsWhitelist).to.have.been.calledWith([ + it('calls into parity_setNewDappsAddresses (with ordering)', () => { + expect(api.parity.setNewDappsAddresses).to.have.been.calledWith([ ACCOUNT_NEW, ACCOUNT_FIRST, ACCOUNT_DEFAULT ]); }); diff --git a/js/src/views/ParityBar/parityBar.test.js b/js/src/views/ParityBar/parityBar.test.js index 2623e4074..97c6e6251 100644 --- a/js/src/views/ParityBar/parityBar.test.js +++ b/js/src/views/ParityBar/parityBar.test.js @@ -36,8 +36,8 @@ function createApi () { parity: { defaultAccount: sinon.stub().resolves(ACCOUNT_DEFAULT), allAccountsInfo: sinon.stub().resolves(ACCOUNTS), - getNewDappsWhitelist: sinon.stub().resolves(null), - setNewDappsWhitelist: sinon.stub().resolves(true) + getNewDappsAddresses: sinon.stub().resolves(null), + setNewDappsAddresses: sinon.stub().resolves(true) } }; diff --git a/json/src/misc/dapps_settings.rs b/json/src/misc/dapps_settings.rs index 1e5565baf..5081c62b2 100644 --- a/json/src/misc/dapps_settings.rs +++ b/json/src/misc/dapps_settings.rs @@ -22,7 +22,9 @@ use hash; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct DappsSettings { /// A list of accounts this Dapp can see. - pub accounts: Vec, + pub accounts: Option>, + /// Default account + pub default: Option, } impl_serialization!(String => DappsSettings); @@ -40,7 +42,10 @@ impl_serialization!(String => DappsHistory); #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum NewDappsPolicy { /// All accounts are exposed by default. - AllAccounts, + AllAccounts { + /// Default account, which should be returned as the first one. + default: hash::Address, + }, /// Only accounts listed here are exposed by default for new dapps. Whitelist(Vec), } diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 9ad8196fe..01627ba28 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -221,7 +221,7 @@ impl EthClient where let store = take_weak!(self.accounts); store .note_dapp_used(dapp.clone()) - .and_then(|_| store.dapps_addresses(dapp)) + .and_then(|_| store.dapp_addresses(dapp)) .map_err(|e| errors::internal("Could not fetch accounts.", e)) } } @@ -308,10 +308,7 @@ impl Eth for EthClient where let author = move || { let mut miner = take_weak!(self.miner).author(); if miner == 0.into() { - let accounts = self.dapp_accounts(dapp.into())?; - if let Some(address) = accounts.get(0) { - miner = *address; - } + miner = self.dapp_accounts(dapp.into())?.get(0).cloned().unwrap_or_default(); } Ok(RpcH160::from(miner)) diff --git a/rpc/src/v1/impls/light/eth.rs b/rpc/src/v1/impls/light/eth.rs index 944b419f7..2e129d31e 100644 --- a/rpc/src/v1/impls/light/eth.rs +++ b/rpc/src/v1/impls/light/eth.rs @@ -187,7 +187,7 @@ impl Eth for EthClient { let accounts = self.accounts .note_dapp_used(dapp.clone()) - .and_then(|_| self.accounts.dapps_addresses(dapp)) + .and_then(|_| self.accounts.dapp_addresses(dapp)) .map_err(|e| errors::internal("Could not fetch accounts.", e)) .map(|accs| accs.into_iter().map(Into::::into).collect()); diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index 431c34e84..3fcc82c3a 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -18,7 +18,7 @@ use std::sync::{Arc, Weak}; use std::str::FromStr; use std::collections::{BTreeMap, HashSet}; -use futures::{self, Future, BoxFuture}; +use futures::{future, Future, BoxFuture}; use util::{RotatingLogger, Address}; use util::misc::version_data; @@ -118,7 +118,7 @@ impl Parity for ParityClient where let store = take_weak!(self.accounts); let dapp_accounts = store .note_dapp_used(dapp.clone().into()) - .and_then(|_| store.dapps_addresses(dapp.into())) + .and_then(|_| store.dapp_addresses(dapp.into())) .map_err(|e| errors::internal("Could not fetch accounts.", e))? .into_iter().collect::>(); @@ -146,16 +146,13 @@ impl Parity for ParityClient where fn default_account(&self, meta: Self::Metadata) -> BoxFuture { let dapp_id = meta.dapp_id(); - let default_account = move || { - Ok(take_weak!(self.accounts) - .dapps_addresses(dapp_id.into()) + future::ok( + take_weakf!(self.accounts) + .dapp_default_address(dapp_id.into()) + .map(Into::into) .ok() - .and_then(|accounts| accounts.get(0).cloned()) - .map(|acc| acc.into()) - .unwrap_or_default()) - }; - - futures::done(default_account()).boxed() + .unwrap_or_default() + ).boxed() } fn transactions_limit(&self) -> Result { diff --git a/rpc/src/v1/impls/parity_accounts.rs b/rpc/src/v1/impls/parity_accounts.rs index e28ea2510..60b615897 100644 --- a/rpc/src/v1/impls/parity_accounts.rs +++ b/rpc/src/v1/impls/parity_accounts.rs @@ -142,41 +142,69 @@ impl ParityAccounts for ParityAccountsClient { Ok(true) } - fn set_account_visibility(&self, _address: RpcH160, _dapp: RpcH256, _visible: bool) -> Result { - Ok(false) - } - - fn set_dapps_addresses(&self, dapp: DappId, addresses: Vec) -> Result { + fn set_dapp_addresses(&self, dapp: DappId, addresses: Option>) -> Result { let store = take_weak!(self.accounts); - store.set_dapps_addresses(dapp.into(), into_vec(addresses)) + store.set_dapp_addresses(dapp.into(), addresses.map(into_vec)) + .map_err(|e| errors::account("Couldn't set dapp addresses.", e)) + .map(|_| true) + } + + fn dapp_addresses(&self, dapp: DappId) -> Result, Error> { + let store = take_weak!(self.accounts); + + store.dapp_addresses(dapp.into()) + .map_err(|e| errors::account("Couldn't get dapp addresses.", e)) + .map(into_vec) + } + + fn set_dapp_default_address(&self, dapp: DappId, address: RpcH160) -> Result { + let store = take_weak!(self.accounts); + + store.set_dapp_default_address(dapp.into(), address.into()) + .map_err(|e| errors::account("Couldn't set dapp default address.", e)) + .map(|_| true) + } + + fn dapp_default_address(&self, dapp: DappId) -> Result { + let store = take_weak!(self.accounts); + + store.dapp_default_address(dapp.into()) + .map_err(|e| errors::account("Couldn't get dapp default address.", e)) + .map(Into::into) + } + + fn set_new_dapps_addresses(&self, addresses: Option>) -> Result { + let store = take_weak!(self.accounts); + + store + .set_new_dapps_addresses(addresses.map(into_vec)) .map_err(|e| errors::account("Couldn't set dapps addresses.", e)) .map(|_| true) } - fn dapps_addresses(&self, dapp: DappId) -> Result, Error> { + fn new_dapps_addresses(&self) -> Result>, Error> { let store = take_weak!(self.accounts); - store.dapps_addresses(dapp.into()) + store.new_dapps_addresses() .map_err(|e| errors::account("Couldn't get dapps addresses.", e)) - .map(into_vec) + .map(|accounts| accounts.map(into_vec)) } - fn set_new_dapps_whitelist(&self, whitelist: Option>) -> Result { + fn set_new_dapps_default_address(&self, address: RpcH160) -> Result { let store = take_weak!(self.accounts); - store - .set_new_dapps_whitelist(whitelist.map(into_vec)) - .map_err(|e| errors::account("Couldn't set dapps whitelist.", e)) + store.set_new_dapps_default_address(address.into()) + .map_err(|e| errors::account("Couldn't set new dapps default address.", e)) .map(|_| true) } - fn new_dapps_whitelist(&self) -> Result>, Error> { + fn new_dapps_default_address(&self) -> Result { let store = take_weak!(self.accounts); - store.new_dapps_whitelist() - .map_err(|e| errors::account("Couldn't get dapps whitelist.", e)) - .map(|accounts| accounts.map(into_vec)) + store.new_dapps_default_address() + .map_err(|e| errors::account("Couldn't get new dapps default address.", e)) + .map(Into::into) } fn recent_dapps(&self) -> Result, Error> { diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 966dc9a74..1f9cad92f 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -101,7 +101,7 @@ impl Personal for PersonalClient { let default = match request.from.as_ref() { Some(account) => Ok(account.clone().into()), None => accounts - .default_address(meta.dapp_id().into()) + .dapp_default_address(meta.dapp_id().into()) .map_err(|e| errors::account("Cannot find default account.", e)), }; diff --git a/rpc/src/v1/impls/signing.rs b/rpc/src/v1/impls/signing.rs index 5c52df79a..d737131a6 100644 --- a/rpc/src/v1/impls/signing.rs +++ b/rpc/src/v1/impls/signing.rs @@ -86,7 +86,7 @@ impl SigningQueueClient { let accounts = take_weakf!(self.accounts); let default_account = match default_account { DefaultAccount::Provided(acc) => acc, - DefaultAccount::ForDapp(dapp) => accounts.default_address(dapp).ok().unwrap_or_default(), + DefaultAccount::ForDapp(dapp) => accounts.dapp_default_address(dapp).ok().unwrap_or_default(), }; let dispatcher = self.dispatcher.clone(); diff --git a/rpc/src/v1/impls/signing_unsafe.rs b/rpc/src/v1/impls/signing_unsafe.rs index 1d778404c..d48935e44 100644 --- a/rpc/src/v1/impls/signing_unsafe.rs +++ b/rpc/src/v1/impls/signing_unsafe.rs @@ -55,7 +55,7 @@ impl SigningUnsafeClient { let accounts = take_weakf!(self.accounts); let default = match account { DefaultAccount::Provided(acc) => acc, - DefaultAccount::ForDapp(dapp) => accounts.default_address(dapp).ok().unwrap_or_default(), + DefaultAccount::ForDapp(dapp) => accounts.dapp_default_address(dapp).ok().unwrap_or_default(), }; let dis = self.dispatcher.clone(); diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index 40ae8c38c..2432b55e7 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -368,7 +368,7 @@ fn rpc_eth_gas_price() { fn rpc_eth_accounts() { let tester = EthTester::default(); let address = tester.accounts_provider.new_account("").unwrap(); - tester.accounts_provider.set_new_dapps_whitelist(None).unwrap(); + tester.accounts_provider.set_new_dapps_addresses(None).unwrap(); tester.accounts_provider.set_address_name(1.into(), "1".into()); tester.accounts_provider.set_address_name(10.into(), "10".into()); @@ -377,14 +377,14 @@ fn rpc_eth_accounts() { let response = r#"{"jsonrpc":"2.0","result":[""#.to_owned() + &format!("0x{:?}", address) + r#""],"id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); - tester.accounts_provider.set_new_dapps_whitelist(Some(vec![1.into()])).unwrap(); + tester.accounts_provider.set_new_dapps_addresses(Some(vec![1.into()])).unwrap(); // even with some account it should return empty list (no dapp detected) let request = r#"{"jsonrpc": "2.0", "method": "eth_accounts", "params": [], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":["0x0000000000000000000000000000000000000001"],"id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); // when we add visible address it should return that. - tester.accounts_provider.set_dapps_addresses("app1".into(), vec![10.into()]).unwrap(); + tester.accounts_provider.set_dapp_addresses("app1".into(), Some(vec![10.into()])).unwrap(); let request = r#"{"jsonrpc": "2.0", "method": "eth_accounts", "params": [], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":["0x000000000000000000000000000000000000000a"],"id":1}"#; let mut meta = Metadata::default(); diff --git a/rpc/src/v1/tests/mocked/parity.rs b/rpc/src/v1/tests/mocked/parity.rs index 1bf557717..a587554a3 100644 --- a/rpc/src/v1/tests/mocked/parity.rs +++ b/rpc/src/v1/tests/mocked/parity.rs @@ -110,17 +110,19 @@ fn rpc_parity_accounts_info() { assert_eq!(accounts.len(), 1); let address = accounts[0]; - deps.accounts.set_account_name(address.clone(), "Test".to_owned()).unwrap(); - deps.accounts.set_account_meta(address.clone(), "{foo: 69}".to_owned()).unwrap(); + deps.accounts.set_address_name(1.into(), "XX".into()); + deps.accounts.set_account_name(address.clone(), "Test".into()).unwrap(); + deps.accounts.set_account_meta(address.clone(), "{foo: 69}".into()).unwrap(); let request = r#"{"jsonrpc": "2.0", "method": "parity_accountsInfo", "params": [], "id": 1}"#; let response = format!("{{\"jsonrpc\":\"2.0\",\"result\":{{\"0x{}\":{{\"name\":\"Test\"}}}},\"id\":1}}", address.hex()); assert_eq!(io.handle_request_sync(request), Some(response)); // Change the whitelist - deps.accounts.set_new_dapps_whitelist(Some(vec![1.into()])).unwrap(); + let address = Address::from(1); + deps.accounts.set_new_dapps_addresses(Some(vec![address.clone()])).unwrap(); let request = r#"{"jsonrpc": "2.0", "method": "parity_accountsInfo", "params": [], "id": 1}"#; - let response = format!("{{\"jsonrpc\":\"2.0\",\"result\":{{}},\"id\":1}}"); + let response = format!("{{\"jsonrpc\":\"2.0\",\"result\":{{\"0x{}\":{{\"name\":\"XX\"}}}},\"id\":1}}", address.hex()); assert_eq!(io.handle_request_sync(request), Some(response)); } diff --git a/rpc/src/v1/tests/mocked/parity_accounts.rs b/rpc/src/v1/tests/mocked/parity_accounts.rs index 6c518945f..304ffd45e 100644 --- a/rpc/src/v1/tests/mocked/parity_accounts.rs +++ b/rpc/src/v1/tests/mocked/parity_accounts.rs @@ -125,48 +125,87 @@ fn rpc_parity_set_and_get_dapps_accounts() { // given let tester = setup(); tester.accounts.set_address_name(10.into(), "10".into()); - assert_eq!(tester.accounts.dapps_addresses("app1".into()).unwrap(), vec![]); + assert_eq!(tester.accounts.dapp_addresses("app1".into()).unwrap(), vec![]); // when - let request = r#"{"jsonrpc": "2.0", "method": "parity_setDappsAddresses","params":["app1",["0x000000000000000000000000000000000000000a","0x0000000000000000000000000000000000000001"]], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_setDappAddresses","params":["app1",["0x000000000000000000000000000000000000000a","0x0000000000000000000000000000000000000001"]], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); // then - assert_eq!(tester.accounts.dapps_addresses("app1".into()).unwrap(), vec![10.into()]); - let request = r#"{"jsonrpc": "2.0", "method": "parity_getDappsAddresses","params":["app1"], "id": 1}"#; + assert_eq!(tester.accounts.dapp_addresses("app1".into()).unwrap(), vec![10.into()]); + let request = r#"{"jsonrpc": "2.0", "method": "parity_getDappAddresses","params":["app1"], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":["0x000000000000000000000000000000000000000a"],"id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); } +#[test] +fn rpc_parity_set_and_get_dapp_default_address() { + // given + let tester = setup(); + tester.accounts.set_address_name(10.into(), "10".into()); + assert_eq!(tester.accounts.dapp_addresses("app1".into()).unwrap(), vec![]); + + // when + let request = r#"{"jsonrpc": "2.0", "method": "parity_setDappDefaultAddress","params":["app1", "0x000000000000000000000000000000000000000a"], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; + assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); + + // then + assert_eq!(tester.accounts.dapp_addresses("app1".into()).unwrap(), vec![10.into()]); + let request = r#"{"jsonrpc": "2.0", "method": "parity_getDappDefaultAddress","params":["app1"], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":"0x000000000000000000000000000000000000000a","id":1}"#; + assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); +} + #[test] fn rpc_parity_set_and_get_new_dapps_whitelist() { // given let tester = setup(); // when set to whitelist - let request = r#"{"jsonrpc": "2.0", "method": "parity_setNewDappsWhitelist","params":[["0x000000000000000000000000000000000000000a"]], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_setNewDappsAddresses","params":[["0x000000000000000000000000000000000000000a"]], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); // then - assert_eq!(tester.accounts.new_dapps_whitelist().unwrap(), Some(vec![10.into()])); - let request = r#"{"jsonrpc": "2.0", "method": "parity_getNewDappsWhitelist","params":[], "id": 1}"#; + assert_eq!(tester.accounts.new_dapps_addresses().unwrap(), Some(vec![10.into()])); + let request = r#"{"jsonrpc": "2.0", "method": "parity_getNewDappsAddresses","params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":["0x000000000000000000000000000000000000000a"],"id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); // when set to empty - let request = r#"{"jsonrpc": "2.0", "method": "parity_setNewDappsWhitelist","params":[null], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_setNewDappsAddresses","params":[null], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); // then - assert_eq!(tester.accounts.new_dapps_whitelist().unwrap(), None); - let request = r#"{"jsonrpc": "2.0", "method": "parity_getNewDappsWhitelist","params":[], "id": 1}"#; + assert_eq!(tester.accounts.new_dapps_addresses().unwrap(), None); + let request = r#"{"jsonrpc": "2.0", "method": "parity_getNewDappsAddresses","params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":null,"id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); } +#[test] +fn rpc_parity_set_and_get_new_dapps_default_address() { + // given + let tester = setup(); + tester.accounts.set_address_name(10.into(), "10".into()); + assert_eq!(tester.accounts.new_dapps_default_address().unwrap(), 0.into()); + + // when + let request = r#"{"jsonrpc": "2.0", "method": "parity_setNewDappsDefaultAddress","params":["0x000000000000000000000000000000000000000a"], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; + assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); + + // then + assert_eq!(tester.accounts.new_dapps_default_address().unwrap(), 10.into()); + let request = r#"{"jsonrpc": "2.0", "method": "parity_getNewDappsDefaultAddress","params":[], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":"0x000000000000000000000000000000000000000a","id":1}"#; + assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); +} + + #[test] fn rpc_parity_recent_dapps() { // given diff --git a/rpc/src/v1/traits/parity_accounts.rs b/rpc/src/v1/traits/parity_accounts.rs index 576786073..a3a9a8d9f 100644 --- a/rpc/src/v1/traits/parity_accounts.rs +++ b/rpc/src/v1/traits/parity_accounts.rs @@ -70,28 +70,51 @@ build_rpc_trait! { #[rpc(name = "parity_setAccountMeta")] fn set_account_meta(&self, H160, String) -> Result; - /// Sets account visibility. - /// @unimplemented - #[rpc(name = "parity_setAccountVisiblity")] - fn set_account_visibility(&self, H160, H256, bool) -> Result; - - /// Sets accounts exposed for particular dapp. - #[rpc(name = "parity_setDappsAddresses")] - fn set_dapps_addresses(&self, DappId, Vec) -> Result; + /// Sets addresses exposed for particular dapp. + /// Setting a non-empty list will also override default account. + /// Setting `None` will resets visible account to what's visible for new dapps + /// (does not affect default account though) + #[rpc(name = "parity_setDappAddresses")] + fn set_dapp_addresses(&self, DappId, Option>) -> Result; /// Gets accounts exposed for particular dapp. - #[rpc(name = "parity_getDappsAddresses")] - fn dapps_addresses(&self, DappId) -> Result, Error>; + #[rpc(name = "parity_getDappAddresses")] + fn dapp_addresses(&self, DappId) -> Result, Error>; + + /// Changes dapp default address. + /// Does not affect other accounts exposed for this dapp, but + /// default account will always be retured as the first one. + #[rpc(name = "parity_setDappDefaultAddress")] + fn set_dapp_default_address(&self, DappId, H160) -> Result; + + /// Returns current dapp default address. + /// If not set explicite for the dapp will return global default. + #[rpc(name = "parity_getDappDefaultAddress")] + fn dapp_default_address(&self, DappId) -> Result; /// Sets accounts exposed for new dapps. - /// `None` means that all accounts will be exposed. - #[rpc(name = "parity_setNewDappsWhitelist")] - fn set_new_dapps_whitelist(&self, Option>) -> Result; + /// Setting a non-empty list will also override default account. + /// Setting `None` exposes all internal-managed accounts. + /// (does not affect default account though) + #[rpc(name = "parity_setNewDappsAddresses")] + fn set_new_dapps_addresses(&self, Option>) -> Result; /// Gets accounts exposed for new dapps. - /// `None` means that all accounts will be exposed. - #[rpc(name = "parity_getNewDappsWhitelist")] - fn new_dapps_whitelist(&self) -> Result>, Error>; + /// `None` means that all accounts are exposes. + #[rpc(name = "parity_getNewDappsAddresses")] + fn new_dapps_addresses(&self) -> Result>, Error>; + + /// Changes default address for new dapps (global default address) + /// Does not affect other accounts exposed for new dapps, but + /// default account will always be retured as the first one. + #[rpc(name = "parity_setNewDappsDefaultAddress")] + fn set_new_dapps_default_address(&self, H160) -> Result; + + /// Returns current default address for new dapps (global default address) + /// In case it's not set explicite will return first available account. + /// If no accounts are available will return `0x0` + #[rpc(name = "parity_getNewDappsDefaultAddress")] + fn new_dapps_default_address(&self) -> Result; /// Returns identified dapps that recently used RPC /// Includes last usage timestamp. From 348559491af0e3ab2efacc50de119a3e526f4efb Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Mon, 20 Feb 2017 16:34:16 +0100 Subject: [PATCH 45/51] Bump CID version to allow compilation on all platforms --- ipfs/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipfs/Cargo.toml b/ipfs/Cargo.toml index d7698ac74..7cc381ee8 100644 --- a/ipfs/Cargo.toml +++ b/ipfs/Cargo.toml @@ -11,5 +11,5 @@ ethcore-util = { path = "../util" } rlp = { path = "../util/rlp" } mime = "0.2" hyper = { default-features = false, git = "https://github.com/ethcore/hyper" } -cid = "0.2" +cid = "0.2.1" multihash = "0.5" From 44769fcd4a065f0a9debb517a6964f6e72745af9 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Mon, 20 Feb 2017 16:34:38 +0100 Subject: [PATCH 46/51] Show only known accounts/wallets/addresses on Home (#4612) * Don't render unknown entries * Only render non-null items * Remove (now) invalid failing test --- js/src/ui/SectionList/sectionList.js | 16 ++++++++++------ js/src/ui/SectionList/sectionList.spec.js | 4 ---- js/src/views/Home/Accounts/accounts.js | 7 ++++++- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/js/src/ui/SectionList/sectionList.js b/js/src/ui/SectionList/sectionList.js index b326b9ed8..f499d8962 100644 --- a/js/src/ui/SectionList/sectionList.js +++ b/js/src/ui/SectionList/sectionList.js @@ -46,10 +46,18 @@ export default class SectionList extends Component { return null; } + const rendered = items + .map(this.renderItem) + .filter((item) => item); + + if (!rendered.length) { + return null; + } + return (
{ this.renderOverlay() } - { chunkArray(items, ITEMS_PER_ROW).map(this.renderRow) } + { chunkArray(rendered, ITEMS_PER_ROW).map(this.renderRow) }
); } @@ -74,11 +82,7 @@ export default class SectionList extends Component { className={ styles.row } key={ `row_${index}` } > - { - row - .map(this.renderItem) - .filter((item) => item) - } + { row }
); } diff --git a/js/src/ui/SectionList/sectionList.spec.js b/js/src/ui/SectionList/sectionList.spec.js index 9022db79d..29c6d1d56 100644 --- a/js/src/ui/SectionList/sectionList.spec.js +++ b/js/src/ui/SectionList/sectionList.spec.js @@ -74,10 +74,6 @@ describe('SectionList', () => { it('adds a key for the row', () => { expect(row.key).to.be.ok; }); - - it('calls renderItem for the items', () => { - expect(instance.renderItem).to.have.been.calledTwice; - }); }); describe('renderItem', () => { diff --git a/js/src/views/Home/Accounts/accounts.js b/js/src/views/Home/Accounts/accounts.js index 73ffce43b..f397994ff 100644 --- a/js/src/views/Home/Accounts/accounts.js +++ b/js/src/views/Home/Accounts/accounts.js @@ -80,7 +80,12 @@ class Accounts extends Component { return null; } - const account = accountsInfo[history.entry] || { meta: {} }; + const account = accountsInfo[history.entry]; + + if (!account) { + return null; + } + let linkType = 'addresses'; if (account.uuid) { From ac6180a6feebb0be31c06ca77d5594698743bd04 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 20 Feb 2017 15:35:53 +0000 Subject: [PATCH 47/51] seals_internally (#4613) --- ethcore/src/engines/authority_round.rs | 4 +-- ethcore/src/engines/basic_authority.rs | 9 ++++--- ethcore/src/engines/instant_seal.rs | 2 +- ethcore/src/engines/mod.rs | 9 +++---- ethcore/src/engines/tendermint/mod.rs | 4 +-- ethcore/src/miner/miner.rs | 37 +++++++++++++------------- 6 files changed, 33 insertions(+), 32 deletions(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 9c69ce6ad..4f99a644e 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -220,8 +220,8 @@ impl Engine for AuthorityRound { }); } - fn is_sealer(&self, author: &Address) -> Option { - Some(self.validators.contains(author)) + fn seals_internally(&self) -> Option { + Some(self.validators.contains(&self.signer.address())) } /// Attempt to seal the block internally. diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 5d1abd064..50051bf7e 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -103,8 +103,8 @@ impl Engine for BasicAuthority { }); } - fn is_sealer(&self, author: &Address) -> Option { - Some(self.validators.contains(author)) + fn seals_internally(&self) -> Option { + Some(self.validators.contains(&self.signer.address())) } /// Attempt to seal the block internally. @@ -268,7 +268,8 @@ mod tests { let authority = tap.insert_account(Secret::from_slice(&"".sha3()).unwrap(), "").unwrap(); let engine = new_test_authority().engine; - assert!(!engine.is_sealer(&Address::default()).unwrap()); - assert!(engine.is_sealer(&authority).unwrap()); + assert!(!engine.seals_internally().unwrap()); + engine.set_signer(Arc::new(tap), authority, "".into()); + assert!(engine.seals_internally().unwrap()); } } diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index c709398e3..d2deea702 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -56,7 +56,7 @@ impl Engine for InstantSeal { Schedule::new_post_eip150(usize::max_value(), true, true, true) } - fn is_sealer(&self, _author: &Address) -> Option { Some(true) } + fn seals_internally(&self) -> Option { Some(true) } fn generate_seal(&self, _block: &ExecutedBlock) -> Seal { Seal::Regular(Vec::new()) diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index a22410cee..2320f49a0 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -128,11 +128,10 @@ pub trait Engine : Sync + Send { /// Block transformation functions, after the transactions. fn on_close_block(&self, _block: &mut ExecutedBlock) {} - /// If Some(true) this author is able to generate seals, generate_seal has to be implemented. - /// None indicates that this Engine never seals internally regardless of author (e.g. PoW). - fn is_sealer(&self, _author: &Address) -> Option { None } - /// Checks if default address is able to seal. - fn is_default_sealer(&self) -> Option { self.is_sealer(&Default::default()) } + /// None means that it requires external input (e.g. PoW) to seal a block. + /// Some(true) means the engine is currently prime for seal generation (i.e. node is the current validator). + /// Some(false) means that the node might seal internally but is not qualified now. + fn seals_internally(&self) -> Option { None } /// Attempt to seal the block internally. /// /// If `Some` is returned, then you get a valid seal. diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index e4bb2ea2f..bc4786526 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -410,8 +410,8 @@ impl Engine for Tendermint { } /// Should this node participate. - fn is_sealer(&self, address: &Address) -> Option { - Some(self.is_authority(address)) + fn seals_internally(&self) -> Option { + Some(self.is_authority(&self.signer.address())) } /// Attempt to seal generate a proposal seal. diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index b4a7f2327..410123c9a 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -215,8 +215,6 @@ pub struct Miner { sealing_block_last_request: Mutex, // for sealing... options: MinerOptions, - /// Does the node perform internal (without work) sealing. - pub seals_internally: bool, gas_range_target: RwLock<(U256, U256)>, author: RwLock
, @@ -275,9 +273,8 @@ impl Miner { queue: UsingQueue::new(options.work_queue_size), enabled: options.force_sealing || !options.new_work_notify.is_empty() - || spec.engine.is_default_sealer().unwrap_or(false) + || spec.engine.seals_internally().is_some() }), - seals_internally: spec.engine.is_default_sealer().is_some(), gas_range_target: RwLock::new((U256::zero(), U256::zero())), author: RwLock::new(Address::default()), extra_data: RwLock::new(Vec::new()), @@ -455,7 +452,7 @@ impl Miner { let last_request = *self.sealing_block_last_request.lock(); let should_disable_sealing = !self.forced_sealing() && !has_local_transactions - && !self.seals_internally + && self.engine.seals_internally().is_none() && best_block > last_request && best_block - last_request > SEALING_TIMEOUT_IN_BLOCKS; @@ -765,21 +762,21 @@ impl MinerService for Miner { } fn set_author(&self, author: Address) { - if self.seals_internally { + if self.engine.seals_internally().is_some() { let mut sealing_work = self.sealing_work.lock(); - sealing_work.enabled = self.engine.is_sealer(&author).unwrap_or(false); + sealing_work.enabled = true; } *self.author.write() = author; } fn set_engine_signer(&self, address: Address, password: String) -> Result<(), AccountError> { - if self.seals_internally { + if self.engine.seals_internally().is_some() { if let Some(ref ap) = self.accounts { ap.sign(address.clone(), Some(password.clone()), Default::default())?; // Limit the scope of the locks. { let mut sealing_work = self.sealing_work.lock(); - sealing_work.enabled = self.engine.is_sealer(&address).unwrap_or(false); + sealing_work.enabled = true; *self.author.write() = address; } // -------------------------------------------------------------------------- @@ -914,7 +911,7 @@ impl MinerService for Miner { if imported.is_ok() && self.options.reseal_on_own_tx && self.tx_reseal_allowed() { // Make sure to do it after transaction is imported and lock is droped. // We need to create pending block and enable sealing. - if self.seals_internally || !self.prepare_work_sealing(chain) { + if self.engine.seals_internally().unwrap_or(false) || !self.prepare_work_sealing(chain) { // If new block has not been prepared (means we already had one) // or Engine might be able to seal internally, // we need to update sealing. @@ -1071,14 +1068,18 @@ impl MinerService for Miner { // -------------------------------------------------------------------------- trace!(target: "miner", "update_sealing: preparing a block"); let (block, original_work_hash) = self.prepare_block(chain); - if self.seals_internally { - trace!(target: "miner", "update_sealing: engine indicates internal sealing"); - if self.seal_and_import_block_internally(chain, block) { - trace!(target: "miner", "update_sealing: imported internally sealed block"); - } - } else { - trace!(target: "miner", "update_sealing: engine does not seal internally, preparing work"); - self.prepare_work(block, original_work_hash); + match self.engine.seals_internally() { + Some(true) => { + trace!(target: "miner", "update_sealing: engine indicates internal sealing"); + if self.seal_and_import_block_internally(chain, block) { + trace!(target: "miner", "update_sealing: imported internally sealed block"); + } + }, + None => { + trace!(target: "miner", "update_sealing: engine does not seal internally, preparing work"); + self.prepare_work(block, original_work_hash) + }, + _ => trace!(target: "miner", "update_sealing: engine is not keen to seal internally right now") } } } From 9e210e5eda80f11a0a5f93a742d9ff8a173b06a2 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Mon, 20 Feb 2017 16:40:01 +0100 Subject: [PATCH 48/51] Vault Management UI (first round) (#4446) * Add RPCs for parity_vault (create, open, list, etc.) * WIP * WIP * WIP * WIP (create should create) * Create & close working * WIP * WIP * WIP * Open & Close now working * WIP * WIP * Merge relevant changes from js-home * Hover actions * WIP (start of account assignment) * Open, Close & Account assignment works * Fix margins * UI updates * Update tests * Add the parity_{get|set}VaultMeta calls * Handle metadata * Adjust padding in Open/Close modals * moveAccounts take both in and out * Adjust padding * Fix stretch * Optimize hover stretch * pre-merge * Cleanup variable naming (duplication) * Rename Vault{Close,Open} -> Vault{Lock,Unlock} * clearVaultFields uses setters * TODO for small Portal sizes * Vaults rendering tests * .only * libusb compile * VaultCard rendering tests * Update message keys (rename gone rouge) * Display passwordHint op vault unlock * Update failing tests * Manually dispatch allAccountsInfo when move completed * Open/Close always shows vault image in colour * Password submit submits modal (PR comment) * Add link to account --- js/src/api/subscriptions/personal.js | 1 - js/src/modals/CreateAccount/createAccount.css | 29 +- js/src/modals/CreateAccount/errors.js | 19 +- js/src/modals/VaultAccounts/index.js | 17 + js/src/modals/VaultAccounts/vaultAccounts.css | 48 ++ js/src/modals/VaultAccounts/vaultAccounts.js | 195 +++++++ .../VaultAccounts/vaultAccounts.spec.js | 179 ++++++ js/src/modals/VaultCreate/index.js | 17 + js/src/modals/VaultCreate/vaultCreate.css | 38 ++ js/src/modals/VaultCreate/vaultCreate.js | 227 ++++++++ js/src/modals/VaultCreate/vaultCreate.spec.js | 162 ++++++ js/src/modals/VaultLock/index.js | 17 + js/src/modals/VaultLock/vaultLock.js | 92 ++++ js/src/modals/VaultLock/vaultLock.spec.js | 131 +++++ js/src/modals/VaultUnlock/index.js | 17 + js/src/modals/VaultUnlock/vaultUnlock.css | 27 + js/src/modals/VaultUnlock/vaultUnlock.js | 118 ++++ js/src/modals/VaultUnlock/vaultUnlock.spec.js | 146 +++++ js/src/modals/index.js | 64 +-- js/src/routes.js | 3 +- js/src/ui/ConfirmDialog/confirmDialog.css | 1 + js/src/ui/ConfirmDialog/confirmDialog.js | 59 +- js/src/ui/ConfirmDialog/confirmDialog.spec.js | 112 ++-- js/src/ui/Container/Title/title.css | 4 + js/src/ui/Container/container.css | 18 +- js/src/ui/Icons/index.js | 3 + js/src/ui/Icons/index.spec.js | 28 + js/src/ui/Page/page.js | 24 +- js/src/ui/Page/page.spec.js | 79 +++ js/src/ui/Portal/portal.css | 29 +- js/src/ui/Portal/portal.js | 6 +- js/src/ui/Title/title.css | 9 +- js/src/ui/Title/title.js | 14 +- js/src/ui/Title/title.spec.js | 90 +++ js/src/ui/VaultCard/Layout/index.js | 17 + js/src/ui/VaultCard/Layout/layout.css | 45 ++ js/src/ui/VaultCard/Layout/layout.js | 66 +++ js/src/ui/VaultCard/Layout/layout.spec.js | 90 +++ js/src/ui/VaultCard/index.js | 17 + js/src/ui/VaultCard/vaultCard.css | 62 +++ js/src/ui/VaultCard/vaultCard.js | 102 ++++ js/src/ui/VaultCard/vaultCard.spec.js | 94 ++++ js/src/ui/index.js | 1 + js/src/views/Accounts/accounts.js | 47 +- js/src/views/Vaults/index.js | 17 + js/src/views/Vaults/store.js | 323 +++++++++++ js/src/views/Vaults/store.spec.js | 516 ++++++++++++++++++ js/src/views/Vaults/vaults.css | 25 + js/src/views/Vaults/vaults.js | 193 +++++++ js/src/views/Vaults/vaults.spec.js | 185 +++++++ js/src/views/Vaults/vaults.test.js | 90 +++ js/src/views/index.js | 1 + 52 files changed, 3722 insertions(+), 192 deletions(-) create mode 100644 js/src/modals/VaultAccounts/index.js create mode 100644 js/src/modals/VaultAccounts/vaultAccounts.css create mode 100644 js/src/modals/VaultAccounts/vaultAccounts.js create mode 100644 js/src/modals/VaultAccounts/vaultAccounts.spec.js create mode 100644 js/src/modals/VaultCreate/index.js create mode 100644 js/src/modals/VaultCreate/vaultCreate.css create mode 100644 js/src/modals/VaultCreate/vaultCreate.js create mode 100644 js/src/modals/VaultCreate/vaultCreate.spec.js create mode 100644 js/src/modals/VaultLock/index.js create mode 100644 js/src/modals/VaultLock/vaultLock.js create mode 100644 js/src/modals/VaultLock/vaultLock.spec.js create mode 100644 js/src/modals/VaultUnlock/index.js create mode 100644 js/src/modals/VaultUnlock/vaultUnlock.css create mode 100644 js/src/modals/VaultUnlock/vaultUnlock.js create mode 100644 js/src/modals/VaultUnlock/vaultUnlock.spec.js create mode 100644 js/src/ui/Icons/index.spec.js create mode 100644 js/src/ui/Page/page.spec.js create mode 100644 js/src/ui/Title/title.spec.js create mode 100644 js/src/ui/VaultCard/Layout/index.js create mode 100644 js/src/ui/VaultCard/Layout/layout.css create mode 100644 js/src/ui/VaultCard/Layout/layout.js create mode 100644 js/src/ui/VaultCard/Layout/layout.spec.js create mode 100644 js/src/ui/VaultCard/index.js create mode 100644 js/src/ui/VaultCard/vaultCard.css create mode 100644 js/src/ui/VaultCard/vaultCard.js create mode 100644 js/src/ui/VaultCard/vaultCard.spec.js create mode 100644 js/src/views/Vaults/index.js create mode 100644 js/src/views/Vaults/store.js create mode 100644 js/src/views/Vaults/store.spec.js create mode 100644 js/src/views/Vaults/vaults.css create mode 100644 js/src/views/Vaults/vaults.js create mode 100644 js/src/views/Vaults/vaults.spec.js create mode 100644 js/src/views/Vaults/vaults.test.js diff --git a/js/src/api/subscriptions/personal.js b/js/src/api/subscriptions/personal.js index c1f070262..15b037b42 100644 --- a/js/src/api/subscriptions/personal.js +++ b/js/src/api/subscriptions/personal.js @@ -119,7 +119,6 @@ export default class Personal { case 'parity_removeAddress': case 'parity_setAccountName': case 'parity_setAccountMeta': - case 'parity_changeVault': this._accountsInfo(); return; diff --git a/js/src/modals/CreateAccount/createAccount.css b/js/src/modals/CreateAccount/createAccount.css index 5c8bc7e5c..d58e0edfd 100644 --- a/js/src/modals/CreateAccount/createAccount.css +++ b/js/src/modals/CreateAccount/createAccount.css @@ -19,23 +19,24 @@ line-height: 1.618em; } -.password { - flex: 0 1 50%; - width: 50%; - box-sizing: border-box; - - &:nth-child(odd) { - padding-right: 0.25rem; - } - - &:nth-child(even) { - padding-left: 0.25rem; - } -} - +/* TODO: 2 column layout can be made generic, now duplicated in Vaults */ .passwords { display: flex; flex-wrap: wrap; + + .password { + box-sizing: border-box; + flex: 0 1 50%; + width: 50%; + + &:nth-child(odd) { + padding-right: 0.25rem; + } + + &:nth-child(even) { + padding-left: 0.25rem; + } + } } .identities, .selector { diff --git a/js/src/modals/CreateAccount/errors.js b/js/src/modals/CreateAccount/errors.js index 487bf9729..bb5708275 100644 --- a/js/src/modals/CreateAccount/errors.js +++ b/js/src/modals/CreateAccount/errors.js @@ -18,37 +18,44 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; export default { + duplicateName: ( + + ), + noFile: ( ), noKey: ( ), noMatchPassword: ( ), noName: ( ), invalidKey: ( ) diff --git a/js/src/modals/VaultAccounts/index.js b/js/src/modals/VaultAccounts/index.js new file mode 100644 index 000000000..506a569f3 --- /dev/null +++ b/js/src/modals/VaultAccounts/index.js @@ -0,0 +1,17 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default from './vaultAccounts'; diff --git a/js/src/modals/VaultAccounts/vaultAccounts.css b/js/src/modals/VaultAccounts/vaultAccounts.css new file mode 100644 index 000000000..1960376f3 --- /dev/null +++ b/js/src/modals/VaultAccounts/vaultAccounts.css @@ -0,0 +1,48 @@ +/* Copyright 2015-2017 Parity Technologies (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: These overlap with DappPermissions now, make DRY */ +/* (selection component or just styles?) */ +.iconDisabled { + opacity: 0.15; +} + +.item { + display: flex; + flex: 1; + position: relative; + + .overlay { + position: absolute; + right: 0.5em; + top: 0.5em; + } +} + +.selected, +.unselected { + margin-bottom: 0.25em; + width: 100%; + + &:focus { + outline: none; + } +} + +.selected { + background: rgba(255, 255, 255, 0.15) !important; +} diff --git a/js/src/modals/VaultAccounts/vaultAccounts.js b/js/src/modals/VaultAccounts/vaultAccounts.js new file mode 100644 index 000000000..96f170f79 --- /dev/null +++ b/js/src/modals/VaultAccounts/vaultAccounts.js @@ -0,0 +1,195 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { observer } from 'mobx-react'; +import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; + +import { newError } from '~/redux/actions'; +import { personalAccountsInfo } from '~/redux/providers/personalActions'; +import { AccountCard, Button, Portal, SectionList } from '~/ui'; +import { CancelIcon, CheckIcon } from '~/ui/Icons'; + +import styles from './vaultAccounts.css'; + +@observer +class VaultAccounts extends Component { + static contextTypes = { + api: PropTypes.object.isRequired + }; + + static propTypes = { + accounts: PropTypes.object.isRequired, + newError: PropTypes.func.isRequired, + personalAccountsInfo: PropTypes.func.isRequired, + vaultStore: PropTypes.object.isRequired + }; + + render () { + const { accounts } = this.props; + const { isBusyAccounts, isModalAccountsOpen, selectedAccounts } = this.props.vaultStore; + + if (!isModalAccountsOpen) { + return null; + } + + const vaultAccounts = Object + .keys(accounts) + .filter((address) => accounts[address].uuid) + .map((address) => accounts[address]); + + return ( + } + key='cancel' + label={ + + } + onClick={ this.onClose } + />, +