Simple signing queue, confirmation APIs exposed in signer WebSockets. (#1182)

* Splitting methods requiring signing into separate trait

* Single place where RPC apis are created.

* Separating eth_filter

* Separating eth_signing

* Stubs for Personal Signer methods

* Test for EthSigningQueueClient

* TransactionConfirmation API

* Exposing PersonalSigner API

* Defining ApiSets dependent on context

* Removing types

* Fixing default impl

* Fixing un-mocked tests

* Update signing_queue.rs

[ci skip]

* Removing unused import [ci skip]
This commit is contained in:
Tomasz Drwięga 2016-06-01 19:37:34 +02:00 committed by Gav Wood
parent b9b0ce8d65
commit 99e26b8480
35 changed files with 1140 additions and 544 deletions

5
Cargo.lock generated
View File

@ -358,12 +358,7 @@ dependencies = [
"ethcore-util 1.2.0",
"jsonrpc-core 2.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_codegen 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"syntex 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ws 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
]

View File

@ -52,6 +52,7 @@ extern crate serde_json;
extern crate jsonrpc_core;
extern crate jsonrpc_http_server;
extern crate parity_dapps;
extern crate ethcore_rpc;
mod endpoint;
mod apps;
@ -66,6 +67,7 @@ use std::net::SocketAddr;
use std::collections::HashMap;
use jsonrpc_core::{IoHandler, IoDelegate};
use router::auth::{Authorization, NoAuth, HttpBasicAuth};
use ethcore_rpc::Extendable;
static DAPPS_DOMAIN : &'static str = ".parity";
@ -74,6 +76,12 @@ pub struct ServerBuilder {
handler: Arc<IoHandler>,
}
impl Extendable for ServerBuilder {
fn add_delegate<D: Send + Sync + 'static>(&self, delegate: IoDelegate<D>) {
self.handler.add_delegate(delegate);
}
}
impl ServerBuilder {
/// Construct new dapps server
pub fn new() -> Self {
@ -82,11 +90,6 @@ impl ServerBuilder {
}
}
/// Add io delegate.
pub fn add_delegate<D>(&self, delegate: IoDelegate<D>) where D: Send + Sync + 'static {
self.handler.add_delegate(delegate);
}
/// Asynchronously start server with no authentication,
/// returns result with `Server` handle on success or an error.
pub fn start_unsecure_http(&self, addr: &SocketAddr) -> Result<Server, ServerError> {

View File

@ -17,14 +17,9 @@
use std::sync::Arc;
use std::str::FromStr;
use std::net::SocketAddr;
use ethcore::client::Client;
use ethsync::EthSync;
use ethminer::{Miner, ExternalMiner};
use util::RotatingLogger;
use util::panics::PanicHandler;
use util::keys::store::AccountService;
use util::network_settings::NetworkSettings;
use die::*;
use rpc_apis;
#[cfg(feature = "dapps")]
pub use ethcore_dapps::Server as WebappServer;
@ -41,13 +36,7 @@ pub struct Configuration {
pub struct Dependencies {
pub panic_handler: Arc<PanicHandler>,
pub client: Arc<Client>,
pub sync: Arc<EthSync>,
pub secret_store: Arc<AccountService>,
pub miner: Arc<Miner>,
pub external_miner: Arc<ExternalMiner>,
pub logger: Arc<RotatingLogger>,
pub settings: Arc<NetworkSettings>,
pub apis: Arc<rpc_apis::Dependencies>,
}
pub fn new(configuration: Configuration, deps: Dependencies) -> Option<WebappServer> {
@ -92,17 +81,10 @@ pub fn setup_dapps_server(
url: &SocketAddr,
auth: Option<(String, String)>
) -> WebappServer {
use ethcore_rpc::v1::*;
use ethcore_dapps as dapps;
let server = dapps::ServerBuilder::new();
server.add_delegate(Web3Client::new().to_delegate());
server.add_delegate(NetClient::new(&deps.sync).to_delegate());
server.add_delegate(EthClient::new(&deps.client, &deps.sync, &deps.secret_store, &deps.miner, &deps.external_miner).to_delegate());
server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate());
server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner).to_delegate());
server.add_delegate(EthcoreClient::new(&deps.miner, deps.logger.clone(), deps.settings.clone()).to_delegate());
let server = rpc_apis::setup_rpc(server, deps.apis.clone(), rpc_apis::ApiSet::UnsafeContext);
let start_result = match auth {
None => {
server.start_unsecure_http(url)

View File

@ -67,6 +67,7 @@ mod cli;
mod configuration;
mod migration;
mod signer;
mod rpc_apis;
use std::io::{Write, Read, BufReader, BufRead};
use std::ops::Deref;
@ -195,8 +196,9 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig)
// Sync
let sync = EthSync::register(service.network(), sync_config, client.clone(), miner.clone());
let dependencies = Arc::new(rpc::Dependencies {
panic_handler: panic_handler.clone(),
let deps_for_rpc_apis = Arc::new(rpc_apis::Dependencies {
signer_enabled: conf.args.flag_signer,
signer_queue: Arc::new(rpc_apis::ConfirmationsQueue::default()),
client: client.clone(),
sync: sync.clone(),
secret_store: account_service.clone(),
@ -206,6 +208,11 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig)
settings: network_settings.clone(),
});
let dependencies = rpc::Dependencies {
panic_handler: panic_handler.clone(),
apis: deps_for_rpc_apis.clone(),
};
// Setup http rpc
let rpc_server = rpc::new_http(rpc::HttpConfiguration {
enabled: network_settings.rpc_enabled,
@ -227,26 +234,16 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig)
pass: conf.args.flag_dapps_pass.clone(),
}, dapps::Dependencies {
panic_handler: panic_handler.clone(),
client: client.clone(),
sync: sync.clone(),
secret_store: account_service.clone(),
miner: miner.clone(),
external_miner: external_miner.clone(),
logger: logger.clone(),
settings: network_settings.clone(),
apis: deps_for_rpc_apis.clone(),
});
// Set up a signer
let signer_server = signer::start(signer::Configuration {
enabled: conf.args.flag_signer,
enabled: deps_for_rpc_apis.signer_enabled,
port: conf.args.flag_signer_port,
}, signer::Dependencies {
panic_handler: panic_handler.clone(),
client: client.clone(),
sync: sync.clone(),
secret_store: account_service.clone(),
miner: miner.clone(),
external_miner: external_miner.clone(),
apis: deps_for_rpc_apis.clone(),
});
// Register IO handler

View File

@ -15,19 +15,13 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::collections::BTreeMap;
use std::str::FromStr;
use std::sync::Arc;
use std::net::SocketAddr;
use ethcore::client::Client;
use ethsync::EthSync;
use ethminer::{Miner, ExternalMiner};
use util::RotatingLogger;
use util::panics::PanicHandler;
use util::keys::store::AccountService;
use util::network_settings::NetworkSettings;
use die::*;
use jsonipc;
use rpc_apis;
#[cfg(feature = "rpc")]
pub use ethcore_rpc::Server as RpcServer;
@ -52,16 +46,10 @@ pub struct IpcConfiguration {
pub struct Dependencies {
pub panic_handler: Arc<PanicHandler>,
pub client: Arc<Client>,
pub sync: Arc<EthSync>,
pub secret_store: Arc<AccountService>,
pub miner: Arc<Miner>,
pub external_miner: Arc<ExternalMiner>,
pub logger: Arc<RotatingLogger>,
pub settings: Arc<NetworkSettings>,
pub apis: Arc<rpc_apis::Dependencies>,
}
pub fn new_http(conf: HttpConfiguration, deps: &Arc<Dependencies>) -> Option<RpcServer> {
pub fn new_http(conf: HttpConfiguration, deps: &Dependencies) -> Option<RpcServer> {
if !conf.enabled {
return None;
}
@ -78,58 +66,23 @@ pub fn new_http(conf: HttpConfiguration, deps: &Arc<Dependencies>) -> Option<Rpc
Some(setup_http_rpc_server(deps, &addr, conf.cors, apis))
}
pub fn new_ipc(conf: IpcConfiguration, deps: &Arc<Dependencies>) -> Option<jsonipc::Server> {
pub fn new_ipc(conf: IpcConfiguration, deps: &Dependencies) -> Option<jsonipc::Server> {
if !conf.enabled { return None; }
let apis = conf.apis.split(',').collect();
Some(setup_ipc_rpc_server(deps, &conf.socket_addr, apis))
}
fn setup_rpc_server(apis: Vec<&str>, deps: &Arc<Dependencies>) -> Server {
use ethcore_rpc::v1::*;
fn setup_rpc_server(apis: Vec<&str>, deps: &Dependencies) -> Server {
let apis = rpc_apis::from_str(apis);
let server = Server::new();
let mut modules = BTreeMap::new();
for api in apis.into_iter() {
match api {
"web3" => {
modules.insert("web3".to_owned(), "1.0".to_owned());
server.add_delegate(Web3Client::new().to_delegate());
},
"net" => {
modules.insert("net".to_owned(), "1.0".to_owned());
server.add_delegate(NetClient::new(&deps.sync).to_delegate());
},
"eth" => {
modules.insert("eth".to_owned(), "1.0".to_owned());
server.add_delegate(EthClient::new(&deps.client, &deps.sync, &deps.secret_store, &deps.miner, &deps.external_miner).to_delegate());
server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate());
},
"personal" => {
modules.insert("personal".to_owned(), "1.0".to_owned());
server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner).to_delegate())
},
"ethcore" => {
modules.insert("ethcore".to_owned(), "1.0".to_owned());
server.add_delegate(EthcoreClient::new(&deps.miner, deps.logger.clone(), deps.settings.clone()).to_delegate())
},
"traces" => {
modules.insert("traces".to_owned(), "1.0".to_owned());
server.add_delegate(TracesClient::new(&deps.client).to_delegate())
},
_ => {
die!("{}: Invalid API name to be enabled.", api);
},
}
}
server.add_delegate(RpcClient::new(modules).to_delegate());
server
rpc_apis::setup_rpc(server, deps.apis.clone(), rpc_apis::ApiSet::List(apis))
}
#[cfg(not(feature = "rpc"))]
pub fn setup_http_rpc_server(
_deps: Dependencies,
_deps: &Dependencies,
_url: &SocketAddr,
_cors_domain: Option<String>,
_cors_domain: Vec<String>,
_apis: Vec<&str>,
) -> ! {
die!("Your Parity version has been compiled without JSON-RPC support.")
@ -137,27 +90,31 @@ pub fn setup_http_rpc_server(
#[cfg(feature = "rpc")]
pub fn setup_http_rpc_server(
dependencies: &Arc<Dependencies>,
dependencies: &Dependencies,
url: &SocketAddr,
cors_domains: Vec<String>,
apis: Vec<&str>,
) -> RpcServer {
let server = setup_rpc_server(apis, dependencies);
let start_result = server.start_http(url, cors_domains);
let deps = dependencies.clone();
let ph = dependencies.panic_handler.clone();
match start_result {
Err(RpcServerError::IoError(err)) => die_with_io_error("RPC", err),
Err(e) => die!("RPC: {:?}", e),
Ok(server) => {
server.set_panic_handler(move || {
deps.panic_handler.notify_all("Panic in RPC thread.".to_owned());
ph.notify_all("Panic in RPC thread.".to_owned());
});
server
},
}
}
pub fn setup_ipc_rpc_server(dependencies: &Arc<Dependencies>, addr: &str, apis: Vec<&str>) -> jsonipc::Server {
#[cfg(not(feature = "rpc"))]
pub fn setup_ipc_rpc_server(_dependencies: &Dependencies, _addr: &str, _apis: Vec<&str>) -> ! {
die!("Your Parity version has been compiled without JSON-RPC support.")
}
#[cfg(feature = "rpc")]
pub fn setup_ipc_rpc_server(dependencies: &Dependencies, addr: &str, apis: Vec<&str>) -> jsonipc::Server {
let server = setup_rpc_server(apis, dependencies);
match server.start_ipc(addr) {
Err(jsonipc::Error::Io(io_error)) => die_with_io_error("RPC", io_error),

168
parity/rpc_apis.rs Normal file
View File

@ -0,0 +1,168 @@
// 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/>.
use std::collections::BTreeMap;
use std::str::FromStr;
use std::sync::Arc;
use die::*;
use ethsync::EthSync;
use ethminer::{Miner, ExternalMiner};
use ethcore::client::Client;
use util::RotatingLogger;
use util::keys::store::AccountService;
use util::network_settings::NetworkSettings;
#[cfg(feature="rpc")]
pub use ethcore_rpc::ConfirmationsQueue;
#[cfg(not(feature="rpc"))]
#[derive(Default)]
pub struct ConfirmationsQueue;
#[cfg(feature="rpc")]
use ethcore_rpc::Extendable;
pub enum Api {
Web3,
Net,
Eth,
Personal,
Ethcore,
Traces,
Rpc,
}
pub enum ApiError {
UnknownApi(String)
}
pub enum ApiSet {
SafeContext,
UnsafeContext,
List(Vec<Api>),
}
impl FromStr for Api {
type Err = ApiError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use self::Api::*;
match s {
"web3" => Ok(Web3),
"net" => Ok(Net),
"eth" => Ok(Eth),
"personal" => Ok(Personal),
"ethcore" => Ok(Ethcore),
"traces" => Ok(Traces),
"rpc" => Ok(Rpc),
e => Err(ApiError::UnknownApi(e.into())),
}
}
}
pub struct Dependencies {
pub signer_enabled: bool,
pub signer_queue: Arc<ConfirmationsQueue>,
pub client: Arc<Client>,
pub sync: Arc<EthSync>,
pub secret_store: Arc<AccountService>,
pub miner: Arc<Miner>,
pub external_miner: Arc<ExternalMiner>,
pub logger: Arc<RotatingLogger>,
pub settings: Arc<NetworkSettings>,
}
fn to_modules(apis: &[Api]) -> BTreeMap<String, String> {
let mut modules = BTreeMap::new();
for api in apis {
let (name, version) = match *api {
Api::Web3 => ("web3", "1.0"),
Api::Net => ("net", "1.0"),
Api::Eth => ("eth", "1.0"),
Api::Personal => ("personal", "1.0"),
Api::Ethcore => ("ethcore", "1.0"),
Api::Traces => ("traces", "1.0"),
Api::Rpc => ("rpc", "1.0"),
};
modules.insert(name.into(), version.into());
}
modules
}
pub fn from_str(apis: Vec<&str>) -> Vec<Api> {
apis.into_iter()
.map(Api::from_str)
.collect::<Result<Vec<Api>, ApiError>>()
.unwrap_or_else(|e| match e {
ApiError::UnknownApi(s) => die!("Unknown RPC API specified: {}", s),
})
}
fn list_apis(apis: ApiSet, signer_enabled: bool) -> Vec<Api> {
match apis {
ApiSet::List(apis) => apis,
ApiSet::UnsafeContext if signer_enabled => {
vec![Api::Web3, Api::Net, Api::Eth, Api::Ethcore, Api::Traces, Api::Rpc]
}
_ => {
vec![Api::Web3, Api::Net, Api::Eth, Api::Personal, Api::Ethcore, Api::Traces, Api::Rpc]
}
}
}
pub fn setup_rpc<T: Extendable>(server: T, deps: Arc<Dependencies>, apis: ApiSet) -> T {
use ethcore_rpc::v1::*;
let apis = list_apis(apis, deps.signer_enabled);
for api in &apis {
match *api {
Api::Web3 => {
server.add_delegate(Web3Client::new().to_delegate());
},
Api::Net => {
server.add_delegate(NetClient::new(&deps.sync).to_delegate());
},
Api::Eth => {
server.add_delegate(EthClient::new(&deps.client, &deps.sync, &deps.secret_store, &deps.miner, &deps.external_miner).to_delegate());
server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate());
if deps.signer_enabled {
server.add_delegate(EthSigningQueueClient::new(&deps.signer_queue).to_delegate());
} else {
server.add_delegate(EthSigningUnsafeClient::new(&deps.client, &deps.secret_store, &deps.miner).to_delegate());
}
},
Api::Personal => {
server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner).to_delegate());
if deps.signer_enabled {
server.add_delegate(SignerClient::new(&deps.secret_store, &deps.client, &deps.miner, &deps.signer_queue).to_delegate());
}
},
Api::Ethcore => {
server.add_delegate(EthcoreClient::new(&deps.miner, deps.logger.clone(), deps.settings.clone()).to_delegate())
},
Api::Traces => {
server.add_delegate(TracesClient::new(&deps.client).to_delegate())
},
Api::Rpc => {
let modules = to_modules(&apis);
server.add_delegate(RpcClient::new(modules).to_delegate());
}
}
}
server
}

View File

@ -15,12 +15,9 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::sync::Arc;
use ethcore::client::Client;
use ethsync::EthSync;
use ethminer::{Miner, ExternalMiner};
use util::keys::store::AccountService;
use util::panics::{PanicHandler, ForwardPanic};
use die::*;
use rpc_apis;
#[cfg(feature = "ethcore-signer")]
use ethcore_signer as signer;
@ -36,11 +33,7 @@ pub struct Configuration {
pub struct Dependencies {
pub panic_handler: Arc<PanicHandler>,
pub client: Arc<Client>,
pub sync: Arc<EthSync>,
pub secret_store: Arc<AccountService>,
pub miner: Arc<Miner>,
pub external_miner: Arc<ExternalMiner>,
pub apis: Arc<rpc_apis::Dependencies>,
}
pub fn start(conf: Configuration, deps: Dependencies) -> Option<SignerServer> {
@ -58,13 +51,8 @@ fn do_start(conf: Configuration, deps: Dependencies) -> SignerServer {
});
let start_result = {
use ethcore_rpc::v1::*;
let server = signer::ServerBuilder::new();
server.add_delegate(Web3Client::new().to_delegate());
server.add_delegate(NetClient::new(&deps.sync).to_delegate());
server.add_delegate(EthClient::new(&deps.client, &deps.sync, &deps.secret_store, &deps.miner, &deps.external_miner).to_delegate());
server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate());
server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner).to_delegate());
let server = rpc_apis::setup_rpc(server, deps.apis, rpc_apis::ApiSet::SafeContext);
server.start(addr)
};

View File

@ -44,12 +44,26 @@ use self::jsonrpc_core::{IoHandler, IoDelegate};
pub use jsonrpc_http_server::{Server, RpcServerError};
pub mod v1;
pub use v1::{SigningQueue, ConfirmationsQueue};
/// An object that can be extended with `IoDelegates`
pub trait Extendable {
/// Add `Delegate` to this object.
fn add_delegate<D: Send + Sync + 'static>(&self, delegate: IoDelegate<D>);
}
/// Http server.
pub struct RpcServer {
handler: Arc<jsonrpc_core::io::IoHandler>,
}
impl Extendable for RpcServer {
/// Add io delegate.
fn add_delegate<D: Send + Sync + 'static>(&self, delegate: IoDelegate<D>) {
self.handler.add_delegate(delegate);
}
}
impl RpcServer {
/// Construct new http server object.
pub fn new() -> RpcServer {
@ -58,11 +72,6 @@ impl RpcServer {
}
}
/// Add io delegate.
pub fn add_delegate<D>(&self, delegate: IoDelegate<D>) where D: Send + Sync + 'static {
self.handler.add_delegate(delegate);
}
/// Start http server asynchronously and returns result with `Server` handle on success or an error.
pub fn start_http(&self, addr: &SocketAddr, cors_domains: Vec<String>) -> Result<Server, RpcServerError> {
let cors_domains = cors_domains.into_iter()

View File

@ -16,6 +16,8 @@
mod poll_manager;
mod poll_filter;
mod signing_queue;
pub use self::poll_manager::PollManager;
pub use self::poll_filter::PollFilter;
pub use self::signing_queue::{ConfirmationsQueue, SigningQueue};

View File

@ -0,0 +1,108 @@
// 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/>.
use std::sync::Mutex;
use std::collections::HashMap;
use v1::types::{TransactionRequest, TransactionConfirmation};
use util::U256;
/// A queue of transactions awaiting to be confirmed and signed.
pub trait SigningQueue: Send + Sync {
/// Add new request to the queue.
fn add_request(&self, transaction: TransactionRequest) -> U256;
/// Remove request from the queue.
fn remove_request(&self, id: U256) -> Option<TransactionConfirmation>;
/// Return copy of all the requests in the queue.
fn requests(&self) -> Vec<TransactionConfirmation>;
}
/// Queue for all unconfirmed transactions.
pub struct ConfirmationsQueue {
id: Mutex<U256>,
queue: Mutex<HashMap<U256, TransactionConfirmation>>,
}
impl Default for ConfirmationsQueue {
fn default() -> Self {
ConfirmationsQueue {
id: Mutex::new(U256::from(0)),
queue: Mutex::new(HashMap::new()),
}
}
}
impl SigningQueue for ConfirmationsQueue {
fn add_request(&self, transaction: TransactionRequest) -> U256 {
// Increment id
let id = {
let mut last_id = self.id.lock().unwrap();
*last_id = *last_id + U256::from(1);
*last_id
};
let mut queue = self.queue.lock().unwrap();
queue.insert(id, TransactionConfirmation {
id: id,
transaction: transaction,
});
id
}
fn remove_request(&self, id: U256) -> Option<TransactionConfirmation> {
self.queue.lock().unwrap().remove(&id)
}
fn requests(&self) -> Vec<TransactionConfirmation> {
let queue = self.queue.lock().unwrap();
queue.values().cloned().collect()
}
}
#[cfg(test)]
mod test {
use util::hash::Address;
use util::numbers::U256;
use v1::types::TransactionRequest;
use super::*;
#[test]
fn should_work_for_hashset() {
// given
let queue = ConfirmationsQueue::default();
let request = TransactionRequest {
from: Address::from(1),
to: Some(Address::from(2)),
gas_price: None,
gas: None,
value: Some(U256::from(10_000_000)),
data: None,
nonce: None,
};
// when
queue.add_request(request.clone());
let all = queue.requests();
// then
assert_eq!(all.len(), 1);
let el = all.get(0).unwrap();
assert_eq!(el.id, U256::from(1));
assert_eq!(el.transaction, request);
}
}

View File

@ -18,7 +18,6 @@
extern crate ethash;
use std::collections::HashSet;
use std::sync::{Arc, Weak, Mutex};
use std::ops::Deref;
use ethsync::{SyncProvider, SyncState};
@ -36,10 +35,9 @@ use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Act
use ethcore::log_entry::LogEntry;
use ethcore::filter::Filter as EthcoreFilter;
use self::ethash::SeedHashCompute;
use v1::traits::{Eth, EthFilter};
use v1::types::{Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, TransactionRequest, CallRequest, OptionalValue, Index, Filter, Log, Receipt};
use v1::helpers::{PollFilter, PollManager};
use v1::impls::{dispatch_transaction, sign_and_dispatch};
use v1::traits::Eth;
use v1::types::{Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, CallRequest, OptionalValue, Index, Filter, Log, Receipt};
use v1::impls::dispatch_transaction;
use serde;
/// Eth rpc implementation.
@ -170,6 +168,25 @@ impl<C, S, A, M, EM> EthClient<C, S, A, M, EM> where
}
}
pub fn pending_logs<M>(miner: &M, filter: &EthcoreFilter) -> Vec<Log> where M: MinerService {
let receipts = miner.pending_receipts();
let pending_logs = receipts.into_iter()
.flat_map(|(hash, r)| r.logs.into_iter().map(|l| (hash.clone(), l)).collect::<Vec<(H256, LogEntry)>>())
.collect::<Vec<(H256, LogEntry)>>();
let result = pending_logs.into_iter()
.filter(|pair| filter.matches(&pair.1))
.map(|pair| {
let mut log = Log::from(pair.1);
log.transaction_hash = Some(pair.0);
log
})
.collect();
result
}
const MAX_QUEUE_SIZE_TO_MINE_ON: usize = 4; // because uncles go back 6.
fn params_len(params: &Params) -> usize {
@ -193,25 +210,6 @@ fn from_params_default_third<F1, F2>(params: Params) -> Result<(F1, F2, BlockNum
}
}
fn pending_logs<M>(miner: &M, filter: &EthcoreFilter) -> Vec<Log> where M: MinerService {
let receipts = miner.pending_receipts();
let pending_logs = receipts.into_iter()
.flat_map(|(hash, r)| r.logs.into_iter().map(|l| (hash.clone(), l)).collect::<Vec<(H256, LogEntry)>>())
.collect::<Vec<(H256, LogEntry)>>();
let result = pending_logs.into_iter()
.filter(|pair| filter.matches(&pair.1))
.map(|pair| {
let mut log = Log::from(pair.1);
log.transaction_hash = Some(pair.0);
log
})
.collect();
result
}
// must be in range [-32099, -32000]
const UNSUPPORTED_REQUEST_CODE: i64 = -32000;
@ -496,23 +494,6 @@ impl<C, S, A, M, EM> Eth for EthClient<C, S, A, M, EM> where
})
}
fn sign(&self, params: Params) -> Result<Value, Error> {
from_params::<(Address, H256)>(params).and_then(|(addr, msg)| {
to_value(&take_weak!(self.accounts).sign(&addr, &msg).unwrap_or(H520::zero()))
})
}
fn send_transaction(&self, params: Params) -> Result<Value, Error> {
from_params::<(TransactionRequest, )>(params)
.and_then(|(request, )| {
let accounts = take_weak!(self.accounts);
match accounts.account_secret(&request.from) {
Ok(secret) => sign_and_dispatch(&self.client, &self.miner, request, secret),
Err(_) => to_value(&H256::zero())
}
})
}
fn send_raw_transaction(&self, params: Params) -> Result<Value, Error> {
from_params::<(Bytes, )>(params)
.and_then(|(raw_transaction, )| {
@ -563,186 +544,3 @@ impl<C, S, A, M, EM> Eth for EthClient<C, S, A, M, EM> where
rpc_unimplemented!()
}
}
/// Eth filter rpc implementation.
pub struct EthFilterClient<C, M> where
C: BlockChainClient,
M: MinerService {
client: Weak<C>,
miner: Weak<M>,
polls: Mutex<PollManager<PollFilter>>,
}
impl<C, M> EthFilterClient<C, M> where
C: BlockChainClient,
M: MinerService {
/// Creates new Eth filter client.
pub fn new(client: &Arc<C>, miner: &Arc<M>) -> Self {
EthFilterClient {
client: Arc::downgrade(client),
miner: Arc::downgrade(miner),
polls: Mutex::new(PollManager::new()),
}
}
}
impl<C, M> EthFilter for EthFilterClient<C, M> where
C: BlockChainClient + 'static,
M: MinerService + 'static {
fn new_filter(&self, params: Params) -> Result<Value, Error> {
from_params::<(Filter,)>(params)
.and_then(|(filter,)| {
let mut polls = self.polls.lock().unwrap();
let block_number = take_weak!(self.client).chain_info().best_block_number;
let id = polls.create_poll(PollFilter::Logs(block_number, Default::default(), filter));
to_value(&U256::from(id))
})
}
fn new_block_filter(&self, params: Params) -> Result<Value, Error> {
match params {
Params::None => {
let mut polls = self.polls.lock().unwrap();
let id = polls.create_poll(PollFilter::Block(take_weak!(self.client).chain_info().best_block_number));
to_value(&U256::from(id))
},
_ => Err(Error::invalid_params())
}
}
fn new_pending_transaction_filter(&self, params: Params) -> Result<Value, Error> {
match params {
Params::None => {
let mut polls = self.polls.lock().unwrap();
let pending_transactions = take_weak!(self.miner).pending_transactions_hashes();
let id = polls.create_poll(PollFilter::PendingTransaction(pending_transactions));
to_value(&U256::from(id))
},
_ => Err(Error::invalid_params())
}
}
fn filter_changes(&self, params: Params) -> Result<Value, Error> {
let client = take_weak!(self.client);
from_params::<(Index,)>(params)
.and_then(|(index,)| {
let mut polls = self.polls.lock().unwrap();
match polls.poll_mut(&index.value()) {
None => Ok(Value::Array(vec![] as Vec<Value>)),
Some(filter) => match *filter {
PollFilter::Block(ref mut block_number) => {
// + 1, cause we want to return hashes including current block hash.
let current_number = client.chain_info().best_block_number + 1;
let hashes = (*block_number..current_number).into_iter()
.map(BlockID::Number)
.filter_map(|id| client.block_hash(id))
.collect::<Vec<H256>>();
*block_number = current_number;
to_value(&hashes)
},
PollFilter::PendingTransaction(ref mut previous_hashes) => {
// get hashes of pending transactions
let current_hashes = take_weak!(self.miner).pending_transactions_hashes();
let new_hashes =
{
let previous_hashes_set = previous_hashes.iter().collect::<HashSet<_>>();
// find all new hashes
current_hashes
.iter()
.filter(|hash| !previous_hashes_set.contains(hash))
.cloned()
.collect::<Vec<H256>>()
};
// save all hashes of pending transactions
*previous_hashes = current_hashes;
// return new hashes
to_value(&new_hashes)
},
PollFilter::Logs(ref mut block_number, ref mut previous_logs, ref filter) => {
// retrive the current block number
let current_number = client.chain_info().best_block_number;
// check if we need to check pending hashes
let include_pending = filter.to_block == Some(BlockNumber::Pending);
// build appropriate filter
let mut filter: EthcoreFilter = filter.clone().into();
filter.from_block = BlockID::Number(*block_number);
filter.to_block = BlockID::Latest;
// retrieve logs in range from_block..min(BlockID::Latest..to_block)
let mut logs = client.logs(filter.clone())
.into_iter()
.map(From::from)
.collect::<Vec<Log>>();
// additionally retrieve pending logs
if include_pending {
let pending_logs = pending_logs(take_weak!(self.miner).deref(), &filter);
// remove logs about which client was already notified about
let new_pending_logs: Vec<_> = pending_logs.iter()
.filter(|p| !previous_logs.contains(p))
.cloned()
.collect();
// save all logs retrieved by client
*previous_logs = pending_logs.into_iter().collect();
// append logs array with new pending logs
logs.extend(new_pending_logs);
}
// save current block number as next from block number
*block_number = current_number;
to_value(&logs)
}
}
}
})
}
fn filter_logs(&self, params: Params) -> Result<Value, Error> {
from_params::<(Index,)>(params)
.and_then(|(index,)| {
let mut polls = self.polls.lock().unwrap();
match polls.poll(&index.value()) {
Some(&PollFilter::Logs(ref _block_number, ref _previous_log, ref filter)) => {
let include_pending = filter.to_block == Some(BlockNumber::Pending);
let filter: EthcoreFilter = filter.clone().into();
let mut logs = take_weak!(self.client).logs(filter.clone())
.into_iter()
.map(From::from)
.collect::<Vec<Log>>();
if include_pending {
logs.extend(pending_logs(take_weak!(self.miner).deref(), &filter));
}
to_value(&logs)
},
// just empty array
_ => Ok(Value::Array(vec![] as Vec<Value>)),
}
})
}
fn uninstall_filter(&self, params: Params) -> Result<Value, Error> {
from_params::<(Index,)>(params)
.and_then(|(index,)| {
self.polls.lock().unwrap().remove_poll(&index.value());
to_value(&true)
})
}
}

