From 3c634701ddc6d6694f2760548c20ae4df869f4e9 Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Tue, 14 Feb 2017 19:30:37 +0100 Subject: [PATCH 01/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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 da696e4a1f7282e3c5a6e228e3121f3c41e5ba06 Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Thu, 16 Feb 2017 18:39:13 +0100 Subject: [PATCH 11/11] 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();