Hash Content RPC method (#2355)

* Moving file fetching to separate crate.

* ethcore_hashContent

* Tests running on mocked fetch.

* Limiting size of downloadable assets
This commit is contained in:
Tomasz Drwięga
2016-09-27 16:27:06 +02:00
committed by Gav Wood
parent 3fb3f1f54e
commit d7bbc5cc3f
21 changed files with 486 additions and 173 deletions

View File

@@ -25,6 +25,7 @@ ethsync = { path = "../sync" }
ethjson = { path = "../json" }
ethcore-devtools = { path = "../devtools" }
rlp = { path = "../util/rlp" }
fetch = { path = "../util/fetch" }
rustc-serialize = "0.3"
transient-hashmap = "0.1"
serde_macros = { version = "0.8.0", optional = true }

View File

@@ -36,6 +36,7 @@ extern crate json_ipc_server as ipc;
extern crate ethcore_ipc;
extern crate time;
extern crate rlp;
extern crate fetch;
#[macro_use]
extern crate log;

View File

@@ -23,6 +23,7 @@ macro_rules! rpc_unimplemented {
use std::fmt;
use ethcore::error::Error as EthcoreError;
use ethcore::account_provider::{Error as AccountError};
use fetch::FetchError;
use jsonrpc_core::{Error, ErrorCode, Value};
mod codes {
@@ -41,6 +42,7 @@ mod codes {
pub const REQUEST_REJECTED_LIMIT: i64 = -32041;
pub const REQUEST_NOT_FOUND: i64 = -32042;
pub const COMPILATION_ERROR: i64 = -32050;
pub const FETCH_ERROR: i64 = -32060;
}
pub fn unimplemented() -> Error {
@@ -155,6 +157,14 @@ pub fn signer_disabled() -> Error {
}
}
pub fn from_fetch_error(error: FetchError) -> Error {
Error {
code: ErrorCode::ServerError(codes::FETCH_ERROR),
message: "Error while fetching content.".into(),
data: Some(Value::String(format!("{:?}", error))),
}
}
pub fn from_signing_error(error: AccountError) -> Error {
Error {
code: ErrorCode::ServerError(codes::ACCOUNT_LOCKED),

View File

@@ -15,30 +15,33 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Ethcore-specific rpc implementation.
use std::sync::{Arc, Weak};
use std::{fs, io};
use std::sync::{mpsc, Arc, Weak};
use std::str::FromStr;
use std::collections::{BTreeMap};
use util::{RotatingLogger, Address};
use util::{RotatingLogger, Address, Mutex, sha3};
use util::misc::version_data;
use crypto::ecies;
use fetch::{Client as FetchClient, Fetch};
use ethkey::{Brain, Generator};
use ethstore::random_phrase;
use ethsync::{SyncProvider, ManageNetwork};
use ethcore::miner::MinerService;
use ethcore::client::{MiningBlockChainClient};
use jsonrpc_core::*;
use jsonrpc_core::{from_params, to_value, Value, Error, Params, Ready};
use v1::traits::Ethcore;
use v1::types::{Bytes, U256, H160, H512, Peers, Transaction};
use v1::types::{Bytes, U256, H160, H256, H512, Peers, Transaction};
use v1::helpers::{errors, SigningQueue, SignerService, NetworkSettings};
use v1::helpers::params::expect_no_params;
/// Ethcore implementation.
pub struct EthcoreClient<C, M, S: ?Sized> where
pub struct EthcoreClient<C, M, S: ?Sized, F=FetchClient> where
C: MiningBlockChainClient,
M: MinerService,
S: SyncProvider {
S: SyncProvider,
F: Fetch {
client: Weak<C>,
miner: Weak<M>,
@@ -47,10 +50,14 @@ pub struct EthcoreClient<C, M, S: ?Sized> where
logger: Arc<RotatingLogger>,
settings: Arc<NetworkSettings>,
signer: Option<Arc<SignerService>>,
fetch: Mutex<F>
}
impl<C, M, S: ?Sized> EthcoreClient<C, M, S> where C: MiningBlockChainClient, M: MinerService, S: SyncProvider {
/// Creates new `EthcoreClient`.
impl<C, M, S: ?Sized> EthcoreClient<C, M, S> where
C: MiningBlockChainClient,
M: MinerService,
S: SyncProvider, {
/// Creates new `EthcoreClient` with default `Fetch`.
pub fn new(
client: &Arc<C>,
miner: &Arc<M>,
@@ -60,6 +67,26 @@ impl<C, M, S: ?Sized> EthcoreClient<C, M, S> where C: MiningBlockChainClient, M:
settings: Arc<NetworkSettings>,
signer: Option<Arc<SignerService>>
) -> Self {
Self::with_fetch(client, miner, sync, net, logger, settings, signer)
}
}
impl<C, M, S: ?Sized, F> EthcoreClient<C, M, S, F> where
C: MiningBlockChainClient,
M: MinerService,
S: SyncProvider,
F: Fetch, {
/// Creates new `EthcoreClient` with customizable `Fetch`.
pub fn with_fetch(
client: &Arc<C>,
miner: &Arc<M>,
sync: &Arc<S>,
net: &Arc<ManageNetwork>,
logger: Arc<RotatingLogger>,
settings: Arc<NetworkSettings>,
signer: Option<Arc<SignerService>>
) -> Self {
EthcoreClient {
client: Arc::downgrade(client),
miner: Arc::downgrade(miner),
@@ -68,6 +95,7 @@ impl<C, M, S: ?Sized> EthcoreClient<C, M, S> where C: MiningBlockChainClient, M:
logger: logger,
settings: settings,
signer: signer,
fetch: Mutex::new(F::default()),
}
}
@@ -78,7 +106,11 @@ impl<C, M, S: ?Sized> EthcoreClient<C, M, S> where C: MiningBlockChainClient, M:
}
}
impl<C, M, S: ?Sized> Ethcore for EthcoreClient<C, M, S> where M: MinerService + 'static, C: MiningBlockChainClient + 'static, S: SyncProvider + 'static {
impl<C, M, S: ?Sized, F> Ethcore for EthcoreClient<C, M, S, F> where
M: MinerService + 'static,
C: MiningBlockChainClient + 'static,
S: SyncProvider + 'static,
F: Fetch + 'static {
fn transactions_limit(&self, params: Params) -> Result<Value, Error> {
try!(self.active());
@@ -233,4 +265,42 @@ impl<C, M, S: ?Sized> Ethcore for EthcoreClient<C, M, S> where M: MinerService +
Ok(to_value(&take_weak!(self.miner).all_transactions().into_iter().map(Into::into).collect::<Vec<Transaction>>()))
}
fn hash_content(&self, params: Params, ready: Ready) {
let res = self.active().and_then(|_| from_params::<(String,)>(params));
let hash_content = |result| {
let path = try!(result);
let mut file = io::BufReader::new(try!(fs::File::open(&path)));
// Try to hash
let result = sha3(&mut file);
// Remove file (always)
try!(fs::remove_file(&path));
// Return the result
Ok(try!(result))
};
match res {
Err(e) => ready.ready(Err(e)),
Ok((url, )) => {
let (tx, rx) = mpsc::channel();
let res = self.fetch.lock().request_async(&url, Default::default(), Box::new(move |result| {
let result = hash_content(result)
.map_err(errors::from_fetch_error)
.map(|hash| to_value(H256::from(hash)));
// Receive ready and invoke with result.
let ready: Ready = rx.try_recv().expect("When on_done is invoked ready object is always sent.");
ready.ready(result);
}));
// Either invoke ready right away or transfer it to the closure.
if let Err(e) = res {
ready.ready(Err(errors::from_fetch_error(e)));
} else {
tx.send(ready).expect("Rx end is sent to on_done closure.");
}
}
}
}
}

View File

@@ -0,0 +1,44 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Test implementation of fetch client.
use std::io::Write;
use std::{env, fs, thread};
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use fetch::{Fetch, FetchError, FetchResult};
/// Test implementation of fetcher. Will always return the same file.
#[derive(Default)]
pub struct TestFetch;
impl Fetch for TestFetch {
fn request_async(&mut self, _url: &str, _abort: Arc<AtomicBool>, on_done: Box<Fn(FetchResult) + Send>) -> Result<(), FetchError> {
thread::spawn(move || {
let mut path = env::temp_dir();
path.push(Self::random_filename());
let mut file = fs::File::create(&path).unwrap();
file.write_all(b"Some content").unwrap();
on_done(Ok(path));
});
Ok(())
}
}

View File

@@ -18,6 +18,8 @@
mod sync_provider;
mod miner_service;
mod fetch;
pub use self::sync_provider::{Config, TestSyncProvider};
pub use self::miner_service::TestMinerService;
pub use self::fetch::TestFetch;

View File

@@ -23,7 +23,7 @@ use ethcore::client::{TestBlockChainClient};
use jsonrpc_core::IoHandler;
use v1::{Ethcore, EthcoreClient};
use v1::helpers::{SignerService, NetworkSettings};
use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService};
use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService, TestFetch};
use super::manage_network::TestManageNetwork;
fn miner_service() -> Arc<TestMinerService> {
@@ -60,12 +60,15 @@ fn network_service() -> Arc<ManageNetwork> {
Arc::new(TestManageNetwork)
}
type TestEthcoreClient = EthcoreClient<TestBlockChainClient, TestMinerService, TestSyncProvider, TestFetch>;
fn ethcore_client(
client: &Arc<TestBlockChainClient>,
miner: &Arc<TestMinerService>,
sync: &Arc<TestSyncProvider>,
net: &Arc<ManageNetwork>) -> EthcoreClient<TestBlockChainClient, TestMinerService, TestSyncProvider> {
EthcoreClient::new(client, miner, sync, net, logger(), settings(), None)
net: &Arc<ManageNetwork>)
-> TestEthcoreClient {
EthcoreClient::with_fetch(client, miner, sync, net, logger(), settings(), None)
}
#[test]
@@ -140,9 +143,9 @@ fn rpc_ethcore_dev_logs() {
let logger = logger();
logger.append("a".to_owned());
logger.append("b".to_owned());
let ethcore = EthcoreClient::new(&client, &miner, &sync, &net, logger.clone(), settings(), None).to_delegate();
let ethcore: TestEthcoreClient = EthcoreClient::with_fetch(&client, &miner, &sync, &net, logger.clone(), settings(), None);
let io = IoHandler::new();
io.add_delegate(ethcore);
io.add_delegate(ethcore.to_delegate());
let request = r#"{"jsonrpc": "2.0", "method": "ethcore_devLogs", "params":[], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":["b","a"],"id":1}"#;
@@ -263,8 +266,8 @@ fn rpc_ethcore_unsigned_transactions_count() {
let net = network_service();
let io = IoHandler::new();
let signer = Arc::new(SignerService::new_test());
let ethcore = EthcoreClient::new(&client, &miner, &sync, &net, logger(), settings(), Some(signer)).to_delegate();
io.add_delegate(ethcore);
let ethcore: TestEthcoreClient = EthcoreClient::with_fetch(&client, &miner, &sync, &net, logger(), settings(), Some(signer));
io.add_delegate(ethcore.to_delegate());
let request = r#"{"jsonrpc": "2.0", "method": "ethcore_unsignedTransactionsCount", "params":[], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":0,"id":1}"#;
@@ -287,6 +290,21 @@ fn rpc_ethcore_unsigned_transactions_count_when_signer_disabled() {
assert_eq!(io.handle_request_sync(request), Some(response.to_owned()));
}
#[test]
fn rpc_ethcore_hash_content() {
let miner = miner_service();
let client = client_service();
let sync = sync_provider();
let net = network_service();
let io = IoHandler::new();
io.add_delegate(ethcore_client(&client, &miner, &sync, &net).to_delegate());
let request = r#"{"jsonrpc": "2.0", "method": "ethcore_hashContent", "params":["https://ethcore.io/assets/images/ethcore-black-horizontal.png"], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":"0x2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e","id":1}"#;
assert_eq!(io.handle_request_sync(request), Some(response.to_owned()));
}
#[test]
fn rpc_ethcore_pending_transactions() {
let miner = miner_service();

View File

@@ -83,6 +83,9 @@ pub trait Ethcore: Sized + Send + Sync + 'static {
/// Returns all pending (current) transactions from transaction queue.
fn pending_transactions(&self, _: Params) -> Result<Value, Error>;
/// Hash a file content under given URL.
fn hash_content(&self, _: Params, _: Ready);
/// Should be used to convert object to io delegate.
fn to_delegate(self) -> IoDelegate<Self> {
let mut delegate = IoDelegate::new(Arc::new(self));
@@ -107,6 +110,8 @@ pub trait Ethcore: Sized + Send + Sync + 'static {
delegate.add_method("ethcore_registryAddress", Ethcore::registry_address);
delegate.add_method("ethcore_encryptMessage", Ethcore::encrypt_message);
delegate.add_method("ethcore_pendingTransactions", Ethcore::pending_transactions);
delegate.add_async_method("ethcore_hashContent", Ethcore::hash_content);
delegate
}
}