View File

@ -0,0 +1,214 @@
// 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/>.
//! Eth Filter RPC implementation
use std::ops::Deref;
use std::sync::{Arc, Weak, Mutex};
use std::collections::HashSet;
use jsonrpc_core::*;
use util::numbers::*;
use ethminer::MinerService;
use ethcore::filter::Filter as EthcoreFilter;
use ethcore::client::{BlockChainClient, BlockID};
use v1::traits::EthFilter;
use v1::types::{BlockNumber, Index, Filter, Log};
use v1::helpers::{PollFilter, PollManager};
use v1::impls::eth::pending_logs;
/// Eth filter rpc implementation.
pub struct EthFilterClient<C, M> where
C: BlockChainClient,
M: MinerService {
client: Weak<C>,
miner: Weak<M>,
polls: Mutex<PollManager<PollFilter>>,
}
impl<C, M> EthFilterClient<C, M> where
C: BlockChainClient,
M: MinerService {
/// Creates new Eth filter client.
pub fn new(client: &Arc<C>, miner: &Arc<M>) -> Self {
EthFilterClient {
client: Arc::downgrade(client),
miner: Arc::downgrade(miner),
polls: Mutex::new(PollManager::new()),
}
}
}
impl<C, M> EthFilter for EthFilterClient<C, M> where
C: BlockChainClient + 'static,
M: MinerService + 'static {
fn new_filter(&self, params: Params) -> Result<Value, Error> {
from_params::<(Filter,)>(params)
.and_then(|(filter,)| {
let mut polls = self.polls.lock().unwrap();
let block_number = take_weak!(self.client).chain_info().best_block_number;
let id = polls.create_poll(PollFilter::Logs(block_number, Default::default(), filter));
to_value(&U256::from(id))
})
}
fn new_block_filter(&self, params: Params) -> Result<Value, Error> {
match params {
Params::None => {
let mut polls = self.polls.lock().unwrap();
let id = polls.create_poll(PollFilter::Block(take_weak!(self.client).chain_info().best_block_number));
to_value(&U256::from(id))
},
_ => Err(Error::invalid_params())
}
}
fn new_pending_transaction_filter(&self, params: Params) -> Result<Value, Error> {
match params {
Params::None => {
let mut polls = self.polls.lock().unwrap();
let pending_transactions = take_weak!(self.miner).pending_transactions_hashes();
let id = polls.create_poll(PollFilter::PendingTransaction(pending_transactions));
to_value(&U256::from(id))
},
_ => Err(Error::invalid_params())
}
}
fn filter_changes(&self, params: Params) -> Result<Value, Error> {
let client = take_weak!(self.client);
from_params::<(Index,)>(params)
.and_then(|(index,)| {
let mut polls = self.polls.lock().unwrap();
match polls.poll_mut(&index.value()) {
None => Ok(Value::Array(vec![] as Vec<Value>)),
Some(filter) => match *filter {
PollFilter::Block(ref mut block_number) => {
// + 1, cause we want to return hashes including current block hash.
let current_number = client.chain_info().best_block_number + 1;
let hashes = (*block_number..current_number).into_iter()
.map(BlockID::Number)
.filter_map(|id| client.block_hash(id))
.collect::<Vec<H256>>();
*block_number = current_number;
to_value(&hashes)
},
PollFilter::PendingTransaction(ref mut previous_hashes) => {
// get hashes of pending transactions
let current_hashes = take_weak!(self.miner).pending_transactions_hashes();
let new_hashes =
{
let previous_hashes_set = previous_hashes.iter().collect::<HashSet<_>>();
// find all new hashes
current_hashes
.iter()
.filter(|hash| !previous_hashes_set.contains(hash))
.cloned()
.collect::<Vec<H256>>()
};
// save all hashes of pending transactions
*previous_hashes = current_hashes;
// return new hashes
to_value(&new_hashes)
},
PollFilter::Logs(ref mut block_number, ref mut previous_logs, ref filter) => {
// retrive the current block number
let current_number = client.chain_info().best_block_number;
// check if we need to check pending hashes
let include_pending = filter.to_block == Some(BlockNumber::Pending);
// build appropriate filter
let mut filter: EthcoreFilter = filter.clone().into();
filter.from_block = BlockID::Number(*block_number);
filter.to_block = BlockID::Latest;
// retrieve logs in range from_block..min(BlockID::Latest..to_block)
let mut logs = client.logs(filter.clone())
.into_iter()
.map(From::from)
.collect::<Vec<Log>>();
// additionally retrieve pending logs
if include_pending {
let pending_logs = pending_logs(take_weak!(self.miner).deref(), &filter);
// remove logs about which client was already notified about
let new_pending_logs: Vec<_> = pending_logs.iter()
.filter(|p| !previous_logs.contains(p))
.cloned()
.collect();
// save all logs retrieved by client
*previous_logs = pending_logs.into_iter().collect();
// append logs array with new pending logs
logs.extend(new_pending_logs);
}
// save current block number as next from block number
*block_number = current_number;
to_value(&logs)
}
}
}
})
}
fn filter_logs(&self, params: Params) -> Result<Value, Error> {
from_params::<(Index,)>(params)
.and_then(|(index,)| {
let mut polls = self.polls.lock().unwrap();
match polls.poll(&index.value()) {
Some(&PollFilter::Logs(ref _block_number, ref _previous_log, ref filter)) => {
let include_pending = filter.to_block == Some(BlockNumber::Pending);
let filter: EthcoreFilter = filter.clone().into();
let mut logs = take_weak!(self.client).logs(filter.clone())
.into_iter()
.map(From::from)
.collect::<Vec<Log>>();
if include_pending {
logs.extend(pending_logs(take_weak!(self.miner).deref(), &filter));
}
to_value(&logs)
},
// just empty array
_ => Ok(Value::Array(vec![] as Vec<Value>)),
}
})
}
fn uninstall_filter(&self, params: Params) -> Result<Value, Error> {
from_params::<(Index,)>(params)
.and_then(|(index,)| {
self.polls.lock().unwrap().remove_poll(&index.value());
to_value(&true)
})
}
}

