Merge pull request #4545 from ethcore/mh-ipfs

IPFS MVP
This commit is contained in:
Robert Habermeier 2017-02-17 17:19:56 +01:00 committed by GitHub
commit 39237e9d15
14 changed files with 726 additions and 2 deletions

72
Cargo.lock generated
View File

@ -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-api 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"
@ -1316,6 +1332,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"
@ -1572,6 +1605,19 @@ dependencies = [
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parity-ipfs-api"
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)",
"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",
]
[[package]]
name = "parity-reactor"
version = "0.1.0"
@ -1827,6 +1873,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"
@ -2336,6 +2391,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"
@ -2363,6 +2423,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"
@ -2460,6 +2525,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"
@ -2474,6 +2540,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)" = "<none>"
"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"
@ -2541,6 +2608,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)" = "<none>"
"checksum nanomsg-sys 0.5.0 (git+https://github.com/ethcore/nanomsg.rs.git)" = "<none>"
"checksum native-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4e52995154bb6f0b41e4379a279482c9387c1632e3798ba4e511ef8c54ee09"
@ -2591,6 +2660,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)" = "<none>"
"checksum rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)" = "<none>"
"checksum rotor 0.6.3 (git+https://github.com/ethcore/rotor)" = "<none>"
@ -2652,9 +2722,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"

View File

@ -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-api = { path = "ipfs" }
parity-updater = { path = "updater" }
parity-reactor = { path = "util/reactor" }
ethcore-dapps = { path = "dapps", optional = true }

15
ipfs/Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
description = "Parity IPFS-compatible API"
name = "parity-ipfs-api"
version = "1.6.0"
license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.io>"]
[dependencies]
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"
multihash = "0.5"

94
ipfs/src/error.rs Normal file
View File

@ -0,0 +1,94 @@
// 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 <http://www.gnu.org/licenses/>.
use {multihash, cid, hyper};
use handler::Out;
pub type Result<T> = ::std::result::Result<T, Error>;
/// IPFS server error
#[derive(Debug)]
pub enum ServerError {
/// Wrapped `std::io::Error`
IoError(::std::io::Error),
/// Other `hyper` error
Other(hyper::error::Error),
}
#[derive(Debug, PartialEq)]
pub enum Error {
CidParsingFailed,
UnsupportedHash,
UnsupportedCid,
BlockNotFound,
TransactionNotFound,
StateRootNotFound,
ContractNotFound,
}
/// Convert Error into Out, handy when switching from Rust's Result-based
/// error handling to Hyper's request handling.
impl From<Error> 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"),
ContractNotFound => Out::NotFound("Contract not found"),
}
}
}
/// Convert Content ID errors.
impl From<cid::Error> for Error {
fn from(_: cid::Error) -> Error {
Error::CidParsingFailed
}
}
/// Convert multihash errors (multihash being part of CID).
impl From<multihash::Error> 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)
}
}
impl From<hyper::error::Error> for ServerError {
fn from(err: hyper::error::Error) -> ServerError {
ServerError::Other(err)
}
}
impl From<ServerError> for String {
fn from(err: ServerError) -> String {
match err {
ServerError::IoError(err) => err.to_string(),
ServerError::Other(err) => err.to_string(),
}
}
}

268
ipfs/src/handler.rs Normal file
View File

@ -0,0 +1,268 @@
// 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 <http://www.gnu.org/licenses/>.
use {rlp, multihash};
use error::{Error, Result};
use cid::{ToCid, Codec};
use std::sync::Arc;
use multihash::Hash;
use hyper::Next;
use util::{Bytes, H256};
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 {
/// Reference to the Blockchain Client
client: Arc<BlockChainClient>,
/// 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<BlockChainClient>) -> Self {
IpfsHandler {
client: client,
out: Out::Bad("Invalid Request"),
out_progress: 0,
}
}
/// Route path + query string to a specialized method
pub fn route(&mut self, path: &str, query: Option<&str>) -> Next {
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")
};
Next::write()
}
/// 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<Out> {
let cid = cid.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.block(hash),
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),
}
}
/// Get block header by hash as raw binary.
fn block(&self, hash: H256) -> Result<Out> {
let block_id = BlockId::Hash(hash);
let block = self.client.block_header(block_id).ok_or(Error::BlockNotFound)?;
Ok(Out::OctetStream(block.into_inner()))
}
/// Get list of block ommers by hash as raw binary.
fn block_list(&self, hash: H256) -> Result<Out> {
let uncles = self.client.find_uncles(&hash).ok_or(Error::BlockNotFound)?;
Ok(Out::OctetStream(rlp::encode(&uncles).to_vec()))
}
/// Get transaction by hash and return as raw binary.
fn transaction(&self, hash: H256) -> Result<Out> {
let tx_id = TransactionId::Hash(hash);
let tx = self.client.transaction(tx_id).ok_or(Error::TransactionNotFound)?;
Ok(Out::OctetStream(rlp::encode(&*tx).to_vec()))
}
/// Get state trie node by hash and return as raw binary.
fn state_trie(&self, hash: H256) -> Result<Out> {
let data = self.client.state_data(&hash).ok_or(Error::StateRootNotFound)?;
Ok(Out::OctetStream(data))
}
/// Get state trie node by hash and return as raw binary.
fn contract_code(&self, hash: H256) -> Result<Out> {
let data = self.client.state_data(&hash).ok_or(Error::ContractNotFound)?;
Ok(Out::OctetStream(data))
}
}
/// Get a query parameter's value by name.
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::*;
use ethcore::client::TestBlockChainClient;
fn get_mocked_handler() -> IpfsHandler {
IpfsHandler::new(Arc::new(TestBlockChainClient::new()))
}
#[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);
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_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();
// `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"));
}
}

