Merge branch 'master' into on-demand-priority
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
description = "Ethcore jsonrpc"
|
||||
name = "ethcore-rpc"
|
||||
description = "Parity JSON-RPC servers."
|
||||
name = "parity-rpc"
|
||||
version = "1.7.0"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
@@ -17,16 +17,20 @@ serde = "0.9"
|
||||
serde_derive = "0.9"
|
||||
serde_json = "0.9"
|
||||
time = "0.1"
|
||||
tokio-timer = "0.1"
|
||||
transient-hashmap = "0.4"
|
||||
cid = "0.2.1"
|
||||
multihash = "0.5"
|
||||
rust-crypto = "0.2.36"
|
||||
rand = "0.3"
|
||||
|
||||
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
|
||||
jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
|
||||
jsonrpc-minihttp-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
|
||||
jsonrpc-ws-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
|
||||
jsonrpc-ipc-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
|
||||
jsonrpc-macros = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
|
||||
jsonrpc-pubsub = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
|
||||
|
||||
ethcore-io = { path = "../util/io" }
|
||||
ethcore-ipc = { path = "../ipc/rpc" }
|
||||
@@ -48,6 +52,7 @@ fetch = { path = "../util/fetch" }
|
||||
stats = { path = "../util/stats" }
|
||||
|
||||
clippy = { version = "0.0.103", optional = true}
|
||||
pretty_assertions = "0.1"
|
||||
|
||||
[features]
|
||||
dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev"]
|
||||
|
||||
@@ -8,10 +8,10 @@ authors = ["Parity Technologies <admin@parity.io>"]
|
||||
[dependencies]
|
||||
ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" }
|
||||
docopt = "0.7"
|
||||
rustc-serialize = "0.3"
|
||||
ethcore = { path = "../../ethcore" }
|
||||
ethcore-devtools = { path = "../../devtools" }
|
||||
ethcore-rpc = { path = ".." }
|
||||
ethcore-util = { path = "../../util" }
|
||||
ethjson = { path = "../../json" }
|
||||
parity-rpc = { path = ".." }
|
||||
rustc-serialize = "0.3"
|
||||
serde_json = "0.8"
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
|
||||
extern crate ctrlc;
|
||||
extern crate docopt;
|
||||
extern crate rustc_serialize;
|
||||
extern crate serde_json;
|
||||
extern crate ethjson;
|
||||
extern crate ethcore_util as util;
|
||||
extern crate ethcore;
|
||||
extern crate ethcore_devtools as devtools;
|
||||
extern crate ethcore_rpc as rpc;
|
||||
extern crate ethcore_util as util;
|
||||
extern crate ethjson;
|
||||
extern crate parity_rpc as rpc;
|
||||
extern crate rustc_serialize;
|
||||
extern crate serde_json;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex, Condvar};
|
||||
|
||||
@@ -26,15 +26,18 @@ extern crate semver;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
extern crate time;
|
||||
extern crate tokio_timer;
|
||||
extern crate transient_hashmap;
|
||||
extern crate cid;
|
||||
extern crate multihash;
|
||||
extern crate crypto as rust_crypto;
|
||||
extern crate rand;
|
||||
|
||||
extern crate jsonrpc_core;
|
||||
extern crate jsonrpc_http_server as http;
|
||||
extern crate jsonrpc_minihttp_server as minihttp;
|
||||
extern crate jsonrpc_ipc_server as ipc;
|
||||
extern crate jsonrpc_minihttp_server as minihttp;
|
||||
extern crate jsonrpc_pubsub;
|
||||
|
||||
extern crate ethash;
|
||||
extern crate ethcore;
|
||||
@@ -66,14 +69,21 @@ extern crate ethjson;
|
||||
#[cfg(test)]
|
||||
extern crate ethcore_devtools as devtools;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
|
||||
pub extern crate jsonrpc_ws_server as ws;
|
||||
|
||||
mod metadata;
|
||||
pub mod v1;
|
||||
|
||||
pub use jsonrpc_pubsub::Session as PubSubSession;
|
||||
pub use ipc::{Server as IpcServer, MetaExtractor as IpcMetaExtractor, RequestContext as IpcRequestContext};
|
||||
pub use http::{
|
||||
hyper,
|
||||
RequestMiddleware, RequestMiddlewareAction,
|
||||
AccessControlAllowOrigin, Host,
|
||||
AccessControlAllowOrigin, Host, DomainsValidation
|
||||
};
|
||||
|
||||
pub use v1::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, Metadata, Origin, informant, dispatch};
|
||||
@@ -193,3 +203,28 @@ pub fn start_ipc<M, S, H, T>(
|
||||
.session_metadata_extractor(extractor)
|
||||
.start(addr)
|
||||
}
|
||||
|
||||
/// Start WS server and return `Server` handle.
|
||||
pub fn start_ws<M, S, H, T, U>(
|
||||
addr: &SocketAddr,
|
||||
handler: H,
|
||||
remote: tokio_core::reactor::Remote,
|
||||
allowed_origins: ws::DomainsValidation<ws::Origin>,
|
||||
allowed_hosts: ws::DomainsValidation<ws::Host>,
|
||||
extractor: T,
|
||||
stats: U,
|
||||
) -> Result<ws::Server, ws::Error> where
|
||||
M: jsonrpc_core::Metadata,
|
||||
S: jsonrpc_core::Middleware<M>,
|
||||
H: Into<jsonrpc_core::MetaIoHandler<M, S>>,
|
||||
T: ws::MetaExtractor<M>,
|
||||
U: ws::SessionStats,
|
||||
{
|
||||
ws::ServerBuilder::new(handler)
|
||||
.event_loop_remote(remote)
|
||||
.allowed_origins(allowed_origins)
|
||||
.allowed_hosts(allowed_hosts)
|
||||
.session_meta_extractor(extractor)
|
||||
.session_stats(stats)
|
||||
.start(addr)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ use ethcore::miner::MinerService;
|
||||
use ethcore::client::MiningBlockChainClient;
|
||||
use ethcore::transaction::{Action, SignedTransaction, PendingTransaction, Transaction};
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use crypto::DEFAULT_MAC;
|
||||
|
||||
use jsonrpc_core::Error;
|
||||
use v1::helpers::{errors, TransactionRequest, FilledTransactionRequest, ConfirmationPayload};
|
||||
@@ -58,7 +59,7 @@ pub trait Dispatcher: Send + Sync + Clone {
|
||||
// type Out<T>: IntoFuture<T, Error>
|
||||
|
||||
/// Fill optional fields of a transaction request, fetching gas price but not nonce.
|
||||
fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address)
|
||||
fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address, force_nonce: bool)
|
||||
-> BoxFuture<FilledTransactionRequest, Error>;
|
||||
|
||||
/// Sign the given transaction request without dispatching, fetching appropriate nonce.
|
||||
@@ -96,17 +97,30 @@ impl<C, M> Clone for FullDispatcher<C, M> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: MiningBlockChainClient, M: MinerService> FullDispatcher<C, M> {
|
||||
fn fill_nonce(nonce: Option<U256>, from: &Address, miner: &M, client: &C) -> U256 {
|
||||
nonce
|
||||
.or_else(|| miner.last_nonce(from).map(|nonce| nonce + U256::one()))
|
||||
.unwrap_or_else(|| client.latest_nonce(from))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: MiningBlockChainClient, M: MinerService> Dispatcher for FullDispatcher<C, M> {
|
||||
fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address)
|
||||
fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address, force_nonce: bool)
|
||||
-> BoxFuture<FilledTransactionRequest, Error>
|
||||
{
|
||||
let (client, miner) = (take_weakf!(self.client), take_weakf!(self.miner));
|
||||
let request = request;
|
||||
let from = request.from.unwrap_or(default_sender);
|
||||
let nonce = match force_nonce {
|
||||
false => request.nonce,
|
||||
true => Some(Self::fill_nonce(request.nonce, &from, &miner, &client)),
|
||||
};
|
||||
future::ok(FilledTransactionRequest {
|
||||
from: request.from.unwrap_or(default_sender),
|
||||
from: from,
|
||||
used_default_from: request.from.is_none(),
|
||||
to: request.to,
|
||||
nonce: request.nonce,
|
||||
nonce: nonce,
|
||||
gas_price: request.gas_price.unwrap_or_else(|| default_gas_price(&*client, &*miner)),
|
||||
gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()),
|
||||
value: request.value.unwrap_or_else(|| 0.into()),
|
||||
@@ -123,12 +137,7 @@ impl<C: MiningBlockChainClient, M: MinerService> Dispatcher for FullDispatcher<C
|
||||
let address = filled.from;
|
||||
future::done({
|
||||
let t = Transaction {
|
||||
nonce: filled.nonce
|
||||
.or_else(|| miner
|
||||
.last_nonce(&filled.from)
|
||||
.map(|nonce| nonce + U256::one()))
|
||||
.unwrap_or_else(|| client.latest_nonce(&filled.from)),
|
||||
|
||||
nonce: Self::fill_nonce(filled.nonce, &filled.from, &miner, &client),
|
||||
action: filled.to.map_or(Action::Create, Action::Call),
|
||||
gas: filled.gas,
|
||||
gas_price: filled.gas_price,
|
||||
@@ -206,6 +215,17 @@ pub fn fetch_gas_price_corpus(
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a eth_sign-compatible hash of data to sign.
|
||||
/// The data is prepended with special message to prevent
|
||||
/// chosen-plaintext attacks.
|
||||
pub fn eth_data_hash(mut data: Bytes) -> H256 {
|
||||
let mut message_data =
|
||||
format!("\x19Ethereum Signed Message:\n{}", data.len())
|
||||
.into_bytes();
|
||||
message_data.append(&mut data);
|
||||
message_data.sha3()
|
||||
}
|
||||
|
||||
/// Dispatcher for light clients -- fetches default gas price, next nonce, etc. from network.
|
||||
#[derive(Clone)]
|
||||
pub struct LightDispatcher {
|
||||
@@ -278,18 +298,20 @@ impl LightDispatcher {
|
||||
}
|
||||
|
||||
impl Dispatcher for LightDispatcher {
|
||||
fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address)
|
||||
fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address, force_nonce: bool)
|
||||
-> BoxFuture<FilledTransactionRequest, Error>
|
||||
{
|
||||
const DEFAULT_GAS_PRICE: U256 = U256([0, 0, 0, 21_000_000]);
|
||||
|
||||
let gas_limit = self.client.best_block_header().gas_limit();
|
||||
let request_gas_price = request.gas_price.clone();
|
||||
let request_nonce = request.nonce.clone();
|
||||
let from = request.from.unwrap_or(default_sender);
|
||||
|
||||
let with_gas_price = move |gas_price| {
|
||||
let request = request;
|
||||
FilledTransactionRequest {
|
||||
from: request.from.unwrap_or(default_sender),
|
||||
from: from.clone(),
|
||||
used_default_from: request.from.is_none(),
|
||||
to: request.to,
|
||||
nonce: request.nonce,
|
||||
@@ -302,7 +324,7 @@ impl Dispatcher for LightDispatcher {
|
||||
};
|
||||
|
||||
// fast path for known gas price.
|
||||
match request_gas_price {
|
||||
let gas_price = match request_gas_price {
|
||||
Some(gas_price) => future::ok(with_gas_price(gas_price)).boxed(),
|
||||
None => fetch_gas_price_corpus(
|
||||
self.sync.clone(),
|
||||
@@ -313,6 +335,20 @@ impl Dispatcher for LightDispatcher {
|
||||
Some(median) => future::ok(*median),
|
||||
None => future::ok(DEFAULT_GAS_PRICE), // fall back to default on error.
|
||||
}).map(with_gas_price).boxed()
|
||||
};
|
||||
|
||||
match (request_nonce, force_nonce) {
|
||||
(_, false) | (Some(_), true) => gas_price,
|
||||
(None, true) => {
|
||||
let next_nonce = self.next_nonce(from);
|
||||
gas_price.and_then(move |mut filled| next_nonce
|
||||
.map_err(|_| errors::no_light_peers())
|
||||
.map(move |nonce| {
|
||||
filled.nonce = Some(nonce);
|
||||
filled
|
||||
})
|
||||
).boxed()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,9 +402,6 @@ impl Dispatcher for LightDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
/// default MAC to use.
|
||||
pub const DEFAULT_MAC: [u8; 2] = [0, 0];
|
||||
|
||||
/// Single-use account token.
|
||||
pub type AccountToken = String;
|
||||
|
||||
@@ -475,21 +508,11 @@ pub fn execute<D: Dispatcher + 'static>(
|
||||
.map(ConfirmationResponse::SignTransaction)
|
||||
).boxed()
|
||||
},
|
||||
ConfirmationPayload::EthSignMessage(address, mut data) => {
|
||||
let mut message_data =
|
||||
format!("\x19Ethereum Signed Message:\n{}", data.len())
|
||||
.into_bytes();
|
||||
message_data.append(&mut data);
|
||||
let res = signature(&accounts, address, message_data.sha3(), pass)
|
||||
ConfirmationPayload::EthSignMessage(address, data) => {
|
||||
let hash = eth_data_hash(data);
|
||||
let res = signature(&accounts, address, hash, pass)
|
||||
.map(|result| result
|
||||
.map(|rsv| {
|
||||
let mut vrs = [0u8; 65];
|
||||
let rsv = rsv.as_ref();
|
||||
vrs[0] = rsv[64] + 27;
|
||||
vrs[1..33].copy_from_slice(&rsv[0..32]);
|
||||
vrs[33..65].copy_from_slice(&rsv[32..64]);
|
||||
H520(vrs)
|
||||
})
|
||||
.map(|rsv| H520(rsv.into_vrs()))
|
||||
.map(RpcH520::from)
|
||||
.map(ConfirmationResponse::Signature)
|
||||
);
|
||||
@@ -563,12 +586,12 @@ pub fn from_rpc<D>(payload: RpcConfirmationPayload, default_account: Address, di
|
||||
{
|
||||
match payload {
|
||||
RpcConfirmationPayload::SendTransaction(request) => {
|
||||
dispatcher.fill_optional_fields(request.into(), default_account)
|
||||
dispatcher.fill_optional_fields(request.into(), default_account, false)
|
||||
.map(ConfirmationPayload::SendTransaction)
|
||||
.boxed()
|
||||
},
|
||||
RpcConfirmationPayload::SignTransaction(request) => {
|
||||
dispatcher.fill_optional_fields(request.into(), default_account)
|
||||
dispatcher.fill_optional_fields(request.into(), default_account, false)
|
||||
.map(ConfirmationPayload::SignTransaction)
|
||||
.boxed()
|
||||
},
|
||||
|
||||
@@ -25,6 +25,7 @@ pub mod light_fetch;
|
||||
pub mod informant;
|
||||
pub mod oneshot;
|
||||
pub mod ipfs;
|
||||
pub mod secretstore;
|
||||
|
||||
mod network_settings;
|
||||
mod poll_manager;
|
||||
@@ -32,6 +33,7 @@ mod poll_filter;
|
||||
mod requests;
|
||||
mod signer;
|
||||
mod signing_queue;
|
||||
mod subscription_manager;
|
||||
|
||||
pub use self::dispatch::{Dispatcher, FullDispatcher};
|
||||
pub use self::network_settings::NetworkSettings;
|
||||
@@ -45,3 +47,4 @@ pub use self::signing_queue::{
|
||||
QUEUE_LIMIT as SIGNING_QUEUE_LIMIT,
|
||||
};
|
||||
pub use self::signer::SignerService;
|
||||
pub use self::subscription_manager::GenericPollManager;
|
||||
|
||||
127
rpc/src/v1/helpers/secretstore.rs
Normal file
127
rpc/src/v1/helpers/secretstore.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
// 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 std::iter::repeat;
|
||||
use rand::{Rng, OsRng};
|
||||
use ethkey::{Public, Secret, math};
|
||||
use crypto;
|
||||
use util::Bytes;
|
||||
use jsonrpc_core::Error;
|
||||
use v1::helpers::errors;
|
||||
|
||||
/// Initialization vector length.
|
||||
const INIT_VEC_LEN: usize = 16;
|
||||
|
||||
/// Encrypt document with distributely generated key.
|
||||
pub fn encrypt_document(key: Bytes, document: Bytes) -> Result<Bytes, Error> {
|
||||
// make document key
|
||||
let key = into_document_key(key)?;
|
||||
|
||||
// use symmetric encryption to encrypt document
|
||||
let iv = initialization_vector();
|
||||
let mut encrypted_document = Vec::with_capacity(document.len() + iv.len());
|
||||
encrypted_document.extend(repeat(0).take(document.len()));
|
||||
crypto::aes::encrypt(&key, &iv, &document, &mut encrypted_document);
|
||||
encrypted_document.extend_from_slice(&iv);
|
||||
|
||||
Ok(encrypted_document)
|
||||
}
|
||||
|
||||
/// Decrypt document with distributely generated key.
|
||||
pub fn decrypt_document(key: Bytes, mut encrypted_document: Bytes) -> Result<Bytes, Error> {
|
||||
// initialization vector takes INIT_VEC_LEN bytes
|
||||
let encrypted_document_len = encrypted_document.len();
|
||||
if encrypted_document_len < INIT_VEC_LEN {
|
||||
return Err(errors::invalid_params("encrypted_document", "invalid encrypted data"));
|
||||
}
|
||||
|
||||
// make document key
|
||||
let key = into_document_key(key)?;
|
||||
|
||||
// use symmetric decryption to decrypt document
|
||||
let iv = encrypted_document.split_off(encrypted_document_len - INIT_VEC_LEN);
|
||||
let mut document = Vec::with_capacity(encrypted_document_len - INIT_VEC_LEN);
|
||||
document.extend(repeat(0).take(encrypted_document_len - INIT_VEC_LEN));
|
||||
crypto::aes::decrypt(&key, &iv, &encrypted_document, &mut document);
|
||||
|
||||
Ok(document)
|
||||
}
|
||||
|
||||
pub fn decrypt_document_with_shadow(decrypted_secret: Public, common_point: Public, shadows: Vec<Secret>, encrypted_document: Bytes) -> Result<Bytes, Error> {
|
||||
let key = decrypt_with_shadow_coefficients(decrypted_secret, common_point, shadows)?;
|
||||
decrypt_document(key.to_vec(), encrypted_document)
|
||||
}
|
||||
|
||||
fn into_document_key(key: Bytes) -> Result<Bytes, Error> {
|
||||
// key is a previously distributely generated Public
|
||||
if key.len() != 64 {
|
||||
return Err(errors::invalid_params("key", "invalid public key length"));
|
||||
}
|
||||
|
||||
// use x coordinate of distributely generated point as encryption key
|
||||
Ok(key[..INIT_VEC_LEN].into())
|
||||
}
|
||||
|
||||
fn initialization_vector() -> [u8; INIT_VEC_LEN] {
|
||||
let mut result = [0u8; INIT_VEC_LEN];
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
rng.fill_bytes(&mut result);
|
||||
result
|
||||
}
|
||||
|
||||
fn decrypt_with_shadow_coefficients(mut decrypted_shadow: Public, mut common_shadow_point: Public, shadow_coefficients: Vec<Secret>) -> Result<Public, Error> {
|
||||
let mut shadow_coefficients_sum = shadow_coefficients[0].clone();
|
||||
for shadow_coefficient in shadow_coefficients.iter().skip(1) {
|
||||
shadow_coefficients_sum.add(shadow_coefficient)
|
||||
.map_err(errors::encryption_error)?;
|
||||
}
|
||||
|
||||
math::public_mul_secret(&mut common_shadow_point, &shadow_coefficients_sum)
|
||||
.map_err(errors::encryption_error)?;
|
||||
math::public_add(&mut decrypted_shadow, &common_shadow_point)
|
||||
.map_err(errors::encryption_error)?;
|
||||
Ok(decrypted_shadow)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use util::Bytes;
|
||||
use rustc_serialize::hex::FromHex;
|
||||
use super::{encrypt_document, decrypt_document, decrypt_document_with_shadow};
|
||||
|
||||
#[test]
|
||||
fn encrypt_and_decrypt_document() {
|
||||
let document_key: Bytes = "cac6c205eb06c8308d65156ff6c862c62b000b8ead121a4455a8ddeff7248128d895692136f240d5d1614dc7cc4147b1bd584bd617e30560bb872064d09ea325".from_hex().unwrap();
|
||||
let document: Bytes = b"Hello, world!!!"[..].into();
|
||||
|
||||
let encrypted_document = encrypt_document(document_key.clone(), document.clone()).unwrap();
|
||||
assert!(document != encrypted_document);
|
||||
|
||||
let decrypted_document = decrypt_document(document_key.clone(), encrypted_document).unwrap();
|
||||
assert_eq!(decrypted_document, document);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encrypt_and_shadow_decrypt_document() {
|
||||
let document: Bytes = "deadbeef".from_hex().unwrap();
|
||||
let encrypted_document = "2ddec1f96229efa2916988d8b2a82a47ef36f71c".from_hex().unwrap();
|
||||
let decrypted_secret = "843645726384530ffb0c52f175278143b5a93959af7864460f5a4fec9afd1450cfb8aef63dec90657f43f55b13e0a73c7524d4e9a13c051b4e5f1e53f39ecd91".parse().unwrap();
|
||||
let common_point = "07230e34ebfe41337d3ed53b186b3861751f2401ee74b988bba55694e2a6f60c757677e194be2e53c3523cc8548694e636e6acb35c4e8fdc5e29d28679b9b2f3".parse().unwrap();
|
||||
let shadows = vec!["46f542416216f66a7d7881f5a283d2a1ab7a87b381cbc5f29d0b093c7c89ee31".parse().unwrap()];
|
||||
let decrypted_document = decrypt_document_with_shadow(decrypted_secret, common_point, shadows, encrypted_document).unwrap();
|
||||
assert_eq!(decrypted_document, document);
|
||||
}
|
||||
}
|
||||
175
rpc/src/v1/helpers/subscription_manager.rs
Normal file
175
rpc/src/v1/helpers/subscription_manager.rs
Normal file
@@ -0,0 +1,175 @@
|
||||
// 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/>.
|
||||
|
||||
//! Generic poll manager for Pub-Sub.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
use util::Mutex;
|
||||
|
||||
use jsonrpc_core::futures::future::{self, Either};
|
||||
use jsonrpc_core::futures::sync::mpsc;
|
||||
use jsonrpc_core::futures::{Sink, Future, BoxFuture};
|
||||
use jsonrpc_core::{self as core, MetaIoHandler};
|
||||
|
||||
use v1::metadata::Metadata;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Subscription {
|
||||
metadata: Metadata,
|
||||
method: String,
|
||||
params: core::Params,
|
||||
sink: mpsc::Sender<Result<core::Value, core::Error>>,
|
||||
last_result: Arc<Mutex<Option<core::Output>>>,
|
||||
}
|
||||
|
||||
/// A struct managing all subscriptions.
|
||||
/// TODO [ToDr] Depending on the method decide on poll interval.
|
||||
/// For most of the methods it will be enough to poll on new block instead of time-interval.
|
||||
pub struct GenericPollManager<S: core::Middleware<Metadata>> {
|
||||
next_id: usize,
|
||||
poll_subscriptions: HashMap<usize, Subscription>,
|
||||
rpc: MetaIoHandler<Metadata, S>,
|
||||
}
|
||||
|
||||
impl<S: core::Middleware<Metadata>> GenericPollManager<S> {
|
||||
/// Creates new poll manager
|
||||
pub fn new(rpc: MetaIoHandler<Metadata, S>) -> Self {
|
||||
GenericPollManager {
|
||||
next_id: 1,
|
||||
poll_subscriptions: Default::default(),
|
||||
rpc: rpc,
|
||||
}
|
||||
}
|
||||
|
||||
/// Subscribes to update from polling given method.
|
||||
pub fn subscribe(&mut self, metadata: Metadata, method: String, params: core::Params)
|
||||
-> (usize, mpsc::Receiver<Result<core::Value, core::Error>>)
|
||||
{
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
|
||||
let (sink, stream) = mpsc::channel(1);
|
||||
|
||||
let subscription = Subscription {
|
||||
metadata: metadata,
|
||||
method: method,
|
||||
params: params,
|
||||
sink: sink,
|
||||
last_result: Default::default(),
|
||||
};
|
||||
|
||||
debug!(target: "pubsub", "Adding subscription id={:?}, {:?}", id, subscription);
|
||||
self.poll_subscriptions.insert(id, subscription);
|
||||
(id, stream)
|
||||
}
|
||||
|
||||
pub fn unsubscribe(&mut self, id: usize) -> bool {
|
||||
debug!(target: "pubsub", "Removing subscription: {:?}", id);
|
||||
self.poll_subscriptions.remove(&id).is_some()
|
||||
}
|
||||
|
||||
pub fn tick(&self) -> BoxFuture<(), ()> {
|
||||
let mut futures = Vec::new();
|
||||
// poll all subscriptions
|
||||
for (id, subscription) in self.poll_subscriptions.iter() {
|
||||
let call = core::MethodCall {
|
||||
jsonrpc: Some(core::Version::V2),
|
||||
id: core::Id::Num(*id as u64),
|
||||
method: subscription.method.clone(),
|
||||
params: Some(subscription.params.clone()),
|
||||
};
|
||||
trace!(target: "pubsub", "Polling method: {:?}", call);
|
||||
let result = self.rpc.handle_call(call.into(), subscription.metadata.clone());
|
||||
|
||||
let last_result = subscription.last_result.clone();
|
||||
let sender = subscription.sink.clone();
|
||||
|
||||
let result = result.and_then(move |response| {
|
||||
let mut last_result = last_result.lock();
|
||||
if *last_result != response && response.is_some() {
|
||||
let output = response.expect("Existence proved by the condition.");
|
||||
debug!(target: "pubsub", "Got new response, sending: {:?}", output);
|
||||
*last_result = Some(output.clone());
|
||||
|
||||
let send = match output {
|
||||
core::Output::Success(core::Success { result, .. }) => Ok(result),
|
||||
core::Output::Failure(core::Failure { error, .. }) => Err(error),
|
||||
};
|
||||
Either::A(sender.send(send).map(|_| ()).map_err(|_| ()))
|
||||
} else {
|
||||
trace!(target: "pubsub", "Response was not changed: {:?}", response);
|
||||
Either::B(future::ok(()))
|
||||
}
|
||||
});
|
||||
|
||||
futures.push(result)
|
||||
}
|
||||
|
||||
// return a future represeting all the polls
|
||||
future::join_all(futures).map(|_| ()).boxed()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::atomic::{self, AtomicBool};
|
||||
|
||||
use jsonrpc_core::{MetaIoHandler, NoopMiddleware, Value, Params};
|
||||
use jsonrpc_core::futures::{Future, Stream};
|
||||
use http::tokio_core::reactor;
|
||||
|
||||
use super::GenericPollManager;
|
||||
|
||||
fn poll_manager() -> GenericPollManager<NoopMiddleware> {
|
||||
let mut io = MetaIoHandler::default();
|
||||
let called = AtomicBool::new(false);
|
||||
io.add_method("hello", move |_| {
|
||||
if !called.load(atomic::Ordering::SeqCst) {
|
||||
called.store(true, atomic::Ordering::SeqCst);
|
||||
Ok(Value::String("hello".into()))
|
||||
} else {
|
||||
Ok(Value::String("world".into()))
|
||||
}
|
||||
});
|
||||
GenericPollManager::new(io)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_poll_subscribed_method() {
|
||||
// given
|
||||
let mut el = reactor::Core::new().unwrap();
|
||||
let mut poll_manager = poll_manager();
|
||||
let (id, rx) = poll_manager.subscribe(Default::default(), "hello".into(), Params::None);
|
||||
assert_eq!(id, 1);
|
||||
|
||||
// then
|
||||
poll_manager.tick().wait().unwrap();
|
||||
let (res, rx) = el.run(rx.into_future()).unwrap();
|
||||
assert_eq!(res, Some(Ok(Value::String("hello".into()))));
|
||||
|
||||
// retrieve second item
|
||||
poll_manager.tick().wait().unwrap();
|
||||
let (res, rx) = el.run(rx.into_future()).unwrap();
|
||||
assert_eq!(res, Some(Ok(Value::String("world".into()))));
|
||||
|
||||
// and no more notifications
|
||||
poll_manager.tick().wait().unwrap();
|
||||
// we need to unsubscribe otherwise the future will never finish.
|
||||
poll_manager.unsubscribe(1);
|
||||
assert_eq!(el.run(rx.into_future()).unwrap().0, None);
|
||||
}
|
||||
}
|
||||
@@ -103,6 +103,7 @@ pub struct EthClient<C, SN: ?Sized, S: ?Sized, M, EM> where
|
||||
external_miner: Arc<EM>,
|
||||
seed_compute: Mutex<SeedHashCompute>,
|
||||
options: EthClientOptions,
|
||||
eip86_transition: u64,
|
||||
}
|
||||
|
||||
impl<C, SN: ?Sized, S: ?Sized, M, EM> EthClient<C, SN, S, M, EM> where
|
||||
@@ -131,6 +132,7 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> EthClient<C, SN, S, M, EM> where
|
||||
external_miner: em.clone(),
|
||||
seed_compute: Mutex::new(SeedHashCompute::new()),
|
||||
options: options,
|
||||
eip86_transition: client.eip86_transition(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +168,7 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> EthClient<C, SN, S, M, EM> where
|
||||
seal_fields: view.seal().into_iter().map(Into::into).collect(),
|
||||
uncles: block.uncle_hashes().into_iter().map(Into::into).collect(),
|
||||
transactions: match include_txs {
|
||||
true => BlockTransactions::Full(block.view().localized_transactions().into_iter().map(Into::into).collect()),
|
||||
true => BlockTransactions::Full(block.view().localized_transactions().into_iter().map(|t| Transaction::from_localized(t, self.eip86_transition)).collect()),
|
||||
false => BlockTransactions::Hashes(block.transaction_hashes().into_iter().map(Into::into).collect()),
|
||||
},
|
||||
extra_data: Bytes::new(view.extra_data()),
|
||||
@@ -180,7 +182,7 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> EthClient<C, SN, S, M, EM> where
|
||||
|
||||
fn transaction(&self, id: TransactionId) -> Result<Option<Transaction>, Error> {
|
||||
match take_weak!(self.client).transaction(id) {
|
||||
Some(t) => Ok(Some(Transaction::from(t))),
|
||||
Some(t) => Ok(Some(Transaction::from_localized(t, self.eip86_transition))),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
@@ -507,7 +509,8 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
||||
let hash: H256 = hash.into();
|
||||
let miner = take_weak!(self.miner);
|
||||
let client = take_weak!(self.client);
|
||||
Ok(self.transaction(TransactionId::Hash(hash))?.or_else(|| miner.transaction(client.chain_info().best_block_number, &hash).map(Into::into)))
|
||||
let block_number = client.chain_info().best_block_number;
|
||||
Ok(self.transaction(TransactionId::Hash(hash))?.or_else(|| miner.transaction(block_number, &hash).map(|t| Transaction::from_pending(t, block_number, self.eip86_transition))))
|
||||
}
|
||||
|
||||
fn transaction_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> Result<Option<Transaction>, Error> {
|
||||
|
||||
@@ -25,7 +25,7 @@ use jsonrpc_core::Error;
|
||||
use jsonrpc_macros::Trailing;
|
||||
|
||||
use light::cache::Cache as LightDataCache;
|
||||
use light::client::Client as LightClient;
|
||||
use light::client::{Client as LightClient, LightChainClient};
|
||||
use light::{cht, TransactionQueue};
|
||||
use light::on_demand::{request, OnDemand};
|
||||
|
||||
@@ -122,6 +122,7 @@ impl EthClient {
|
||||
fn rich_block(&self, id: BlockId, include_txs: bool) -> BoxFuture<RichBlock, Error> {
|
||||
let (on_demand, sync) = (self.on_demand.clone(), self.sync.clone());
|
||||
let (client, engine) = (self.client.clone(), self.client.engine().clone());
|
||||
let eip86_transition = self.client.eip86_transition();
|
||||
|
||||
// helper for filling out a rich block once we've got a block and a score.
|
||||
let fill_rich = move |block: encoded::Block, score: Option<U256>| {
|
||||
@@ -148,8 +149,8 @@ impl EthClient {
|
||||
seal_fields: header.seal().into_iter().cloned().map(Into::into).collect(),
|
||||
uncles: block.uncle_hashes().into_iter().map(Into::into).collect(),
|
||||
transactions: match include_txs {
|
||||
true => BlockTransactions::Full(block.view().localized_transactions().into_iter().map(Into::into).collect()),
|
||||
false => BlockTransactions::Hashes(block.transaction_hashes().into_iter().map(Into::into).collect()),
|
||||
true => BlockTransactions::Full(block.view().localized_transactions().into_iter().map(|t| Transaction::from_localized(t, eip86_transition)).collect()),
|
||||
_ => BlockTransactions::Hashes(block.transaction_hashes().into_iter().map(Into::into).collect()),
|
||||
},
|
||||
extra_data: Bytes::new(header.extra_data().to_vec()),
|
||||
},
|
||||
|
||||
@@ -27,11 +27,14 @@ use ethkey::{Brain, Generator};
|
||||
use ethstore::random_phrase;
|
||||
use ethsync::LightSyncProvider;
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use crypto::DEFAULT_MAC;
|
||||
|
||||
use light::client::LightChainClient;
|
||||
|
||||
use jsonrpc_core::Error;
|
||||
use jsonrpc_macros::Trailing;
|
||||
use v1::helpers::{errors, ipfs, SigningQueue, SignerService, NetworkSettings};
|
||||
use v1::helpers::dispatch::{LightDispatcher, DEFAULT_MAC};
|
||||
use v1::helpers::dispatch::LightDispatcher;
|
||||
use v1::helpers::light_fetch::LightFetch;
|
||||
use v1::metadata::Metadata;
|
||||
use v1::traits::Parity;
|
||||
@@ -53,11 +56,13 @@ pub struct ParityClient {
|
||||
signer: Option<Arc<SignerService>>,
|
||||
dapps_interface: Option<String>,
|
||||
dapps_port: Option<u16>,
|
||||
eip86_transition: u64,
|
||||
}
|
||||
|
||||
impl ParityClient {
|
||||
/// Creates new `ParityClient`.
|
||||
pub fn new(
|
||||
client: Arc<LightChainClient>,
|
||||
light_dispatch: Arc<LightDispatcher>,
|
||||
accounts: Arc<AccountProvider>,
|
||||
logger: Arc<RotatingLogger>,
|
||||
@@ -74,6 +79,7 @@ impl ParityClient {
|
||||
signer: signer,
|
||||
dapps_interface: dapps_interface,
|
||||
dapps_port: dapps_port,
|
||||
eip86_transition: client.eip86_transition(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +251,7 @@ impl Parity for ParityClient {
|
||||
Ok(
|
||||
txq.ready_transactions(chain_info.best_block_number, chain_info.best_block_timestamp)
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.map(|tx| Transaction::from_pending(tx, chain_info.best_block_number, self.eip86_transition))
|
||||
.collect::<Vec<_>>()
|
||||
)
|
||||
}
|
||||
@@ -256,7 +262,7 @@ impl Parity for ParityClient {
|
||||
Ok(
|
||||
txq.future_transactions(chain_info.best_block_number, chain_info.best_block_timestamp)
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.map(|tx| Transaction::from_pending(tx, chain_info.best_block_number, self.eip86_transition))
|
||||
.collect::<Vec<_>>()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -23,16 +23,17 @@ mod parity;
|
||||
mod parity_accounts;
|
||||
mod parity_set;
|
||||
mod personal;
|
||||
mod pubsub;
|
||||
mod signer;
|
||||
mod signing;
|
||||
mod signing_unsafe;
|
||||
mod rpc;
|
||||
mod secretstore;
|
||||
mod traces;
|
||||
mod web3;
|
||||
|
||||
pub mod light;
|
||||
|
||||
pub use self::web3::Web3Client;
|
||||
pub use self::eth::{EthClient, EthClientOptions};
|
||||
pub use self::eth_filter::EthFilterClient;
|
||||
pub use self::net::NetClient;
|
||||
@@ -40,8 +41,11 @@ pub use self::parity::ParityClient;
|
||||
pub use self::parity_accounts::ParityAccountsClient;
|
||||
pub use self::parity_set::ParitySetClient;
|
||||
pub use self::personal::PersonalClient;
|
||||
pub use self::pubsub::PubSubClient;
|
||||
pub use self::signer::SignerClient;
|
||||
pub use self::signing::SigningQueueClient;
|
||||
pub use self::signing_unsafe::SigningUnsafeClient;
|
||||
pub use self::traces::TracesClient;
|
||||
pub use self::web3::Web3Client;
|
||||
pub use self::rpc::RpcClient;
|
||||
pub use self::secretstore::SecretStoreClient;
|
||||
|
||||
@@ -34,12 +34,12 @@ use ethcore::client::{MiningBlockChainClient};
|
||||
use ethcore::mode::Mode;
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use updater::{Service as UpdateService};
|
||||
use crypto::DEFAULT_MAC;
|
||||
|
||||
use jsonrpc_core::Error;
|
||||
use jsonrpc_macros::Trailing;
|
||||
use v1::helpers::{errors, ipfs, SigningQueue, SignerService, NetworkSettings};
|
||||
use v1::helpers::accounts::unwrap_provider;
|
||||
use v1::helpers::dispatch::DEFAULT_MAC;
|
||||
use v1::metadata::Metadata;
|
||||
use v1::traits::Parity;
|
||||
use v1::types::{
|
||||
@@ -69,6 +69,7 @@ pub struct ParityClient<C, M, S: ?Sized, U> where
|
||||
signer: Option<Arc<SignerService>>,
|
||||
dapps_interface: Option<String>,
|
||||
dapps_port: Option<u16>,
|
||||
eip86_transition: u64,
|
||||
}
|
||||
|
||||
impl<C, M, S: ?Sized, U> ParityClient<C, M, S, U> where
|
||||
@@ -103,6 +104,7 @@ impl<C, M, S: ?Sized, U> ParityClient<C, M, S, U> where
|
||||
signer: signer,
|
||||
dapps_interface: dapps_interface,
|
||||
dapps_port: dapps_port,
|
||||
eip86_transition: client.eip86_transition(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,11 +290,13 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
||||
}
|
||||
|
||||
fn pending_transactions(&self) -> Result<Vec<Transaction>, Error> {
|
||||
Ok(take_weak!(self.miner).pending_transactions().into_iter().map(Into::into).collect::<Vec<_>>())
|
||||
let block_number = take_weak!(self.client).chain_info().best_block_number;
|
||||
Ok(take_weak!(self.miner).pending_transactions().into_iter().map(|t| Transaction::from_pending(t, block_number, self.eip86_transition)).collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
fn future_transactions(&self) -> Result<Vec<Transaction>, Error> {
|
||||
Ok(take_weak!(self.miner).future_transactions().into_iter().map(Into::into).collect::<Vec<_>>())
|
||||
let block_number = take_weak!(self.client).chain_info().best_block_number;
|
||||
Ok(take_weak!(self.miner).future_transactions().into_iter().map(|t| Transaction::from_pending(t, block_number, self.eip86_transition)).collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
fn pending_transactions_stats(&self) -> Result<BTreeMap<H256, TransactionStats>, Error> {
|
||||
@@ -305,9 +309,10 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
||||
|
||||
fn local_transactions(&self) -> Result<BTreeMap<H256, LocalTransactionStatus>, Error> {
|
||||
let transactions = take_weak!(self.miner).local_transactions();
|
||||
let block_number = take_weak!(self.client).chain_info().best_block_number;
|
||||
Ok(transactions
|
||||
.into_iter()
|
||||
.map(|(hash, status)| (hash.into(), status.into()))
|
||||
.map(|(hash, status)| (hash.into(), LocalTransactionStatus::from(status, block_number, self.eip86_transition)))
|
||||
.collect()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -39,9 +39,12 @@ pub struct ParitySetClient<C, M, U, F = fetch::Client> {
|
||||
updater: Weak<U>,
|
||||
net: Weak<ManageNetwork>,
|
||||
fetch: F,
|
||||
eip86_transition: u64,
|
||||
}
|
||||
|
||||
impl<C, M, U, F> ParitySetClient<C, M, U, F> {
|
||||
impl<C, M, U, F> ParitySetClient<C, M, U, F>
|
||||
where C: MiningBlockChainClient + 'static,
|
||||
{
|
||||
/// Creates new `ParitySetClient` with given `Fetch`.
|
||||
pub fn new(client: &Arc<C>, miner: &Arc<M>, updater: &Arc<U>, net: &Arc<ManageNetwork>, fetch: F) -> Self {
|
||||
ParitySetClient {
|
||||
@@ -50,6 +53,7 @@ impl<C, M, U, F> ParitySetClient<C, M, U, F> {
|
||||
updater: Arc::downgrade(updater),
|
||||
net: Arc::downgrade(net),
|
||||
fetch: fetch,
|
||||
eip86_transition: client.eip86_transition(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,8 +179,9 @@ impl<C, M, U, F> ParitySet for ParitySetClient<C, M, U, F> where
|
||||
fn remove_transaction(&self, hash: H256) -> Result<Option<Transaction>, Error> {
|
||||
let miner = take_weak!(self.miner);
|
||||
let client = take_weak!(self.client);
|
||||
let block_number = take_weak!(self.client).chain_info().best_block_number;
|
||||
let hash = hash.into();
|
||||
|
||||
Ok(miner.remove_pending_transaction(&*client, &hash).map(Into::into))
|
||||
Ok(miner.remove_pending_transaction(&*client, &hash).map(|t| Transaction::from_pending(t, block_number, self.eip86_transition)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ impl<D: Dispatcher + 'static> Personal for PersonalClient<D> {
|
||||
Err(e) => return future::err(e).boxed(),
|
||||
};
|
||||
|
||||
dispatcher.fill_optional_fields(request.into(), default)
|
||||
dispatcher.fill_optional_fields(request.into(), default, false)
|
||||
.and_then(move |filled| {
|
||||
let condition = filled.condition.clone().map(Into::into);
|
||||
dispatcher.sign(accounts, filled, SignWith::Password(password))
|
||||
|
||||
100
rpc/src/v1/impls/pubsub.rs
Normal file
100
rpc/src/v1/impls/pubsub.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
// 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/>.
|
||||
|
||||
//! Parity-specific PUB-SUB rpc implementation.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use util::RwLock;
|
||||
|
||||
use futures::{self, BoxFuture, Future, Stream, Sink};
|
||||
use jsonrpc_core::{self as core, Error, MetaIoHandler};
|
||||
use jsonrpc_macros::pubsub::Subscriber;
|
||||
use jsonrpc_pubsub::SubscriptionId;
|
||||
use tokio_timer;
|
||||
|
||||
use parity_reactor::Remote;
|
||||
use v1::helpers::GenericPollManager;
|
||||
use v1::metadata::Metadata;
|
||||
use v1::traits::PubSub;
|
||||
|
||||
/// Parity PubSub implementation.
|
||||
pub struct PubSubClient<S: core::Middleware<Metadata>> {
|
||||
poll_manager: Arc<RwLock<GenericPollManager<S>>>,
|
||||
remote: Remote,
|
||||
}
|
||||
|
||||
impl<S: core::Middleware<Metadata>> PubSubClient<S> {
|
||||
/// Creates new `PubSubClient`.
|
||||
pub fn new(rpc: MetaIoHandler<Metadata, S>, remote: Remote) -> Self {
|
||||
let poll_manager = Arc::new(RwLock::new(GenericPollManager::new(rpc)));
|
||||
let pm2 = poll_manager.clone();
|
||||
|
||||
let timer = tokio_timer::wheel()
|
||||
.tick_duration(Duration::from_millis(500))
|
||||
.build();
|
||||
|
||||
// Start ticking
|
||||
let interval = timer.interval(Duration::from_millis(1000));
|
||||
remote.spawn(interval
|
||||
.map_err(|e| warn!("Polling timer error: {:?}", e))
|
||||
.for_each(move |_| pm2.read().tick())
|
||||
);
|
||||
|
||||
PubSubClient {
|
||||
poll_manager: poll_manager,
|
||||
remote: remote,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: core::Middleware<Metadata>> PubSub for PubSubClient<S> {
|
||||
type Metadata = Metadata;
|
||||
|
||||
fn parity_subscribe(&self, mut meta: Metadata, subscriber: Subscriber<core::Value>, method: String, params: core::Params) {
|
||||
// Make sure to get rid of PubSub session otherwise it will never be dropped.
|
||||
meta.session = None;
|
||||
|
||||
let mut poll_manager = self.poll_manager.write();
|
||||
let (id, receiver) = poll_manager.subscribe(meta, method, params);
|
||||
match subscriber.assign_id(SubscriptionId::Number(id as u64)) {
|
||||
Ok(sink) => {
|
||||
self.remote.spawn(receiver.map(|res| match res {
|
||||
Ok(val) => val,
|
||||
Err(error) => {
|
||||
warn!(target: "pubsub", "Subscription error: {:?}", error);
|
||||
core::Value::Null
|
||||
},
|
||||
}).forward(sink.sink_map_err(|e| {
|
||||
warn!("Cannot send notification: {:?}", e);
|
||||
})).map(|_| ()));
|
||||
},
|
||||
Err(_) => {
|
||||
poll_manager.unsubscribe(id);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn parity_unsubscribe(&self, id: SubscriptionId) -> BoxFuture<bool, Error> {
|
||||
let res = if let SubscriptionId::Number(id) = id {
|
||||
self.poll_manager.write().unsubscribe(id as usize)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
futures::future::ok(res).boxed()
|
||||
}
|
||||
}
|
||||
85
rpc/src/v1/impls/secretstore.rs
Normal file
85
rpc/src/v1/impls/secretstore.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
// 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/>.
|
||||
|
||||
//! SecretStore-specific rpc implementation.
|
||||
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use crypto::DEFAULT_MAC;
|
||||
use ethkey::Secret;
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
|
||||
use jsonrpc_core::Error;
|
||||
use v1::helpers::errors;
|
||||
use v1::helpers::accounts::unwrap_provider;
|
||||
use v1::helpers::secretstore::{encrypt_document, decrypt_document, decrypt_document_with_shadow};
|
||||
use v1::traits::SecretStore;
|
||||
use v1::types::{H160, H512, Bytes};
|
||||
|
||||
/// Parity implementation.
|
||||
pub struct SecretStoreClient {
|
||||
accounts: Option<Weak<AccountProvider>>,
|
||||
}
|
||||
|
||||
impl SecretStoreClient {
|
||||
/// Creates new SecretStoreClient
|
||||
pub fn new(store: &Option<Arc<AccountProvider>>) -> Self {
|
||||
SecretStoreClient {
|
||||
accounts: store.as_ref().map(Arc::downgrade),
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to get the `Arc<AccountProvider>`, errors if provider was not
|
||||
/// set, or if upgrading the weak reference failed.
|
||||
fn account_provider(&self) -> Result<Arc<AccountProvider>, Error> {
|
||||
unwrap_provider(&self.accounts)
|
||||
}
|
||||
|
||||
/// Decrypt public key using account' private key
|
||||
fn decrypt_key(&self, address: H160, password: String, key: Bytes) -> Result<Vec<u8>, Error> {
|
||||
let store = self.account_provider()?;
|
||||
store.decrypt(address.into(), Some(password), &DEFAULT_MAC, &key.0)
|
||||
.map_err(|e| errors::account("Could not decrypt key.", e))
|
||||
}
|
||||
|
||||
/// Decrypt secret key using account' private key
|
||||
fn decrypt_secret(&self, address: H160, password: String, key: Bytes) -> Result<Secret, Error> {
|
||||
self.decrypt_key(address, password, key)
|
||||
.and_then(|s| Secret::from_slice(&s).map_err(|e| errors::account("invalid secret", e)))
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretStore for SecretStoreClient {
|
||||
fn encrypt(&self, address: H160, password: String, key: Bytes, data: Bytes) -> Result<Bytes, Error> {
|
||||
encrypt_document(self.decrypt_key(address, password, key)?, data.0)
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
fn decrypt(&self, address: H160, password: String, key: Bytes, data: Bytes) -> Result<Bytes, Error> {
|
||||
decrypt_document(self.decrypt_key(address, password, key)?, data.0)
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
fn shadow_decrypt(&self, address: H160, password: String, decrypted_secret: H512, common_point: H512, decrypt_shadows: Vec<Bytes>, data: Bytes) -> Result<Bytes, Error> {
|
||||
let mut shadows = Vec::with_capacity(decrypt_shadows.len());
|
||||
for decrypt_shadow in decrypt_shadows {
|
||||
shadows.push(self.decrypt_secret(address.clone(), password.clone(), decrypt_shadow)?);
|
||||
}
|
||||
|
||||
decrypt_document_with_shadow(decrypted_secret.into(), common_point.into(), shadows, data.0)
|
||||
.map(Into::into)
|
||||
}
|
||||
}
|
||||
@@ -21,12 +21,13 @@ use std::sync::{Arc, Weak};
|
||||
use rlp::UntrustedRlp;
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use ethcore::transaction::{SignedTransaction, PendingTransaction};
|
||||
use ethkey;
|
||||
use futures::{future, BoxFuture, Future, IntoFuture};
|
||||
|
||||
use jsonrpc_core::Error;
|
||||
use v1::helpers::{errors, SignerService, SigningQueue, ConfirmationPayload};
|
||||
use v1::helpers::dispatch::{self, Dispatcher, WithToken};
|
||||
use v1::helpers::accounts::unwrap_provider;
|
||||
use v1::helpers::dispatch::{self, Dispatcher, WithToken, eth_data_hash};
|
||||
use v1::helpers::{errors, SignerService, SigningQueue, ConfirmationPayload, FilledTransactionRequest};
|
||||
use v1::traits::Signer;
|
||||
use v1::types::{TransactionModification, ConfirmationRequest, ConfirmationResponse, ConfirmationResponseWithToken, U256, Bytes};
|
||||
|
||||
@@ -104,6 +105,37 @@ impl<D: Dispatcher + 'static> SignerClient<D> {
|
||||
})
|
||||
.unwrap_or_else(|| future::err(errors::invalid_params("Unknown RequestID", id)).boxed())
|
||||
}
|
||||
|
||||
fn verify_transaction<F>(bytes: Bytes, request: FilledTransactionRequest, process: F) -> Result<ConfirmationResponse, Error> where
|
||||
F: FnOnce(PendingTransaction) -> Result<ConfirmationResponse, Error>,
|
||||
{
|
||||
let signed_transaction = UntrustedRlp::new(&bytes.0).as_val().map_err(errors::from_rlp_error)?;
|
||||
let signed_transaction = SignedTransaction::new(signed_transaction).map_err(|e| errors::invalid_params("Invalid signature.", e))?;
|
||||
let sender = signed_transaction.sender();
|
||||
|
||||
// Verification
|
||||
let sender_matches = sender == request.from;
|
||||
let data_matches = signed_transaction.data == request.data;
|
||||
let value_matches = signed_transaction.value == request.value;
|
||||
let nonce_matches = match request.nonce {
|
||||
Some(nonce) => signed_transaction.nonce == nonce,
|
||||
None => true,
|
||||
};
|
||||
|
||||
// Dispatch if everything is ok
|
||||
if sender_matches && data_matches && value_matches && nonce_matches {
|
||||
let pending_transaction = PendingTransaction::new(signed_transaction, request.condition.map(Into::into));
|
||||
process(pending_transaction)
|
||||
} else {
|
||||
let mut error = Vec::new();
|
||||
if !sender_matches { error.push("from") }
|
||||
if !data_matches { error.push("data") }
|
||||
if !value_matches { error.push("value") }
|
||||
if !nonce_matches { error.push("nonce") }
|
||||
|
||||
Err(errors::invalid_params("Sent transaction does not match the request.", error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Dispatcher + 'static> Signer for SignerClient<D> {
|
||||
@@ -149,38 +181,27 @@ impl<D: Dispatcher + 'static> Signer for SignerClient<D> {
|
||||
signer.peek(&id).map(|confirmation| {
|
||||
let result = match confirmation.payload {
|
||||
ConfirmationPayload::SendTransaction(request) => {
|
||||
let signed_transaction = UntrustedRlp::new(&bytes.0).as_val().map_err(errors::from_rlp_error)?;
|
||||
let signed_transaction = SignedTransaction::new(signed_transaction).map_err(|e| errors::invalid_params("Invalid signature.", e))?;
|
||||
let sender = signed_transaction.sender();
|
||||
|
||||
// Verification
|
||||
let sender_matches = sender == request.from;
|
||||
let data_matches = signed_transaction.data == request.data;
|
||||
let value_matches = signed_transaction.value == request.value;
|
||||
let nonce_matches = match request.nonce {
|
||||
Some(nonce) => signed_transaction.nonce == nonce,
|
||||
None => true,
|
||||
};
|
||||
|
||||
// Dispatch if everything is ok
|
||||
if sender_matches && data_matches && value_matches && nonce_matches {
|
||||
let pending_transaction = PendingTransaction::new(signed_transaction, request.condition.map(Into::into));
|
||||
Self::verify_transaction(bytes, request, |pending_transaction| {
|
||||
self.dispatcher.dispatch_transaction(pending_transaction)
|
||||
.map(Into::into)
|
||||
.map(ConfirmationResponse::SendTransaction)
|
||||
} else {
|
||||
let mut error = Vec::new();
|
||||
if !sender_matches { error.push("from") }
|
||||
if !data_matches { error.push("data") }
|
||||
if !value_matches { error.push("value") }
|
||||
if !nonce_matches { error.push("nonce") }
|
||||
|
||||
Err(errors::invalid_params("Sent transaction does not match the request.", error))
|
||||
})
|
||||
},
|
||||
ConfirmationPayload::SignTransaction(request) => {
|
||||
Self::verify_transaction(bytes, request, |pending_transaction| {
|
||||
Ok(ConfirmationResponse::SignTransaction(pending_transaction.transaction.into()))
|
||||
})
|
||||
},
|
||||
ConfirmationPayload::EthSignMessage(address, data) => {
|
||||
let expected_hash = eth_data_hash(data);
|
||||
let signature = ethkey::Signature::from_vrs(&bytes.0);
|
||||
match ethkey::verify_address(&address, &signature, &expected_hash) {
|
||||
Ok(true) => Ok(ConfirmationResponse::Signature(bytes.0.as_slice().into())),
|
||||
Ok(false) => Err(errors::invalid_params("Sender address does not match the signature.", ())),
|
||||
Err(err) => Err(errors::invalid_params("Invalid signature received.", err)),
|
||||
}
|
||||
},
|
||||
// TODO [ToDr]:
|
||||
// 1. Sign - verify signature
|
||||
// 2. Decrypt - pass through?
|
||||
// TODO [ToDr]: Decrypt - pass through?
|
||||
_ => Err(errors::unimplemented(Some("Non-transaction requests does not support RAW signing yet.".into()))),
|
||||
};
|
||||
if let Ok(ref response) = result {
|
||||
|
||||
@@ -27,7 +27,7 @@ use jsonrpc_core::Error;
|
||||
use v1::helpers::{
|
||||
errors, oneshot,
|
||||
DefaultAccount,
|
||||
SIGNING_QUEUE_LIMIT, SigningQueue, ConfirmationPromise, ConfirmationResult, SignerService
|
||||
SIGNING_QUEUE_LIMIT, SigningQueue, ConfirmationPromise, ConfirmationResult, SignerService,
|
||||
};
|
||||
use v1::helpers::dispatch::{self, Dispatcher};
|
||||
use v1::helpers::accounts::unwrap_provider;
|
||||
@@ -137,6 +137,12 @@ impl<D: Dispatcher + 'static> SigningQueueClient<D> {
|
||||
impl<D: Dispatcher + 'static> ParitySigning for SigningQueueClient<D> {
|
||||
type Metadata = Metadata;
|
||||
|
||||
fn compose_transaction(&self, meta: Metadata, transaction: RpcTransactionRequest) -> BoxFuture<RpcTransactionRequest, Error> {
|
||||
let accounts = try_bf!(self.account_provider());
|
||||
let default_account = accounts.dapp_default_address(meta.dapp_id().into()).ok().unwrap_or_default();
|
||||
self.dispatcher.fill_optional_fields(transaction.into(), default_account, true).map(Into::into).boxed()
|
||||
}
|
||||
|
||||
fn post_sign(&self, meta: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcEither<RpcU256, RpcConfirmationResponse>, Error> {
|
||||
let pending = self.pending.clone();
|
||||
self.dispatch(
|
||||
|
||||
@@ -111,6 +111,12 @@ impl<D: Dispatcher + 'static> EthSigning for SigningUnsafeClient<D>
|
||||
impl<D: Dispatcher + 'static> ParitySigning for SigningUnsafeClient<D> {
|
||||
type Metadata = Metadata;
|
||||
|
||||
fn compose_transaction(&self, meta: Metadata, transaction: RpcTransactionRequest) -> BoxFuture<RpcTransactionRequest, Error> {
|
||||
let accounts = try_bf!(self.account_provider());
|
||||
let default_account = accounts.dapp_default_address(meta.dapp_id().into()).ok().unwrap_or_default();
|
||||
self.dispatcher.fill_optional_fields(transaction.into(), default_account, true).map(Into::into).boxed()
|
||||
}
|
||||
|
||||
fn decrypt_message(&self, _: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcBytes, Error> {
|
||||
self.handle(RpcConfirmationPayload::Decrypt((address.clone(), data).into()), address.into())
|
||||
.then(|res| match res {
|
||||
|
||||
@@ -14,20 +14,26 @@
|
||||
// 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;
|
||||
use jsonrpc_pubsub::{Session, PubSubMetadata};
|
||||
|
||||
use v1::types::{DappId, Origin};
|
||||
|
||||
/// RPC methods metadata.
|
||||
#[derive(Clone, Default, Debug, PartialEq)]
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct Metadata {
|
||||
/// Request origin
|
||||
pub origin: Origin,
|
||||
/// Request PubSub Session
|
||||
pub session: Option<Arc<Session>>,
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
/// Get
|
||||
/// Returns dapp id if this request is coming from a Dapp or default `DappId` otherwise.
|
||||
pub fn dapp_id(&self) -> DappId {
|
||||
// TODO [ToDr] Extract dapp info from Ws connections.
|
||||
match self.origin {
|
||||
Origin::Dapps(ref dapp_id) => dapp_id.clone(),
|
||||
_ => DappId::default(),
|
||||
@@ -36,4 +42,8 @@ impl Metadata {
|
||||
}
|
||||
|
||||
impl jsonrpc_core::Metadata for Metadata {}
|
||||
|
||||
impl PubSubMetadata for Metadata {
|
||||
fn session(&self) -> Option<Arc<Session>> {
|
||||
self.session.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ pub mod traits;
|
||||
pub mod tests;
|
||||
pub mod types;
|
||||
|
||||
pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Net, Parity, ParityAccounts, ParitySet, ParitySigning, Signer, Personal, Traces, Rpc};
|
||||
pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Net, Parity, ParityAccounts, ParitySet, ParitySigning, PubSub, Signer, Personal, Traces, Rpc, SecretStore};
|
||||
pub use self::impls::*;
|
||||
pub use self::helpers::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, block_import, informant, dispatch};
|
||||
pub use self::metadata::Metadata;
|
||||
|
||||
@@ -83,7 +83,7 @@ fn make_spec(chain: &BlockChain) -> Spec {
|
||||
let genesis = Genesis::from(chain.genesis());
|
||||
let mut spec = ethereum::new_frontier_test();
|
||||
let state = chain.pre_state.clone().into();
|
||||
spec.set_genesis_state(state);
|
||||
spec.set_genesis_state(state).expect("unable to set genesis state");
|
||||
spec.overwrite_genesis_params(genesis);
|
||||
assert!(spec.is_state_root_valid());
|
||||
spec
|
||||
|
||||
@@ -47,5 +47,4 @@ impl SnapshotService for TestSnapshotService {
|
||||
fn abort_restore(&self) { }
|
||||
fn restore_state_chunk(&self, _hash: H256, _chunk: Bytes) { }
|
||||
fn restore_block_chunk(&self, _hash: H256, _chunk: Bytes) { }
|
||||
fn provide_canon_hashes(&self, _hashes: &[(u64, H256)]) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,9 @@ mod parity;
|
||||
mod parity_accounts;
|
||||
mod parity_set;
|
||||
mod personal;
|
||||
mod pubsub;
|
||||
mod rpc;
|
||||
mod secretstore;
|
||||
mod signer;
|
||||
mod signing;
|
||||
mod traces;
|
||||
|
||||
@@ -198,7 +198,7 @@ fn rpc_parity_set_hash_content() {
|
||||
let mut io = IoHandler::new();
|
||||
io.extend_with(parity_set_client(&client, &miner, &updater, &network).to_delegate());
|
||||
|
||||
let request = r#"{"jsonrpc": "2.0", "method": "parity_hashContent", "params":["https://ethcore.io/assets/images/ethcore-black-horizontal.png"], "id": 1}"#;
|
||||
let request = r#"{"jsonrpc": "2.0", "method": "parity_hashContent", "params":["https://parity.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()));
|
||||
@@ -227,7 +227,7 @@ fn rpc_parity_remove_transaction() {
|
||||
let hash = signed.hash();
|
||||
|
||||
let request = r#"{"jsonrpc": "2.0", "method": "parity_removeTransaction", "params":[""#.to_owned() + &format!("0x{:?}", hash) + r#""], "id": 1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"condition":null,"creates":null,"from":"0x0000000000000000000000000000000000000002","gas":"0x76c0","gasPrice":"0x9184e72a000","hash":"0x0072c69d780cdfbfc02fed5c7d184151f9a166971d045e55e27695aaa5bcb55e","input":"0x","networkId":null,"nonce":"0x1","publicKey":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","r":"0x0","raw":"0xe9018609184e72a0008276c0940000000000000000000000000000000000000005849184e72a80808080","s":"0x0","standardV":"0x4","to":"0x0000000000000000000000000000000000000005","transactionIndex":null,"v":"0x0","value":"0x9184e72a"},"id":1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"condition":null,"creates":null,"from":"0x0000000000000000000000000000000000000002","gas":"0x76c0","gasPrice":"0x9184e72a000","hash":"0xa2e0da8a8064e0b9f93e95a53c2db6d01280efb8ac72a708d25487e67dd0f8fc","input":"0x","networkId":null,"nonce":"0x1","publicKey":null,"r":"0x1","raw":"0xe9018609184e72a0008276c0940000000000000000000000000000000000000005849184e72a80800101","s":"0x1","standardV":"0x4","to":"0x0000000000000000000000000000000000000005","transactionIndex":null,"v":"0x0","value":"0x9184e72a"},"id":1}"#;
|
||||
|
||||
miner.pending_transactions.lock().insert(hash, signed);
|
||||
assert_eq!(io.handle_request_sync(&request), Some(response.to_owned()));
|
||||
|
||||
76
rpc/src/v1/tests/mocked/pubsub.rs
Normal file
76
rpc/src/v1/tests/mocked/pubsub.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
// 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 std::sync::{atomic, Arc};
|
||||
|
||||
use jsonrpc_core::{self as core, MetaIoHandler};
|
||||
use jsonrpc_core::futures::{self, Stream, Future};
|
||||
use jsonrpc_pubsub::Session;
|
||||
|
||||
use parity_reactor::EventLoop;
|
||||
use v1::{PubSub, PubSubClient, Metadata};
|
||||
|
||||
fn rpc() -> MetaIoHandler<Metadata, core::NoopMiddleware> {
|
||||
let mut io = MetaIoHandler::default();
|
||||
let called = atomic::AtomicBool::new(false);
|
||||
io.add_method("hello", move |_| {
|
||||
if !called.load(atomic::Ordering::SeqCst) {
|
||||
called.store(true, atomic::Ordering::SeqCst);
|
||||
Ok(core::Value::String("hello".into()))
|
||||
} else {
|
||||
Ok(core::Value::String("world".into()))
|
||||
}
|
||||
});
|
||||
io
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_subscribe_to_a_method() {
|
||||
// given
|
||||
let el = EventLoop::spawn();
|
||||
let rpc = rpc();
|
||||
let pubsub = PubSubClient::new(rpc, el.remote()).to_delegate();
|
||||
|
||||
let mut io = MetaIoHandler::default();
|
||||
io.extend_with(pubsub);
|
||||
|
||||
let mut metadata = Metadata::default();
|
||||
let (sender, receiver) = futures::sync::mpsc::channel(8);
|
||||
metadata.session = Some(Arc::new(Session::new(sender)));
|
||||
|
||||
// Subscribe
|
||||
let request = r#"{"jsonrpc": "2.0", "method": "parity_subscribe", "params": ["hello", []], "id": 1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":1,"id":1}"#;
|
||||
assert_eq!(io.handle_request_sync(request, metadata.clone()), Some(response.to_owned()));
|
||||
|
||||
// Check notifications
|
||||
let (res, receiver) = receiver.into_future().wait().unwrap();
|
||||
let response = r#"{"jsonrpc":"2.0","method":"parity_subscription","params":{"result":"hello","subscription":1}}"#;
|
||||
assert_eq!(res, Some(response.into()));
|
||||
|
||||
let (res, receiver) = receiver.into_future().wait().unwrap();
|
||||
let response = r#"{"jsonrpc":"2.0","method":"parity_subscription","params":{"result":"world","subscription":1}}"#;
|
||||
assert_eq!(res, Some(response.into()));
|
||||
|
||||
// And unsubscribe
|
||||
let request = r#"{"jsonrpc": "2.0", "method": "parity_unsubscribe", "params": [1], "id": 1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
|
||||
assert_eq!(io.handle_request_sync(request, metadata), Some(response.to_owned()));
|
||||
|
||||
let (res, _receiver) = receiver.into_future().wait().unwrap();
|
||||
assert_eq!(res, None);
|
||||
}
|
||||
|
||||
98
rpc/src/v1/tests/mocked/secretstore.rs
Normal file
98
rpc/src/v1/tests/mocked/secretstore.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
// 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 std::sync::Arc;
|
||||
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
|
||||
use serde_json;
|
||||
use jsonrpc_core::{IoHandler, Success};
|
||||
use v1::metadata::Metadata;
|
||||
use v1::SecretStoreClient;
|
||||
use v1::traits::secretstore::SecretStore;
|
||||
|
||||
struct Dependencies {
|
||||
pub accounts: Arc<AccountProvider>,
|
||||
}
|
||||
|
||||
impl Dependencies {
|
||||
pub fn new() -> Self {
|
||||
Dependencies {
|
||||
accounts: Arc::new(AccountProvider::transient_provider()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn client(&self) -> SecretStoreClient {
|
||||
SecretStoreClient::new(&Some(self.accounts.clone()))
|
||||
}
|
||||
|
||||
fn default_client(&self) -> IoHandler<Metadata> {
|
||||
let mut io = IoHandler::default();
|
||||
io.extend_with(self.client().to_delegate());
|
||||
io
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rpc_secretstore_encrypt_and_decrypt() {
|
||||
let deps = Dependencies::new();
|
||||
let io = deps.default_client();
|
||||
|
||||
// insert new account && unlock it
|
||||
let secret = "c1f1cfe279a5c350d13795bce162941967340c8a228e6ba175489afc564a5bef".parse().unwrap();
|
||||
deps.accounts.insert_account(secret, "password").unwrap();
|
||||
|
||||
// execute encryption request
|
||||
let encryption_request = r#"{"jsonrpc": "2.0", "method": "secretstore_encrypt", "params":[
|
||||
"0x5c2f3b4ec0c2234f8358697edc8b82a62e3ac995", "password",
|
||||
"0x0440262acc06f1e13cb11b34e792cdf698673a16bb812163cb52689ac34c94ae47047b58f58d8b596d21ac7b03a55896132d07a7dc028b2dad88f6c5a90623fa5b30ff4b1ba385a98c970432d13417cf6d7facd62f86faaef15ca993735890da0cb3e417e2740fc72de7501eef083a12dd5a9ebe513b592b1740848576a936a1eb88fc553fc624b1cae41a0a4e074e34e2aaae686709f08d70e505c5acba12ef96017e89be675a2adb07c72c4e95814fbf",
|
||||
"0xdeadbeef"
|
||||
], "id": 1}"#;
|
||||
let encryption_response = io.handle_request_sync(encryption_request).unwrap();
|
||||
let encryption_response: Success = serde_json::from_str(&encryption_response).unwrap();
|
||||
|
||||
// execute decryption request
|
||||
let decryption_request_left = r#"{"jsonrpc": "2.0", "method": "secretstore_decrypt", "params":[
|
||||
"0x5c2f3b4ec0c2234f8358697edc8b82a62e3ac995", "password",
|
||||
"0x0440262acc06f1e13cb11b34e792cdf698673a16bb812163cb52689ac34c94ae47047b58f58d8b596d21ac7b03a55896132d07a7dc028b2dad88f6c5a90623fa5b30ff4b1ba385a98c970432d13417cf6d7facd62f86faaef15ca993735890da0cb3e417e2740fc72de7501eef083a12dd5a9ebe513b592b1740848576a936a1eb88fc553fc624b1cae41a0a4e074e34e2aaae686709f08d70e505c5acba12ef96017e89be675a2adb07c72c4e95814fbf",""#;
|
||||
let decryption_request_mid = encryption_response.result.as_str().unwrap();
|
||||
let decryption_request_right = r#""
|
||||
], "id": 2}"#;
|
||||
let decryption_request = decryption_request_left.to_owned() + decryption_request_mid + decryption_request_right;
|
||||
let decryption_response = io.handle_request_sync(&decryption_request).unwrap();
|
||||
assert_eq!(decryption_response, r#"{"jsonrpc":"2.0","result":"0xdeadbeef","id":2}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rpc_secretstore_shadow_decrypt() {
|
||||
let deps = Dependencies::new();
|
||||
let io = deps.default_client();
|
||||
|
||||
// insert new account && unlock it
|
||||
let secret = "82758356bf46b42710d3946a8efa612b7bf5e125e4d49f28facf1139db4a46f4".parse().unwrap();
|
||||
deps.accounts.insert_account(secret, "password").unwrap();
|
||||
|
||||
// execute decryption request
|
||||
let decryption_request = r#"{"jsonrpc": "2.0", "method": "secretstore_shadowDecrypt", "params":[
|
||||
"0x00dfE63B22312ab4329aD0d28CaD8Af987A01932", "password",
|
||||
"0x843645726384530ffb0c52f175278143b5a93959af7864460f5a4fec9afd1450cfb8aef63dec90657f43f55b13e0a73c7524d4e9a13c051b4e5f1e53f39ecd91",
|
||||
"0x07230e34ebfe41337d3ed53b186b3861751f2401ee74b988bba55694e2a6f60c757677e194be2e53c3523cc8548694e636e6acb35c4e8fdc5e29d28679b9b2f3",
|
||||
["0x049ce50bbadb6352574f2c59742f78df83333975cbd5cbb151c6e8628749a33dc1fa93bb6dffae5994e3eb98ae859ed55ee82937538e6adb054d780d1e89ff140f121529eeadb1161562af9d3342db0008919ca280a064305e5a4e518e93279de7a9396fe5136a9658e337e8e276221248c381c5384cd1ad28e5921f46ff058d5fbcf8a388fc881d0dd29421c218d51761"],
|
||||
"0x2ddec1f96229efa2916988d8b2a82a47ef36f71c"
|
||||
], "id": 1}"#;
|
||||
let decryption_response = io.handle_request_sync(&decryption_request).unwrap();
|
||||
assert_eq!(decryption_response, r#"{"jsonrpc":"2.0","result":"0xdeadbeef","id":1}"#);
|
||||
}
|
||||
@@ -20,7 +20,7 @@ use util::{U256, Uint, Address, ToPretty};
|
||||
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use ethcore::client::TestBlockChainClient;
|
||||
use ethcore::transaction::{Transaction, Action};
|
||||
use ethcore::transaction::{Transaction, Action, SignedTransaction};
|
||||
use rlp::encode;
|
||||
|
||||
use serde_json;
|
||||
@@ -28,8 +28,9 @@ use jsonrpc_core::IoHandler;
|
||||
use v1::{SignerClient, Signer, Origin};
|
||||
use v1::metadata::Metadata;
|
||||
use v1::tests::helpers::TestMinerService;
|
||||
use v1::types::H520;
|
||||
use v1::helpers::{SigningQueue, SignerService, FilledTransactionRequest, ConfirmationPayload};
|
||||
use v1::helpers::dispatch::FullDispatcher;
|
||||
use v1::helpers::dispatch::{FullDispatcher, eth_data_hash};
|
||||
|
||||
struct SignerTester {
|
||||
signer: Arc<SignerService>,
|
||||
@@ -359,7 +360,6 @@ fn should_confirm_transaction_with_rlp() {
|
||||
"params":["0x1", "0x"#.to_owned() + &rlp.to_hex() + r#""],
|
||||
"id":1
|
||||
}"#;
|
||||
println!("{:?}", request);
|
||||
let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#;
|
||||
|
||||
// then
|
||||
@@ -415,6 +415,101 @@ fn should_return_error_when_sender_does_not_match() {
|
||||
assert_eq!(tester.signer.requests().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_confirm_sign_transaction_with_rlp() {
|
||||
// given
|
||||
let tester = signer_tester();
|
||||
let address = tester.accounts.new_account("test").unwrap();
|
||||
let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap();
|
||||
tester.signer.add_request(ConfirmationPayload::SignTransaction(FilledTransactionRequest {
|
||||
from: address,
|
||||
used_default_from: false,
|
||||
to: Some(recipient),
|
||||
gas_price: U256::from(10_000),
|
||||
gas: U256::from(10_000_000),
|
||||
value: U256::from(1),
|
||||
data: vec![],
|
||||
nonce: None,
|
||||
condition: None,
|
||||
}), Origin::Unknown).unwrap();
|
||||
assert_eq!(tester.signer.requests().len(), 1);
|
||||
|
||||
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![]
|
||||
};
|
||||
let signature = tester.accounts.sign(address, Some("test".into()), t.hash(None)).unwrap();
|
||||
let t = SignedTransaction::new(t.with_signature(signature.clone(), None)).unwrap();
|
||||
let rlp = encode(&t);
|
||||
|
||||
// when
|
||||
let request = r#"{
|
||||
"jsonrpc":"2.0",
|
||||
"method":"signer_confirmRequestRaw",
|
||||
"params":["0x1", "0x"#.to_owned() + &rlp.to_hex() + r#""],
|
||||
"id":1
|
||||
}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":{"#.to_owned() +
|
||||
r#""raw":"0x"# + &rlp.to_hex() + r#"","# +
|
||||
r#""tx":{"# +
|
||||
r#""blockHash":null,"blockNumber":null,"condition":null,"creates":null,"# +
|
||||
&format!("\"from\":\"0x{:?}\",", &address) +
|
||||
r#""gas":"0x989680","gasPrice":"0x1000","# +
|
||||
&format!("\"hash\":\"0x{:?}\",", t.hash()) +
|
||||
r#""input":"0x","# +
|
||||
&format!("\"networkId\":{},", t.network_id().map_or("null".to_owned(), |n| format!("{}", n))) +
|
||||
r#""nonce":"0x0","# +
|
||||
&format!("\"publicKey\":\"0x{:?}\",", t.public_key().unwrap()) +
|
||||
&format!("\"r\":\"0x{}\",", U256::from(signature.r()).to_hex()) +
|
||||
&format!("\"raw\":\"0x{}\",", rlp.to_hex()) +
|
||||
&format!("\"s\":\"0x{}\",", U256::from(signature.s()).to_hex()) +
|
||||
&format!("\"standardV\":\"0x{}\",", U256::from(t.standard_v()).to_hex()) +
|
||||
r#""to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","transactionIndex":null,"# +
|
||||
&format!("\"v\":\"0x{}\",", U256::from(t.original_v()).to_hex()) +
|
||||
r#""value":"0x1""# +
|
||||
r#"}},"id":1}"#;
|
||||
|
||||
// then
|
||||
assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned()));
|
||||
assert_eq!(tester.signer.requests().len(), 0);
|
||||
assert_eq!(tester.miner.imported_transactions.lock().len(), 0);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn should_confirm_data_sign_with_signature() {
|
||||
// given
|
||||
let tester = signer_tester();
|
||||
let address = tester.accounts.new_account("test").unwrap();
|
||||
tester.signer.add_request(ConfirmationPayload::EthSignMessage(
|
||||
address,
|
||||
vec![1, 2, 3, 4].into(),
|
||||
), Origin::Unknown).unwrap();
|
||||
assert_eq!(tester.signer.requests().len(), 1);
|
||||
|
||||
let data_hash = eth_data_hash(vec![1, 2, 3, 4].into());
|
||||
let signature = H520(tester.accounts.sign(address, Some("test".into()), data_hash).unwrap().into_vrs());
|
||||
let signature = format!("0x{:?}", signature);
|
||||
|
||||
// when
|
||||
let request = r#"{
|
||||
"jsonrpc":"2.0",
|
||||
"method":"signer_confirmRequestRaw",
|
||||
"params":["0x1", ""#.to_owned() + &signature + r#""],
|
||||
"id":1
|
||||
}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + &signature + r#"","id":1}"#;
|
||||
|
||||
// then
|
||||
assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned()));
|
||||
assert_eq!(tester.signer.requests().len(), 0);
|
||||
assert_eq!(tester.miner.imported_transactions.lock().len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_generate_new_token() {
|
||||
// given
|
||||
|
||||
@@ -304,7 +304,7 @@ fn should_add_sign_transaction_to_the_queue() {
|
||||
r#""input":"0x","# +
|
||||
&format!("\"networkId\":{},", t.network_id().map_or("null".to_owned(), |n| format!("{}", n))) +
|
||||
r#""nonce":"0x1","# +
|
||||
&format!("\"publicKey\":\"0x{:?}\",", t.public_key()) +
|
||||
&format!("\"publicKey\":\"0x{:?}\",", t.public_key().unwrap()) +
|
||||
&format!("\"r\":\"0x{}\",", U256::from(signature.r()).to_hex()) +
|
||||
&format!("\"raw\":\"0x{}\",", rlp.to_hex()) +
|
||||
&format!("\"s\":\"0x{}\",", U256::from(signature.s()).to_hex()) +
|
||||
@@ -439,3 +439,29 @@ fn should_add_decryption_to_the_queue() {
|
||||
let res = promise.wait().unwrap();
|
||||
assert_eq!(res, Some(response.to_owned()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_compose_transaction() {
|
||||
// given
|
||||
let tester = eth_signing();
|
||||
let acc = Random.generate().unwrap();
|
||||
assert_eq!(tester.signer.requests().len(), 0);
|
||||
let from = format!("{:?}", acc.address());
|
||||
|
||||
// when
|
||||
let request = r#"{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "parity_composeTransaction",
|
||||
"params": [{"from":"0x"#.to_owned() + &from + r#"","value":"0x5"}],
|
||||
"id": 1
|
||||
}"#;
|
||||
|
||||
let response = r#"{"jsonrpc":"2.0","result":{"condition":null,"data":"0x","from":"0x"#.to_owned()
|
||||
+ &from
|
||||
+ r#"","gas":"0x5208","gasPrice":"0x4a817c800","nonce":"0x0","to":null,"value":"0x5"},"id":1}"#;
|
||||
|
||||
|
||||
// then
|
||||
let res = tester.io.handle_request(&request).wait().unwrap();
|
||||
assert_eq!(res, Some(response.to_owned()));
|
||||
}
|
||||
|
||||
@@ -25,9 +25,11 @@ pub mod parity_accounts;
|
||||
pub mod parity_set;
|
||||
pub mod parity_signing;
|
||||
pub mod personal;
|
||||
pub mod pubsub;
|
||||
pub mod signer;
|
||||
pub mod traces;
|
||||
pub mod rpc;
|
||||
pub mod secretstore;
|
||||
|
||||
pub use self::web3::Web3;
|
||||
pub use self::eth::{Eth, EthFilter};
|
||||
@@ -38,7 +40,8 @@ pub use self::parity_accounts::ParityAccounts;
|
||||
pub use self::parity_set::ParitySet;
|
||||
pub use self::parity_signing::ParitySigning;
|
||||
pub use self::personal::Personal;
|
||||
pub use self::pubsub::PubSub;
|
||||
pub use self::signer::Signer;
|
||||
pub use self::traces::Traces;
|
||||
pub use self::rpc::Rpc;
|
||||
|
||||
pub use self::secretstore::SecretStore;
|
||||
|
||||
@@ -25,6 +25,11 @@ build_rpc_trait! {
|
||||
pub trait ParitySigning {
|
||||
type Metadata;
|
||||
|
||||
/// Given partial transaction request produces transaction with all fields filled in.
|
||||
/// Such transaction can be then signed externally.
|
||||
#[rpc(meta, name = "parity_composeTransaction")]
|
||||
fn compose_transaction(&self, Self::Metadata, TransactionRequest) -> BoxFuture<TransactionRequest, Error>;
|
||||
|
||||
/// Posts sign request asynchronously.
|
||||
/// Will return a confirmation ID for later use with check_transaction.
|
||||
#[rpc(meta, name = "parity_postSign")]
|
||||
|
||||
@@ -43,7 +43,7 @@ build_rpc_trait! {
|
||||
#[rpc(meta, name = "personal_sendTransaction")]
|
||||
fn send_transaction(&self, Self::Metadata, TransactionRequest, String) -> BoxFuture<H256, Error>;
|
||||
|
||||
/// Deprecated alias for `personal_sendTransaction`.
|
||||
/// @deprecated alias for `personal_sendTransaction`.
|
||||
#[rpc(meta, name = "personal_signAndSendTransaction")]
|
||||
fn sign_and_send_transaction(&self, Self::Metadata, TransactionRequest, String) -> BoxFuture<H256, Error>;
|
||||
|
||||
|
||||
39
rpc/src/v1/traits/pubsub.rs
Normal file
39
rpc/src/v1/traits/pubsub.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
// 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/>.
|
||||
|
||||
//! Parity-specific PUB-SUB rpc interface.
|
||||
|
||||
use jsonrpc_core::{Error, Value, Params};
|
||||
use jsonrpc_pubsub::SubscriptionId;
|
||||
use jsonrpc_macros::pubsub::Subscriber;
|
||||
use futures::BoxFuture;
|
||||
|
||||
build_rpc_trait! {
|
||||
/// Parity-specific PUB-SUB rpc interface.
|
||||
pub trait PubSub {
|
||||
type Metadata;
|
||||
|
||||
#[pubsub(name = "parity_subscription")] {
|
||||
/// Subscribe to changes of any RPC method in Parity.
|
||||
#[rpc(name = "parity_subscribe")]
|
||||
fn parity_subscribe(&self, Self::Metadata, Subscriber<Value>, String, Params);
|
||||
|
||||
/// Unsubscribe from existing Parity subscription.
|
||||
#[rpc(name = "parity_unsubscribe")]
|
||||
fn parity_unsubscribe(&self, SubscriptionId) -> BoxFuture<bool, Error>;
|
||||
}
|
||||
}
|
||||
}
|
||||
41
rpc/src/v1/traits/secretstore.rs
Normal file
41
rpc/src/v1/traits/secretstore.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
// 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/>.
|
||||
|
||||
//! SecretStore-specific rpc interface.
|
||||
|
||||
use jsonrpc_core::Error;
|
||||
|
||||
use v1::types::{H160, H512, Bytes};
|
||||
|
||||
build_rpc_trait! {
|
||||
/// Parity-specific rpc interface.
|
||||
pub trait SecretStore {
|
||||
/// Encrypt data with key, received from secret store.
|
||||
/// Arguments: `account`, `password`, `key`, `data`.
|
||||
#[rpc(name = "secretstore_encrypt")]
|
||||
fn encrypt(&self, H160, String, Bytes, Bytes) -> Result<Bytes, Error>;
|
||||
|
||||
/// Decrypt data with key, received from secret store.
|
||||
/// Arguments: `account`, `password`, `key`, `data`.
|
||||
#[rpc(name = "secretstore_decrypt")]
|
||||
fn decrypt(&self, H160, String, Bytes, Bytes) -> Result<Bytes, Error>;
|
||||
|
||||
/// Decrypt data with shadow key, received from secret store.
|
||||
/// Arguments: `account`, `password`, `decrypted_secret`, `common_point`, `decrypt_shadows`, `data`.
|
||||
#[rpc(name = "secretstore_shadowDecrypt")]
|
||||
fn shadow_decrypt(&self, H160, String, H512, H512, Vec<Bytes>, Bytes) -> Result<Bytes, Error>;
|
||||
}
|
||||
}
|
||||
@@ -129,7 +129,7 @@ pub enum ConfirmationResponse {
|
||||
SendTransaction(H256),
|
||||
/// Transaction RLP
|
||||
SignTransaction(RichRawTransaction),
|
||||
/// Signature
|
||||
/// Signature (encoded as VRS)
|
||||
Signature(H520),
|
||||
/// Decrypted data
|
||||
Decrypt(Bytes),
|
||||
|
||||
@@ -33,6 +33,9 @@ pub enum Origin {
|
||||
/// IPC server (includes session hash)
|
||||
#[serde(rename="ipc")]
|
||||
Ipc(H256),
|
||||
/// WS server (includes session hash)
|
||||
#[serde(rename="ws")]
|
||||
Ws(H256),
|
||||
/// Signer (includes session hash)
|
||||
#[serde(rename="signer")]
|
||||
Signer(H256),
|
||||
@@ -53,6 +56,7 @@ impl fmt::Display for Origin {
|
||||
Origin::Rpc(ref origin) => write!(f, "RPC (service: {})", origin),
|
||||
Origin::Dapps(ref origin) => write!(f, "Dapp {}", origin),
|
||||
Origin::Ipc(ref session) => write!(f, "IPC (session: {})", session),
|
||||
Origin::Ws(ref session) => write!(f, "WebSocket (session: {})", session),
|
||||
Origin::Signer(ref session) => write!(f, "UI (session: {})", session),
|
||||
Origin::Unknown => write!(f, "unknown origin"),
|
||||
}
|
||||
@@ -112,6 +116,7 @@ mod tests {
|
||||
let o3 = Origin::Ipc(5.into());
|
||||
let o4 = Origin::Signer(10.into());
|
||||
let o5 = Origin::Unknown;
|
||||
let o6 = Origin::Ws(5.into());
|
||||
|
||||
// when
|
||||
let res1 = serde_json::to_string(&o1).unwrap();
|
||||
@@ -119,6 +124,7 @@ mod tests {
|
||||
let res3 = serde_json::to_string(&o3).unwrap();
|
||||
let res4 = serde_json::to_string(&o4).unwrap();
|
||||
let res5 = serde_json::to_string(&o5).unwrap();
|
||||
let res6 = serde_json::to_string(&o6).unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(res1, r#"{"rpc":"test service"}"#);
|
||||
@@ -126,6 +132,7 @@ mod tests {
|
||||
assert_eq!(res3, r#"{"ipc":"0x0000000000000000000000000000000000000000000000000000000000000005"}"#);
|
||||
assert_eq!(res4, r#"{"signer":"0x000000000000000000000000000000000000000000000000000000000000000a"}"#);
|
||||
assert_eq!(res5, r#""unknown""#);
|
||||
assert_eq!(res6, r#"{"ws":"0x0000000000000000000000000000000000000000000000000000000000000005"}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
|
||||
use serde::{Serialize, Serializer};
|
||||
use serde::ser::SerializeStruct;
|
||||
use util::Hashable;
|
||||
use ethcore::miner;
|
||||
use ethcore::contract_address;
|
||||
use ethcore::{contract_address, CreateContractAddress};
|
||||
use ethcore::transaction::{LocalizedTransaction, Action, PendingTransaction, SignedTransaction};
|
||||
use v1::helpers::errors;
|
||||
use v1::types::{Bytes, H160, H256, U256, H512, TransactionCondition};
|
||||
@@ -158,9 +159,11 @@ pub struct RichRawTransaction {
|
||||
pub transaction: Transaction
|
||||
}
|
||||
|
||||
|
||||
impl From<SignedTransaction> for RichRawTransaction {
|
||||
fn from(t: SignedTransaction) -> Self {
|
||||
let tx: Transaction = t.into();
|
||||
// TODO: change transition to 0 when EIP-86 is commonly used.
|
||||
let tx: Transaction = Transaction::from_signed(t, 0, u64::max_value());
|
||||
RichRawTransaction {
|
||||
raw: tx.raw.clone(),
|
||||
transaction: tx,
|
||||
@@ -168,9 +171,11 @@ impl From<SignedTransaction> for RichRawTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LocalizedTransaction> for Transaction {
|
||||
fn from(mut t: LocalizedTransaction) -> Transaction {
|
||||
impl Transaction {
|
||||
/// Convert `LocalizedTransaction` into RPC Transaction.
|
||||
pub fn from_localized(mut t: LocalizedTransaction, eip86_transition: u64) -> Transaction {
|
||||
let signature = t.signature();
|
||||
let scheme = if t.block_number >= eip86_transition { CreateContractAddress::FromCodeHash } else { CreateContractAddress::FromSenderAndNonce };
|
||||
Transaction {
|
||||
hash: t.hash().into(),
|
||||
nonce: t.nonce.into(),
|
||||
@@ -187,7 +192,7 @@ impl From<LocalizedTransaction> for Transaction {
|
||||
gas: t.gas.into(),
|
||||
input: Bytes::new(t.data.clone()),
|
||||
creates: match t.action {
|
||||
Action::Create => Some(contract_address(&t.sender(), &t.nonce).into()),
|
||||
Action::Create => Some(contract_address(scheme, &t.sender(), &t.nonce, &t.data.sha3()).into()),
|
||||
Action::Call(_) => None,
|
||||
},
|
||||
raw: ::rlp::encode(&t.signed).to_vec().into(),
|
||||
@@ -200,11 +205,11 @@ impl From<LocalizedTransaction> for Transaction {
|
||||
condition: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SignedTransaction> for Transaction {
|
||||
fn from(t: SignedTransaction) -> Transaction {
|
||||
/// Convert `SignedTransaction` into RPC Transaction.
|
||||
pub fn from_signed(t: SignedTransaction, block_number: u64, eip86_transition: u64) -> Transaction {
|
||||
let signature = t.signature();
|
||||
let scheme = if block_number >= eip86_transition { CreateContractAddress::FromCodeHash } else { CreateContractAddress::FromSenderAndNonce };
|
||||
Transaction {
|
||||
hash: t.hash().into(),
|
||||
nonce: t.nonce.into(),
|
||||
@@ -221,11 +226,11 @@ impl From<SignedTransaction> for Transaction {
|
||||
gas: t.gas.into(),
|
||||
input: Bytes::new(t.data.clone()),
|
||||
creates: match t.action {
|
||||
Action::Create => Some(contract_address(&t.sender(), &t.nonce).into()),
|
||||
Action::Create => Some(contract_address(scheme, &t.sender(), &t.nonce, &t.data.sha3()).into()),
|
||||
Action::Call(_) => None,
|
||||
},
|
||||
raw: ::rlp::encode(&t).to_vec().into(),
|
||||
public_key: Some(t.public_key().into()),
|
||||
public_key: t.public_key().map(Into::into),
|
||||
network_id: t.network_id(),
|
||||
standard_v: t.standard_v().into(),
|
||||
v: t.original_v().into(),
|
||||
@@ -234,28 +239,28 @@ impl From<SignedTransaction> for Transaction {
|
||||
condition: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PendingTransaction> for Transaction {
|
||||
fn from(t: PendingTransaction) -> Transaction {
|
||||
let mut r = Transaction::from(t.transaction);
|
||||
/// Convert `PendingTransaction` into RPC Transaction.
|
||||
pub fn from_pending(t: PendingTransaction, block_number: u64, eip86_transition: u64) -> Transaction {
|
||||
let mut r = Transaction::from_signed(t.transaction, block_number, eip86_transition);
|
||||
r.condition = t.condition.map(|b| b.into());
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
impl From<miner::LocalTransactionStatus> for LocalTransactionStatus {
|
||||
fn from(s: miner::LocalTransactionStatus) -> Self {
|
||||
impl LocalTransactionStatus {
|
||||
/// Convert `LocalTransactionStatus` into RPC `LocalTransactionStatus`.
|
||||
pub fn from(s: miner::LocalTransactionStatus, block_number: u64, eip86_transition: u64) -> Self {
|
||||
use ethcore::miner::LocalTransactionStatus::*;
|
||||
match s {
|
||||
Pending => LocalTransactionStatus::Pending,
|
||||
Future => LocalTransactionStatus::Future,
|
||||
Mined(tx) => LocalTransactionStatus::Mined(tx.into()),
|
||||
Dropped(tx) => LocalTransactionStatus::Dropped(tx.into()),
|
||||
Rejected(tx, err) => LocalTransactionStatus::Rejected(tx.into(), errors::transaction_message(err)),
|
||||
Replaced(tx, gas_price, hash) => LocalTransactionStatus::Replaced(tx.into(), gas_price.into(), hash.into()),
|
||||
Invalid(tx) => LocalTransactionStatus::Invalid(tx.into()),
|
||||
Canceled(tx) => LocalTransactionStatus::Canceled(tx.into()),
|
||||
Mined(tx) => LocalTransactionStatus::Mined(Transaction::from_signed(tx, block_number, eip86_transition)),
|
||||
Dropped(tx) => LocalTransactionStatus::Dropped(Transaction::from_signed(tx, block_number, eip86_transition)),
|
||||
Rejected(tx, err) => LocalTransactionStatus::Rejected(Transaction::from_signed(tx, block_number, eip86_transition), errors::transaction_message(err)),
|
||||
Replaced(tx, gas_price, hash) => LocalTransactionStatus::Replaced(Transaction::from_signed(tx, block_number, eip86_transition), gas_price.into(), hash.into()),
|
||||
Invalid(tx) => LocalTransactionStatus::Invalid(Transaction::from_signed(tx, block_number, eip86_transition)),
|
||||
Canceled(tx) => LocalTransactionStatus::Canceled(Transaction::from_pending(tx, block_number, eip86_transition)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user