View File

@ -0,0 +1,111 @@
// 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/>.
//! Eth Signing RPC implementation.
use std::sync::{Arc, Weak};
use jsonrpc_core::*;
use ethminer::MinerService;
use ethcore::client::BlockChainClient;
use util::numbers::*;
use util::keys::store::AccountProvider;
use v1::helpers::{SigningQueue, ConfirmationsQueue};
use v1::traits::EthSigning;
use v1::types::TransactionRequest;
use v1::impls::sign_and_dispatch;
/// Implementation of functions that require signing when no trusted signer is used.
pub struct EthSigningQueueClient {
queue: Weak<ConfirmationsQueue>,
}
impl EthSigningQueueClient {
/// Creates a new signing queue client given shared signing queue.
pub fn new(queue: &Arc<ConfirmationsQueue>) -> Self {
EthSigningQueueClient {
queue: Arc::downgrade(queue),
}
}
}
impl EthSigning for EthSigningQueueClient {
fn sign(&self, _params: Params) -> Result<Value, Error> {
// TODO [ToDr] Implement sign when rest of the signing queue is ready.
rpc_unimplemented!()
}
fn send_transaction(&self, params: Params) -> Result<Value, Error> {
from_params::<(TransactionRequest, )>(params)
.and_then(|(request, )| {
let queue = take_weak!(self.queue);
queue.add_request(request);
// TODO [ToDr] Block and wait for confirmation?
to_value(&H256::zero())
})
}
}
/// Implementation of functions that require signing when no trusted signer is used.
pub struct EthSigningUnsafeClient<C, A, M> where
C: BlockChainClient,
A: AccountProvider,
M: MinerService {
client: Weak<C>,
accounts: Weak<A>,
miner: Weak<M>,
}
impl<C, A, M> EthSigningUnsafeClient<C, A, M> where
C: BlockChainClient,
A: AccountProvider,
M: MinerService {
/// Creates new EthClient.
pub fn new(client: &Arc<C>, accounts: &Arc<A>, miner: &Arc<M>)
-> Self {
EthSigningUnsafeClient {
client: Arc::downgrade(client),
miner: Arc::downgrade(miner),
accounts: Arc::downgrade(accounts),
}
}
}
impl<C, A, M> EthSigning for EthSigningUnsafeClient<C, A, M> where
C: BlockChainClient + 'static,
A: AccountProvider + 'static,
M: MinerService + 'static {
fn sign(&self, params: Params) -> Result<Value, Error> {
from_params::<(Address, H256)>(params).and_then(|(addr, msg)| {
to_value(&take_weak!(self.accounts).sign(&addr, &msg).unwrap_or(H520::zero()))
})
}
fn send_transaction(&self, params: Params) -> Result<Value, Error> {
from_params::<(TransactionRequest, )>(params)
.and_then(|(request, )| {
let accounts = take_weak!(self.accounts);
match accounts.account_secret(&request.from) {
Ok(secret) => sign_and_dispatch(&self.client, &self.miner, request, secret),
Err(_) => to_value(&H256::zero())
}
})
}
}