205
ipfs/src/lib.rs Normal file
View File

@ -0,0 +1,205 @@
// 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 <http://www.gnu.org/licenses/>.
#[macro_use]
extern crate mime;
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::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};
use hyper::net::HttpStream;
use hyper::header::{ContentLength, ContentType, Origin};
use hyper::{Next, Encoder, Decoder, Method, RequestUri, StatusCode};
use ethcore::client::BlockChainClient;
/// Implement Hyper's HTTP handler
impl Handler<HttpStream> for IpfsHandler {
fn on_request(&mut self, req: Request<HttpStream>) -> Next {
if *req.method() != Method::Get {
return Next::write();
}
// Reject requests if the Origin header isn't valid
if req.headers().get::<Origin>().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(),
};
self.route(path, query)
}
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
Next::write()
}
fn on_response(&mut self, res: &mut Response) -> Next {
use Out::*;
match self.out {
OctetStream(ref bytes) => {
use mime::{Mime, TopLevel, SubLevel};
// `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()
},
NotFound(reason) => {
res.set_status(StatusCode::NotFound);
res.headers_mut().set(ContentLength(reason.len() as u64));
res.headers_mut().set(ContentType(mime!(Text/Plain)));
Next::write()
},
Bad(reason) => {
res.set_status(StatusCode::BadRequest);
res.headers_mut().set(ContentLength(reason.len() as u64));
res.headers_mut().set(ContentType(mime!(Text/Plain)));
Next::write()
}
}
}
fn on_response_writable(&mut self, transport: &mut Encoder<HttpStream>) -> Next {
use Out::*;
// 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(),
};
write_chunk(transport, &mut self.out_progress, data)
}
}
fn write_chunk<W: Write>(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()
}
}
pub fn start_server(port: u16, client: Arc<BlockChainClient>) -> Result<Listening, ServerError> {
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port);
Ok(
hyper::Server::http(&addr)?
.handle(move |_| IpfsHandler::new(client.clone()))
.map(|(listening, srv)| {
::std::thread::spawn(move || {
srv.run();
});
listening
})?
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn write_chunk_to_vec() {
let mut transport = Vec::new();
let mut progress = 0;
let _ = write_chunk(&mut transport, &mut progress, b"foobar");
assert_eq!(b"foobar".to_vec(), transport);
assert_eq!(6, progress);
}
#[test]
fn write_chunk_to_vec_part() {
let mut transport = Vec::new();
let mut progress = 3;
let _ = write_chunk(&mut transport, &mut progress, b"foobar");
assert_eq!(b"bar".to_vec(), transport);
assert_eq!(6, progress);
}
#[test]
fn write_chunk_to_array() {
use std::io::Cursor;
let mut buf = [0u8; 3];
let mut progress = 0;
{
let mut transport: Cursor<&mut [u8]> = 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);
}
}

View File

@ -67,6 +67,10 @@ path = "$HOME/.parity/dapps"
user = "test_user"
pass = "test_pass"
[ipfs]
enable = false
port = 5001
[mining]
author = "0xdeadbeefcafe0000000000000000000000000001"
engine_signer = "0xdeadbeefcafe0000000000000000000000000001"

View File

@ -38,6 +38,10 @@ port = 8080
user = "username"
pass = "password"
[ipfs]
enable = false
port = 5001
[mining]
author = "0xdeadbeefcafe0000000000000000000000000001"
engine_signer = "0xdeadbeefcafe0000000000000000000000000001"

View File

@ -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_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
flag_author: Option<String> = None,
or |c: &Config| otry!(c.mining).author.clone().map(Some),
@ -321,6 +327,7 @@ struct Config {
rpc: Option<Rpc>,
ipc: Option<Ipc>,
dapps: Option<Dapps>,
ipfs: Option<Ipfs>,
mining: Option<Mining>,
footprint: Option<Footprint>,
snapshots: Option<Snapshots>,
@ -409,6 +416,12 @@ struct Dapps {
pass: Option<String>,
}
#[derive(Default, Debug, PartialEq, RustcDecodable)]
struct Ipfs {
enable: Option<bool>,
port: Option<u16>,
}
#[derive(Default, Debug, PartialEq, RustcDecodable)]
struct Mining {
author: Option<String>,
@ -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_api: false,
flag_ipfs_api_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 {
enable: Some(false),
port: Some(5001)
}),
mining: Some(Mining {
author: Some("0xdeadbeefcafe0000000000000000000000000001".into()),
engine_signer: Some("0xdeadbeefcafe0000000000000000000000000001".into()),

View File

@ -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})
--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

View File

@ -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<Option<String>, 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,

16
parity/ipfs.rs Normal file
View File

@ -0,0 +1,16 @@
pub use parity_ipfs_api::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,
}
}
}

View File

@ -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_api;
extern crate parity_reactor;
extern crate parity_updater as updater;
extern crate rpc_cli;
@ -86,6 +87,7 @@ mod cache;
mod cli;
mod configuration;
mod dapps;
mod ipfs;
mod deprecated;
mod dir;
mod helpers;

View File

@ -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;
@ -93,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<String>,
pub ui: bool,
@ -420,6 +422,12 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
};
let signer_server = signer::start(cmd.signer_conf.clone(), signer_deps)?;
// the ipfs server
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(
service.client(),
@ -476,7 +484,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> 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...");