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:
@@ -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 }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
44
rpc/src/v1/tests/helpers/fetch.rs
Normal file
44
rpc/src/v1/tests/helpers/fetch.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user