View File

@ -31,16 +31,22 @@ macro_rules! rpc_unimplemented {
mod web3;
mod eth;
mod eth_filter;
mod eth_signing;
mod net;
mod personal;
mod personal_signer;
mod ethcore;
mod traces;
mod rpc;
pub use self::web3::Web3Client;
pub use self::eth::{EthClient, EthFilterClient};
pub use self::eth::EthClient;
pub use self::eth_filter::EthFilterClient;
pub use self::eth_signing::{EthSigningUnsafeClient, EthSigningQueueClient};
pub use self::net::NetClient;
pub use self::personal::PersonalClient;
pub use self::personal_signer::SignerClient;
pub use self::ethcore::EthcoreClient;
pub use self::traces::TracesClient;
pub use self::rpc::RpcClient;

View File

@ -20,7 +20,7 @@ use jsonrpc_core::*;
use v1::traits::Personal;
use v1::types::TransactionRequest;
use v1::impls::sign_and_dispatch;
use util::keys::store::*;
use util::keys::store::AccountProvider;
use util::numbers::*;
use ethcore::client::BlockChainClient;
use ethminer::MinerService;

View File

@ -0,0 +1,93 @@
// 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/>.
//! Transactions Confirmations (personal) rpc implementation
use std::sync::{Arc, Weak};
use jsonrpc_core::*;
use v1::traits::PersonalSigner;
use v1::types::TransactionModification;
use v1::impls::sign_and_dispatch;
use v1::helpers::{SigningQueue, ConfirmationsQueue};
use util::keys::store::AccountProvider;
use util::numbers::*;
use ethcore::client::BlockChainClient;
use ethminer::MinerService;
/// Transactions confirmation (personal) rpc implementation.
pub struct SignerClient<A, C, M>
where A: AccountProvider, C: BlockChainClient, M: MinerService {
queue: Weak<ConfirmationsQueue>,
accounts: Weak<A>,
client: Weak<C>,
miner: Weak<M>,
}
impl<A: 'static, C: 'static, M: 'static> SignerClient<A, C, M>
where A: AccountProvider, C: BlockChainClient, M: MinerService {
/// Create new instance of signer client.
pub fn new(store: &Arc<A>, client: &Arc<C>, miner: &Arc<M>, queue: &Arc<ConfirmationsQueue>) -> Self {
SignerClient {
queue: Arc::downgrade(queue),
accounts: Arc::downgrade(store),
client: Arc::downgrade(client),
miner: Arc::downgrade(miner),
}
}
}
impl<A: 'static, C: 'static, M: 'static> PersonalSigner for SignerClient<A, C, M>
where A: AccountProvider, C: BlockChainClient, M: MinerService {
fn transactions_to_confirm(&self, _params: Params) -> Result<Value, Error> {
let queue = take_weak!(self.queue);
to_value(&queue.requests())
}
fn confirm_transaction(&self, params: Params) -> Result<Value, Error> {
from_params::<(U256, TransactionModification, String)>(params).and_then(
|(id, modification, pass)| {
let accounts = take_weak!(self.accounts);
let queue = take_weak!(self.queue);
queue.remove_request(id)
.and_then(|confirmation| {
let mut request = confirmation.transaction;
// apply modification
if let Some(gas_price) = modification.gas_price {
request.gas_price = Some(gas_price);
}
match accounts.locked_account_secret(&request.from, &pass) {
Ok(secret) => Some(sign_and_dispatch(&self.client, &self.miner, request, secret)),
Err(_) => None
}
})
.unwrap_or_else(|| to_value(&H256::zero()))
}
)
}
fn reject_transaction(&self, params: Params) -> Result<Value, Error> {
from_params::<(U256, )>(params).and_then(
|(id, )| {
let queue = take_weak!(self.queue);
let res = queue.remove_request(id);
to_value(&res.is_some())
}
)
}
}

View File

@ -25,5 +25,6 @@ pub mod traits;
pub mod tests;
pub mod types;
pub use self::traits::{Web3, Eth, EthFilter, Personal, Net, Ethcore, Traces, Rpc};
pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Personal, PersonalSigner, Net, Ethcore, Traces, Rpc};
pub use self::impls::*;
pub use self::helpers::{SigningQueue, ConfirmationsQueue};

View File

@ -35,8 +35,8 @@ use util::keys::{AccountProvider, TestAccount, TestAccountProvider};
use jsonrpc_core::IoHandler;
use ethjson::blockchain::BlockChain;
use v1::traits::eth::Eth;
use v1::impls::EthClient;
use v1::traits::eth::{Eth, EthSigning};
use v1::impls::{EthClient, EthSigningUnsafeClient};
use v1::tests::helpers::{TestSyncProvider, Config};
fn account_provider() -> Arc<TestAccountProvider> {
@ -109,10 +109,15 @@ impl EthTester {
&miner_service,
&external_miner
);
let eth_sign = EthSigningUnsafeClient::new(
&client,
&account_provider,
&miner_service
);
let handler = IoHandler::new();
let delegate = eth_client.to_delegate();
handler.add_delegate(delegate);
handler.add_delegate(eth_client.to_delegate());
handler.add_delegate(eth_sign.to_delegate());
EthTester {
_miner: miner_service,

View File

@ -27,7 +27,7 @@ use ethcore::receipt::LocalizedReceipt;
use ethcore::transaction::{Transaction, Action};
use ethminer::{ExternalMiner, MinerService};
use ethsync::SyncState;
use v1::{Eth, EthClient};
use v1::{Eth, EthClient, EthSigning, EthSigningUnsafeClient};
use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService};
use rustc_serialize::hex::ToHex;
@ -72,8 +72,11 @@ impl Default for EthTester {
let hashrates = Arc::new(RwLock::new(HashMap::new()));
let external_miner = Arc::new(ExternalMiner::new(hashrates.clone()));
let eth = EthClient::new(&client, &sync, &ap, &miner, &external_miner).to_delegate();
let sign = EthSigningUnsafeClient::new(&client, &ap, &miner).to_delegate();
let io = IoHandler::new();
io.add_delegate(eth);
io.add_delegate(sign);
EthTester {
client: client,
sync: sync,

View File

@ -0,0 +1,75 @@
// 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/>.
use std::sync::Arc;
use jsonrpc_core::IoHandler;
use v1::impls::EthSigningQueueClient;
use v1::traits::EthSigning;
use v1::helpers::{ConfirmationsQueue, SigningQueue};
use util::keys::TestAccount;
struct EthSigningTester {
pub queue: Arc<ConfirmationsQueue>,
pub io: IoHandler,
}
impl Default for EthSigningTester {
fn default() -> Self {
let queue = Arc::new(ConfirmationsQueue::default());
let io = IoHandler::new();
io.add_delegate(EthSigningQueueClient::new(&queue).to_delegate());
EthSigningTester {
queue: queue,
io: io,
}
}
}
fn eth_signing() -> EthSigningTester {
EthSigningTester::default()
}
#[test]
fn should_add_transaction_to_queue() {
// given
let tester = eth_signing();
let account = TestAccount::new("123");
let address = account.address();
assert_eq!(tester.queue.requests().len(), 0);
// when
let request = r#"{
"jsonrpc": "2.0",
"method": "eth_sendTransaction",
"params": [{
"from": ""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"",
"to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
"gas": "0x76c0",
"gasPrice": "0x9184e72a000",
"value": "0x9184e72a"
}],
"id": 1
}"#;
let response = r#"{"jsonrpc":"2.0","result":"0x0000000000000000000000000000000000000000000000000000000000000000","id":1}"#;
// then
assert_eq!(tester.io.handle_request(&request), Some(response.to_owned()));
assert_eq!(tester.queue.requests().len(), 1);
}

View File

@ -18,8 +18,10 @@
//! method calls properly.
mod eth;
mod eth_signing;
mod net;
mod web3;
mod personal;
mod personal_signer;
mod ethcore;
mod rpc;

View File

@ -0,0 +1,169 @@
// 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/>.
use std::sync::Arc;
use std::str::FromStr;
use std::collections::HashMap;
use jsonrpc_core::IoHandler;
use util::numbers::*;
use util::keys::{TestAccount, TestAccountProvider};
use ethcore::client::TestBlockChainClient;
use ethcore::transaction::{Transaction, Action};
use v1::{SignerClient, PersonalSigner};
use v1::tests::helpers::TestMinerService;
use v1::helpers::{SigningQueue, ConfirmationsQueue};
use v1::types::TransactionRequest;
struct PersonalSignerTester {
queue: Arc<ConfirmationsQueue>,
accounts: Arc<TestAccountProvider>,
io: IoHandler,
miner: Arc<TestMinerService>,
// these unused fields are necessary to keep the data alive
// as the handler has only weak pointers.
_client: Arc<TestBlockChainClient>,
}
fn blockchain_client() -> Arc<TestBlockChainClient> {
let client = TestBlockChainClient::new();
Arc::new(client)
}
fn accounts_provider() -> Arc<TestAccountProvider> {
let accounts = HashMap::new();
let ap = TestAccountProvider::new(accounts);
Arc::new(ap)
}
fn miner_service() -> Arc<TestMinerService> {
Arc::new(TestMinerService::default())
}
fn signer_tester() -> PersonalSignerTester {
let queue = Arc::new(ConfirmationsQueue::default());
let accounts = accounts_provider();
let client = blockchain_client();
let miner = miner_service();
let io = IoHandler::new();
io.add_delegate(SignerClient::new(&accounts, &client, &miner, &queue).to_delegate());
PersonalSignerTester {
queue: queue,
accounts: accounts,
io: io,
miner: miner,
_client: client,
}
}
#[test]
fn should_return_list_of_transactions_in_queue() {
// given
let tester = signer_tester();
tester.queue.add_request(TransactionRequest {
from: Address::from(1),
to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()),
gas_price: Some(U256::from(10_000)),
gas: Some(U256::from(10_000_000)),
value: Some(U256::from(1)),
data: None,
nonce: None,
});
// when
let request = r#"{"jsonrpc":"2.0","method":"personal_transactionsToConfirm","params":[],"id":1}"#;
let response = r#"{"jsonrpc":"2.0","result":[{"id":"0x01","transaction":{"data":null,"from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x01"}}],"id":1}"#;
// then
assert_eq!(tester.io.handle_request(&request), Some(response.to_owned()));
}
#[test]
fn should_reject_transaction_from_queue_without_dispatching() {
// given
let tester = signer_tester();
tester.queue.add_request(TransactionRequest {
from: Address::from(1),
to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()),
gas_price: Some(U256::from(10_000)),
gas: Some(U256::from(10_000_000)),
value: Some(U256::from(1)),
data: None,
nonce: None,
});
assert_eq!(tester.queue.requests().len(), 1);
// when
let request = r#"{"jsonrpc":"2.0","method":"personal_rejectTransaction","params":["0x01"],"id":1}"#;
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
// then
assert_eq!(tester.io.handle_request(&request), Some(response.to_owned()));
assert_eq!(tester.queue.requests().len(), 0);
assert_eq!(tester.miner.imported_transactions.lock().unwrap().len(), 0);
}
#[test]
fn should_confirm_transaction_and_dispatch() {
// given
let tester = signer_tester();
let account = TestAccount::new("test");
let address = account.address();
let secret = account.secret.clone();
let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap();
tester.accounts.accounts
.write()
.unwrap()
.insert(address, account);
tester.queue.add_request(TransactionRequest {
from: address,
to: Some(recipient),
gas_price: Some(U256::from(10_000)),
gas: Some(U256::from(10_000_000)),
value: Some(U256::from(1)),
data: None,
nonce: None,
});
let t = Transaction {
nonce: U256::zero(),
gas_price: U256::from(0x1000),
gas: U256::from(10_000_000),
action: Action::Call(recipient),
value: U256::from(0x1),
data: vec![]
}.sign(&secret);
assert_eq!(tester.queue.requests().len(), 1);
// when
let request = r#"{
"jsonrpc":"2.0",
"method":"personal_confirmTransaction",
"params":["0x01", {"gasPrice":"0x1000"}, "test"],
"id":1
}"#;
let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#;
// then
assert_eq!(tester.io.handle_request(&request), Some(response.to_owned()));
assert_eq!(tester.queue.requests().len(), 0);
assert_eq!(tester.miner.imported_transactions.lock().unwrap().len(), 1);
}

View File

@ -74,12 +74,6 @@ pub trait Eth: Sized + Send + Sync + 'static {
/// Returns the code at given address at given time (block number).
fn code_at(&self, _: Params) -> Result<Value, Error>;
/// Signs the data with given address signature.
fn sign(&self, _: Params) -> Result<Value, Error>;
/// Sends transaction.
fn send_transaction(&self, _: Params) -> Result<Value, Error>;
/// Sends signed transaction.
fn send_raw_transaction(&self, _: Params) -> Result<Value, Error>;
@ -150,8 +144,6 @@ pub trait Eth: Sized + Send + Sync + 'static {
delegate.add_method("eth_getUncleCountByBlockHash", Eth::block_uncles_count_by_hash);
delegate.add_method("eth_getUncleCountByBlockNumber", Eth::block_uncles_count_by_number);
delegate.add_method("eth_getCode", Eth::code_at);
delegate.add_method("eth_sign", Eth::sign);
delegate.add_method("eth_sendTransaction", Eth::send_transaction);
delegate.add_method("eth_sendRawTransaction", Eth::send_raw_transaction);
delegate.add_method("eth_call", Eth::call);
delegate.add_method("eth_estimateGas", Eth::estimate_gas);
@ -208,3 +200,20 @@ pub trait EthFilter: Sized + Send + Sync + 'static {
delegate
}
}
/// Signing methods implementation relying on unlocked accounts.
pub trait EthSigning: Sized + Send + Sync + 'static {
/// Signs the data with given address signature.
fn sign(&self, _: Params) -> Result<Value, Error>;
/// Sends transaction.
fn send_transaction(&self, _: Params) -> Result<Value, Error>;
/// Should be used to convert object to io delegate.
fn to_delegate(self) -> IoDelegate<Self> {
let mut delegate = IoDelegate::new(Arc::new(self));
delegate.add_method("eth_sign", EthSigning::sign);
delegate.add_method("eth_sendTransaction", EthSigning::send_transaction);
delegate
}
}

View File

@ -25,9 +25,11 @@ pub mod traces;
pub mod rpc;
pub use self::web3::Web3;
pub use self::eth::{Eth, EthFilter};
pub use self::eth::{Eth, EthFilter, EthSigning};
pub use self::net::Net;
pub use self::personal::Personal;
pub use self::personal::{Personal, PersonalSigner};
pub use self::ethcore::Ethcore;
pub use self::traces::Traces;
pub use self::rpc::Rpc;

View File

@ -43,3 +43,26 @@ pub trait Personal: Sized + Send + Sync + 'static {
delegate
}
}
/// Personal extension for transactions confirmations rpc interface.
pub trait PersonalSigner: Sized + Send + Sync + 'static {
/// Returns a list of transactions to confirm.
fn transactions_to_confirm(&self, _: Params) -> Result<Value, Error>;
/// Confirm and send a specific transaction.
fn confirm_transaction(&self, _: Params) -> Result<Value, Error>;
/// Reject the transaction request.
fn reject_transaction(&self, _: Params) -> Result<Value, Error>;
/// Should be used to convert object to io delegate.
fn to_delegate(self) -> IoDelegate<Self> {
let mut delegate = IoDelegate::new(Arc::new(self));
delegate.add_method("personal_transactionsToConfirm", PersonalSigner::transactions_to_confirm);
delegate.add_method("personal_confirmTransaction", PersonalSigner::confirm_transaction);
delegate.add_method("personal_rejectTransaction", PersonalSigner::reject_transaction);
delegate
}
}

View File

@ -38,7 +38,7 @@ pub use self::log::Log;
pub use self::optionals::OptionalValue;
pub use self::sync::{SyncStatus, SyncInfo};
pub use self::transaction::Transaction;
pub use self::transaction_request::TransactionRequest;
pub use self::transaction_request::{TransactionRequest, TransactionConfirmation, TransactionModification};
pub use self::call_request::CallRequest;
pub use self::receipt::Receipt;
pub use self::trace::Trace;

View File

@ -21,7 +21,7 @@ use util::numbers::U256;
use v1::types::bytes::Bytes;
/// Transaction request coming from RPC
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Deserialize)]
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct TransactionRequest {
/// Sender
pub from: Address,
@ -40,6 +40,24 @@ pub struct TransactionRequest {
pub nonce: Option<U256>,
}
/// Transaction confirmation waiting in a queue
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize)]
pub struct TransactionConfirmation {
/// Id of this confirmation
pub id: U256,
/// TransactionRequest
pub transaction: TransactionRequest,
}
/// Possible modifications to the confirmed transaction sent by SystemUI
#[derive(Debug, PartialEq, Deserialize)]
pub struct TransactionModification {
/// Modified gas price
#[serde(rename="gasPrice")]
pub gas_price: Option<U256>,
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
@ -135,5 +153,26 @@ mod tests {
nonce: None,
});
}
#[test]
fn should_deserialize_modification() {
// given
let s1 = r#"{
"gasPrice":"0x0ba43b7400"
}"#;
let s2 = r#"{}"#;
// when
let res1: TransactionModification = serde_json::from_str(s1).unwrap();
let res2: TransactionModification = serde_json::from_str(s2).unwrap();
// then
assert_eq!(res1, TransactionModification {
gas_price: Some(U256::from_str("0ba43b7400").unwrap()),
});
assert_eq!(res2, TransactionModification {
gas_price: None,
});
}
}

View File

@ -9,13 +9,8 @@ build = "build.rs"
[build-dependencies]
rustc_version = "0.1"
serde_codegen = { version = "0.7.0", optional = true }
syntex = "^0.32.0"
[dependencies]
serde = "0.7.0"
serde_json = "0.7.0"
rustc-serialize = "0.3"
jsonrpc-core = "2.0"
log = "0.3"
env_logger = "0.3"
@ -23,10 +18,7 @@ ws = "0.4.7"
ethcore-util = { path = "../util" }
ethcore-rpc = { path = "../rpc" }
serde_macros = { version = "0.7.0", optional = true }
clippy = { version = "0.0.69", optional = true}
[features]
default = ["serde_codegen"]
nightly = ["serde_macros"]
dev = ["clippy"]

View File

@ -19,34 +19,7 @@ extern crate rustc_version;
use rustc_version::{version_meta, Channel};
fn main() {
serde::main();
if let Channel::Nightly = version_meta().channel {
println!("cargo:rustc-cfg=nightly");
}
}
#[cfg(not(feature = "serde_macros"))]
mod serde {
extern crate syntex;
extern crate serde_codegen;
use std::env;
use std::path::Path;
pub fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let src = Path::new("src/types/mod.rs.in");
let dst = Path::new(&out_dir).join("mod.rs");
let mut registry = syntex::Registry::new();
serde_codegen::register(&mut registry);
registry.expand("", &src, &dst).unwrap();
}
}
#[cfg(feature = "serde_macros")]
mod serde {
pub fn main() {}
}

View File

@ -17,8 +17,6 @@
#![warn(missing_docs)]
#![cfg_attr(all(nightly, feature="dev"), feature(plugin))]
#![cfg_attr(all(nightly, feature="dev"), plugin(clippy))]
// Generated by serde
#![cfg_attr(all(nightly, feature="dev"), allow(redundant_closure_call))]
//! Signer module
//!
@ -45,18 +43,12 @@
extern crate log;
extern crate env_logger;
extern crate serde;
extern crate serde_json;
extern crate rustc_serialize;
extern crate ethcore_util as util;
extern crate ethcore_rpc as rpc;
extern crate jsonrpc_core;
extern crate ws;
mod signing_queue;
mod ws_server;
pub use ws_server::*;
#[cfg(test)]

View File

@ -1,74 +0,0 @@
// 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/>.
use std::collections::HashSet;
use rpc::v1::types::TransactionRequest;
pub trait SigningQueue {
fn add_request(&mut self, transaction: TransactionRequest);
fn remove_request(&mut self, id: TransactionRequest);
fn requests(&self) -> &HashSet<TransactionRequest>;
}
impl SigningQueue for HashSet<TransactionRequest> {
fn add_request(&mut self, transaction: TransactionRequest) {
self.insert(transaction);
}
fn remove_request(&mut self, id: TransactionRequest) {
self.remove(&id);
}
fn requests(&self) -> &HashSet<TransactionRequest> {
self
}
}
#[cfg(test)]
mod test {
use std::collections::HashSet;
use util::hash::Address;
use util::numbers::U256;
use rpc::v1::types::TransactionRequest;
use super::*;
#[test]
fn should_work_for_hashset() {
// given
let mut queue = HashSet::new();
let request = TransactionRequest {
from: Address::from(1),
to: Some(Address::from(2)),
gas_price: None,
gas: None,
value: Some(U256::from(10_000_000)),
data: None,
nonce: None,
};
// when
queue.add_request(request.clone());
let all = queue.requests();
// then
assert_eq!(all.len(), 1);
assert!(all.contains(&request));
}
}

View File

@ -1,23 +0,0 @@
// 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/>.
//! Reusable types with JSON Serialization.
#[cfg(feature = "serde_macros")]
include!("mod.rs.in");
#[cfg(not(feature = "serde_macros"))]
include!(concat!(env!("OUT_DIR"), "/mod.rs"));

View File

@ -1,25 +0,0 @@
// 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/>.
// TODO [ToDr] Types are empty for now. But they are about to come.

View File

@ -25,6 +25,7 @@ use std::sync::Arc;
use std::net::SocketAddr;
use util::panics::{PanicHandler, OnPanicListener, MayPanic};
use jsonrpc_core::{IoHandler, IoDelegate};
use rpc::Extendable;
mod session;
@ -57,6 +58,12 @@ impl Default for ServerBuilder {
}
}
impl Extendable for ServerBuilder {
fn add_delegate<D: Send + Sync + 'static>(&self, delegate: IoDelegate<D>) {
self.handler.add_delegate(delegate);
}
}
impl ServerBuilder {
/// Creates new `ServerBuilder`
pub fn new() -> Self {
@ -65,11 +72,6 @@ impl ServerBuilder {
}
}
/// Adds rpc delegate
pub fn add_delegate<D>(&self, delegate: IoDelegate<D>) where D: Send + Sync + 'static {
self.handler.add_delegate(delegate);
}
/// Starts a new `WebSocket` server in separate thread.
/// Returns a `Server` handle which closes the server when droped.
pub fn start(self, addr: SocketAddr) -> Result<Server, ServerError> {