Refactoring Signer to auto_args + eth_signTransaction (#3261)

* Sign transaction initial

* Refactoring signer to auto_args
This commit is contained in:
Tomasz Drwięga 2016-11-09 13:13:35 +01:00 committed by Gav Wood
parent 3c6f148a16
commit b33b237f76
17 changed files with 560 additions and 283 deletions

View File

@ -14,84 +14,153 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use rlp;
use util::{Address, H256, U256, Uint, Bytes}; use util::{Address, H256, U256, Uint, Bytes};
use util::bytes::ToPretty; use util::bytes::ToPretty;
use ethkey::Signature; use ethkey::Signature;
use ethcore::miner::MinerService; use ethcore::miner::MinerService;
use ethcore::client::MiningBlockChainClient; use ethcore::client::MiningBlockChainClient;
use ethcore::transaction::{Action, SignedTransaction, Transaction}; use ethcore::transaction::{Action, SignedTransaction, Transaction};
use ethcore::account_provider::AccountProvider; use ethcore::account_provider::AccountProvider;
use jsonrpc_core::{Error, Value, to_value};
use v1::helpers::TransactionRequest; use jsonrpc_core::Error;
use v1::types::{H256 as RpcH256, H520 as RpcH520, Bytes as RpcBytes}; use v1::helpers::{errors, TransactionRequest, FilledTransactionRequest, ConfirmationPayload};
use v1::helpers::errors; use v1::types::{
H256 as RpcH256, H520 as RpcH520, Bytes as RpcBytes,
ConfirmationPayload as RpcConfirmationPayload,
ConfirmationResponse,
SignRequest as RpcSignRequest,
DecryptRequest as RpcDecryptRequest,
};
pub const DEFAULT_MAC: [u8; 2] = [0, 0]; pub const DEFAULT_MAC: [u8; 2] = [0, 0];
pub fn dispatch_transaction<C, M>(client: &C, miner: &M, signed_transaction: SignedTransaction) -> Result<RpcH256, Error> pub fn execute<C, M>(client: &C, miner: &M, accounts: &AccountProvider, payload: ConfirmationPayload, pass: Option<String>) -> Result<ConfirmationResponse, Error>
where C: MiningBlockChainClient, M: MinerService { where C: MiningBlockChainClient, M: MinerService
let hash = RpcH256::from(signed_transaction.hash()); {
match payload {
miner.import_own_transaction(client, signed_transaction) ConfirmationPayload::SendTransaction(request) => {
.map_err(errors::from_transaction_error) sign_and_dispatch(client, miner, accounts, request, pass)
.map(|_| hash) .map(RpcH256::from)
.map(ConfirmationResponse::SendTransaction)
},
ConfirmationPayload::SignTransaction(request) => {
sign_no_dispatch(client, miner, accounts, request, pass)
.map(|tx| rlp::encode(&tx).to_vec())
.map(RpcBytes)
.map(ConfirmationResponse::SignTransaction)
},
ConfirmationPayload::Signature(address, hash) => {
signature(accounts, address, hash, pass)
.map(RpcH520::from)
.map(ConfirmationResponse::Signature)
},
ConfirmationPayload::Decrypt(address, data) => {
decrypt(accounts, address, data, pass)
.map(RpcBytes)
.map(ConfirmationResponse::Decrypt)
},
}
} }
fn signature(accounts: &AccountProvider, address: Address, password: Option<String>, hash: H256) -> Result<Signature, Error> { fn signature(accounts: &AccountProvider, address: Address, hash: H256, password: Option<String>) -> Result<Signature, Error> {
accounts.sign(address, password.clone(), hash).map_err(|e| match password { accounts.sign(address, password.clone(), hash).map_err(|e| match password {
Some(_) => errors::from_password_error(e), Some(_) => errors::from_password_error(e),
None => errors::from_signing_error(e), None => errors::from_signing_error(e),
}) })
} }
pub fn sign(accounts: &AccountProvider, address: Address, password: Option<String>, hash: H256) -> Result<Value, Error> { fn decrypt(accounts: &AccountProvider, address: Address, msg: Bytes, password: Option<String>) -> Result<Bytes, Error> {
signature(accounts, address, password, hash)
.map(RpcH520::from)
.map(to_value)
}
pub fn decrypt(accounts: &AccountProvider, address: Address, password: Option<String>, msg: Bytes) -> Result<Value, Error> {
accounts.decrypt(address, password.clone(), &DEFAULT_MAC, &msg) accounts.decrypt(address, password.clone(), &DEFAULT_MAC, &msg)
.map_err(|e| match password { .map_err(|e| match password {
Some(_) => errors::from_password_error(e), Some(_) => errors::from_password_error(e),
None => errors::from_signing_error(e), None => errors::from_signing_error(e),
}) })
.map(RpcBytes::from)
.map(to_value)
} }
pub fn sign_and_dispatch<C, M>(client: &C, miner: &M, accounts: &AccountProvider, request: TransactionRequest, password: Option<String>) -> Result<RpcH256, Error> pub fn dispatch_transaction<C, M>(client: &C, miner: &M, signed_transaction: SignedTransaction) -> Result<H256, Error>
where C: MiningBlockChainClient, M: MinerService {
let hash = signed_transaction.hash();
miner.import_own_transaction(client, signed_transaction)
.map_err(errors::from_transaction_error)
.map(|_| hash)
}
pub fn sign_no_dispatch<C, M>(client: &C, miner: &M, accounts: &AccountProvider, filled: FilledTransactionRequest, password: Option<String>) -> Result<SignedTransaction, Error>
where C: MiningBlockChainClient, M: MinerService { where C: MiningBlockChainClient, M: MinerService {
let network_id = client.signing_network_id(); let network_id = client.signing_network_id();
let address = request.from; let address = filled.from;
let signed_transaction = { let signed_transaction = {
let t = prepare_transaction(client, miner, request); 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)),
action: filled.to.map_or(Action::Create, Action::Call),
gas: filled.gas,
gas_price: filled.gas_price,
value: filled.value,
data: filled.data,
};
let hash = t.hash(network_id); let hash = t.hash(network_id);
let signature = try!(signature(accounts, address, password, hash)); let signature = try!(signature(accounts, address, hash, password));
t.with_signature(signature, network_id) t.with_signature(signature, network_id)
}; };
Ok(signed_transaction)
}
trace!(target: "miner", "send_transaction: dispatching tx: {} for network ID {:?}", ::rlp::encode(&signed_transaction).to_vec().pretty(), network_id); pub fn sign_and_dispatch<C, M>(client: &C, miner: &M, accounts: &AccountProvider, filled: FilledTransactionRequest, password: Option<String>) -> Result<H256, Error>
where C: MiningBlockChainClient, M: MinerService
{
let network_id = client.signing_network_id();
let signed_transaction = try!(sign_no_dispatch(client, miner, accounts, filled, password));
trace!(target: "miner", "send_transaction: dispatching tx: {} for network ID {:?}", rlp::encode(&signed_transaction).to_vec().pretty(), network_id);
dispatch_transaction(&*client, &*miner, signed_transaction) dispatch_transaction(&*client, &*miner, signed_transaction)
} }
fn prepare_transaction<C, M>(client: &C, miner: &M, request: TransactionRequest) -> Transaction where C: MiningBlockChainClient, M: MinerService { pub fn fill_optional_fields<C, M>(request: TransactionRequest, client: &C, miner: &M) -> FilledTransactionRequest
Transaction { where C: MiningBlockChainClient, M: MinerService
nonce: request.nonce {
.or_else(|| miner FilledTransactionRequest {
.last_nonce(&request.from) from: request.from,
.map(|nonce| nonce + U256::one())) to: request.to,
.unwrap_or_else(|| client.latest_nonce(&request.from)), nonce: request.nonce,
action: request.to.map_or(Action::Create, Action::Call),
gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()),
gas_price: request.gas_price.unwrap_or_else(|| default_gas_price(client, miner)), gas_price: request.gas_price.unwrap_or_else(|| default_gas_price(client, miner)),
value: request.value.unwrap_or_else(U256::zero), gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()),
data: request.data.map_or_else(Vec::new, |b| b.to_vec()), value: request.value.unwrap_or_else(|| 0.into()),
data: request.data.unwrap_or_else(Vec::new),
} }
} }
pub fn default_gas_price<C, M>(client: &C, miner: &M) -> U256 where C: MiningBlockChainClient, M: MinerService { pub fn default_gas_price<C, M>(client: &C, miner: &M) -> U256
where C: MiningBlockChainClient, M: MinerService
{
client.gas_price_median(100).unwrap_or_else(|| miner.sensible_gas_price()) client.gas_price_median(100).unwrap_or_else(|| miner.sensible_gas_price())
} }
pub fn from_rpc<C, M>(payload: RpcConfirmationPayload, client: &C, miner: &M) -> ConfirmationPayload
where C: MiningBlockChainClient, M: MinerService {
match payload {
RpcConfirmationPayload::SendTransaction(request) => {
ConfirmationPayload::SendTransaction(fill_optional_fields(request.into(), client, miner))
},
RpcConfirmationPayload::SignTransaction(request) => {
ConfirmationPayload::SignTransaction(fill_optional_fields(request.into(), client, miner))
},
RpcConfirmationPayload::Decrypt(RpcDecryptRequest { address, msg }) => {
ConfirmationPayload::Decrypt(address.into(), msg.into())
},
RpcConfirmationPayload::Signature(RpcSignRequest { address, hash }) => {
ConfirmationPayload::Signature(address.into(), hash.into())
},
}
}

View File

@ -100,9 +100,22 @@ pub struct ConfirmationRequest {
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum ConfirmationPayload { pub enum ConfirmationPayload {
/// Transaction /// Transaction
Transaction(FilledTransactionRequest), SendTransaction(FilledTransactionRequest),
/// Sign Transaction
SignTransaction(FilledTransactionRequest),
/// Sign request /// Sign request
Sign(Address, H256), Signature(Address, H256),
/// Decrypt request /// Decrypt request
Decrypt(Address, Bytes), Decrypt(Address, Bytes),
} }
impl ConfirmationPayload {
pub fn sender(&self) -> Address {
match *self {
ConfirmationPayload::SendTransaction(ref request) => request.from,
ConfirmationPayload::SignTransaction(ref request) => request.from,
ConfirmationPayload::Signature(ref address, _) => *address,
ConfirmationPayload::Decrypt(ref address, _) => *address,
}
}
}

View File

@ -21,9 +21,10 @@ use std::collections::BTreeMap;
use jsonrpc_core; use jsonrpc_core;
use util::{Mutex, RwLock, U256}; use util::{Mutex, RwLock, U256};
use v1::helpers::{ConfirmationRequest, ConfirmationPayload}; use v1::helpers::{ConfirmationRequest, ConfirmationPayload};
use v1::types::ConfirmationResponse;
/// Result that can be returned from JSON RPC. /// Result that can be returned from JSON RPC.
pub type RpcResult = Result<jsonrpc_core::Value, jsonrpc_core::Error>; pub type RpcResult = Result<ConfirmationResponse, jsonrpc_core::Error>;
/// Possible events happening in the queue that can be listened to. /// Possible events happening in the queue that can be listened to.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -314,13 +315,12 @@ mod test {
use std::time::Duration; use std::time::Duration;
use std::thread; use std::thread;
use std::sync::{mpsc, Arc}; use std::sync::{mpsc, Arc};
use util::{Address, U256, H256, Mutex}; use util::{Address, U256, Mutex};
use v1::helpers::{SigningQueue, ConfirmationsQueue, QueueEvent, FilledTransactionRequest, ConfirmationPayload}; use v1::helpers::{SigningQueue, ConfirmationsQueue, QueueEvent, FilledTransactionRequest, ConfirmationPayload};
use v1::types::H256 as NH256; use v1::types::ConfirmationResponse;
use jsonrpc_core::to_value;
fn request() -> ConfirmationPayload { fn request() -> ConfirmationPayload {
ConfirmationPayload::Transaction(FilledTransactionRequest { ConfirmationPayload::SendTransaction(FilledTransactionRequest {
from: Address::from(1), from: Address::from(1),
to: Some(Address::from(2)), to: Some(Address::from(2)),
gas_price: 0.into(), gas_price: 0.into(),
@ -353,10 +353,10 @@ mod test {
// Just wait for the other thread to start // Just wait for the other thread to start
thread::sleep(Duration::from_millis(100)); thread::sleep(Duration::from_millis(100));
} }
queue.request_confirmed(id, Ok(to_value(&NH256::from(H256::from(1))))); queue.request_confirmed(id, Ok(ConfirmationResponse::SendTransaction(1.into())));
// then // then
assert_eq!(handle.join().expect("Thread should finish nicely"), Ok(to_value(&NH256::from(H256::from(1))))); assert_eq!(handle.join().expect("Thread should finish nicely"), Ok(ConfirmationResponse::SendTransaction(1.into())));
} }
#[test] #[test]

View File

@ -49,7 +49,7 @@ use v1::types::{
H64 as RpcH64, H256 as RpcH256, H160 as RpcH160, U256 as RpcU256, H64 as RpcH64, H256 as RpcH256, H160 as RpcH160, U256 as RpcU256,
}; };
use v1::helpers::{CallRequest as CRequest, errors, limit_logs}; use v1::helpers::{CallRequest as CRequest, errors, limit_logs};
use v1::helpers::dispatch::{default_gas_price, dispatch_transaction}; use v1::helpers::dispatch::{dispatch_transaction, default_gas_price};
use v1::helpers::block_import::is_major_importing; use v1::helpers::block_import::is_major_importing;
use v1::helpers::auto_args::Trailing; use v1::helpers::auto_args::Trailing;
@ -610,7 +610,7 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
let raw_transaction = raw.to_vec(); let raw_transaction = raw.to_vec();
match UntrustedRlp::new(&raw_transaction).as_val() { match UntrustedRlp::new(&raw_transaction).as_val() {
Ok(signed_transaction) => dispatch_transaction(&*take_weak!(self.client), &*take_weak!(self.miner), signed_transaction), Ok(signed_transaction) => dispatch_transaction(&*take_weak!(self.client), &*take_weak!(self.miner), signed_transaction).map(Into::into),
Err(e) => Err(errors::from_rlp_error(e)), Err(e) => Err(errors::from_rlp_error(e)),
} }
} }

View File

@ -26,7 +26,7 @@ use jsonrpc_core::Error;
use v1::traits::Personal; use v1::traits::Personal;
use v1::types::{H160 as RpcH160, H256 as RpcH256, TransactionRequest}; use v1::types::{H160 as RpcH160, H256 as RpcH256, TransactionRequest};
use v1::helpers::errors; use v1::helpers::errors;
use v1::helpers::dispatch::sign_and_dispatch; use v1::helpers::dispatch::{self, sign_and_dispatch};
/// Account management (personal) rpc implementation. /// Account management (personal) rpc implementation.
pub struct PersonalClient<C, M> where C: MiningBlockChainClient, M: MinerService { pub struct PersonalClient<C, M> where C: MiningBlockChainClient, M: MinerService {
@ -92,13 +92,17 @@ impl<C: 'static, M: 'static> Personal for PersonalClient<C, M> where C: MiningBl
fn sign_and_send_transaction(&self, request: TransactionRequest, password: String) -> Result<RpcH256, Error> { fn sign_and_send_transaction(&self, request: TransactionRequest, password: String) -> Result<RpcH256, Error> {
try!(self.active()); try!(self.active());
let client = take_weak!(self.client);
let miner = take_weak!(self.miner);
let accounts = take_weak!(self.accounts);
let request = dispatch::fill_optional_fields(request.into(), &*client, &*miner);
sign_and_dispatch( sign_and_dispatch(
&*take_weak!(self.client), &*client,
&*take_weak!(self.miner), &*miner,
&*take_weak!(self.accounts), &*accounts,
request.into(), request,
Some(password) Some(password)
) ).map(Into::into)
} }
} }

View File

@ -17,14 +17,15 @@
//! Transactions Confirmations rpc implementation //! Transactions Confirmations rpc implementation
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use jsonrpc_core::*; use jsonrpc_core::*;
use ethcore::account_provider::AccountProvider; use ethcore::account_provider::AccountProvider;
use ethcore::client::MiningBlockChainClient; use ethcore::client::MiningBlockChainClient;
use ethcore::miner::MinerService; use ethcore::miner::MinerService;
use v1::traits::Signer; use v1::traits::Signer;
use v1::types::{TransactionModification, ConfirmationRequest, U256}; use v1::types::{TransactionModification, ConfirmationRequest, ConfirmationResponse, U256};
use v1::helpers::{errors, SignerService, SigningQueue, ConfirmationPayload}; use v1::helpers::{errors, SignerService, SigningQueue, ConfirmationPayload};
use v1::helpers::dispatch::{sign_and_dispatch, sign, decrypt}; use v1::helpers::dispatch;
/// Transactions confirmation (personal) rpc implementation. /// Transactions confirmation (personal) rpc implementation.
pub struct SignerClient<C, M> where C: MiningBlockChainClient, M: MinerService { pub struct SignerClient<C, M> where C: MiningBlockChainClient, M: MinerService {
@ -73,7 +74,7 @@ impl<C: 'static, M: 'static> Signer for SignerClient<C, M> where C: MiningBlockC
// TODO [ToDr] TransactionModification is redundant for some calls // TODO [ToDr] TransactionModification is redundant for some calls
// might be better to replace it in future // might be better to replace it in future
fn confirm_request(&self, id: U256, modification: TransactionModification, pass: String) -> Result<Value, Error> { fn confirm_request(&self, id: U256, modification: TransactionModification, pass: String) -> Result<ConfirmationResponse, Error> {
try!(self.active()); try!(self.active());
let id = id.into(); let id = id.into();
@ -83,21 +84,16 @@ impl<C: 'static, M: 'static> Signer for SignerClient<C, M> where C: MiningBlockC
let miner = take_weak!(self.miner); let miner = take_weak!(self.miner);
signer.peek(&id).map(|confirmation| { signer.peek(&id).map(|confirmation| {
let result = match confirmation.payload { let mut payload = confirmation.payload.clone();
ConfirmationPayload::Transaction(mut request) => { // Modify payload
// apply modification match (&mut payload, modification.gas_price) {
if let Some(gas_price) = modification.gas_price { (&mut ConfirmationPayload::SendTransaction(ref mut request), Some(gas_price)) => {
request.gas_price = gas_price.into(); request.gas_price = gas_price.into();
}
sign_and_dispatch(&*client, &*miner, &*accounts, request.into(), Some(pass)).map(to_value)
}, },
ConfirmationPayload::Sign(address, hash) => { _ => {},
sign(&*accounts, address, Some(pass), hash) }
}, // Execute
ConfirmationPayload::Decrypt(address, msg) => { let result = dispatch::execute(&*client, &*miner, &*accounts, payload, Some(pass));
decrypt(&*accounts, address, Some(pass), msg)
},
};
if let Ok(ref response) = result { if let Ok(ref response) = result {
signer.request_confirmed(id, Ok(response.clone())); signer.request_confirmed(id, Ok(response.clone()));
} }

View File

@ -17,24 +17,33 @@
//! Signing RPC implementation. //! Signing RPC implementation.
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use transient_hashmap::TransientHashMap;
use util::{U256, Mutex};
use ethcore::account_provider::AccountProvider; use ethcore::account_provider::AccountProvider;
use ethcore::miner::MinerService; use ethcore::miner::MinerService;
use ethcore::client::MiningBlockChainClient; use ethcore::client::MiningBlockChainClient;
use transient_hashmap::TransientHashMap;
use util::{U256, Address, H256, Mutex};
use jsonrpc_core::*; use jsonrpc_core::Error;
use v1::helpers::{errors, SigningQueue, ConfirmationPromise, ConfirmationResult, ConfirmationPayload, TransactionRequest as TRequest, FilledTransactionRequest as FilledRequest, SignerService}; use v1::helpers::auto_args::Ready;
use v1::helpers::dispatch::{default_gas_price, sign_and_dispatch, sign, decrypt}; use v1::helpers::{
errors, dispatch,
SigningQueue, ConfirmationPromise, ConfirmationResult, ConfirmationPayload, SignerService
};
use v1::traits::{EthSigning, ParitySigning}; use v1::traits::{EthSigning, ParitySigning};
use v1::types::{TransactionRequest, H160 as RpcH160, H256 as RpcH256, U256 as RpcU256, Bytes as RpcBytes}; use v1::types::{
H160 as RpcH160, H256 as RpcH256, U256 as RpcU256, Bytes as RpcBytes, H520 as RpcH520,
Either as RpcEither,
TransactionRequest as RpcTransactionRequest,
ConfirmationPayload as RpcConfirmationPayload,
ConfirmationResponse as RpcConfirmationResponse
};
const MAX_PENDING_DURATION: u64 = 60 * 60; const MAX_PENDING_DURATION: u64 = 60 * 60;
pub enum DispatchResult { pub enum DispatchResult {
Promise(ConfirmationPromise), Promise(ConfirmationPromise),
Value(Value), Value(RpcConfirmationResponse),
} }
/// Implementation of functions that require signing when no trusted signer is used. /// Implementation of functions that require signing when no trusted signer is used.
@ -68,59 +77,41 @@ impl<C, M> SigningQueueClient<C, M> where
Ok(()) Ok(())
} }
fn add_to_queue<WhenUnlocked, Payload>(&self, sender: Address, when_unlocked: WhenUnlocked, payload: Payload) fn handle_dispatch<OnResponse>(&self, res: Result<DispatchResult, Error>, on_response: OnResponse)
-> Result<DispatchResult, Error> where where OnResponse: FnOnce(Result<RpcConfirmationResponse, Error>) + Send + 'static
WhenUnlocked: Fn(&AccountProvider) -> Result<Value, Error>, {
Payload: Fn() -> ConfirmationPayload, { match res {
Ok(DispatchResult::Value(result)) => on_response(Ok(result)),
Ok(DispatchResult::Promise(promise)) => {
promise.wait_for_result(move |result| {
on_response(result.unwrap_or_else(|| Err(errors::request_rejected())))
})
},
Err(e) => on_response(Err(e)),
}
}
fn add_to_queue(&self, payload: ConfirmationPayload) -> Result<DispatchResult, Error> {
let client = take_weak!(self.client);
let miner = take_weak!(self.miner);
let accounts = take_weak!(self.accounts); let accounts = take_weak!(self.accounts);
let sender = payload.sender();
if accounts.is_unlocked(sender) { if accounts.is_unlocked(sender) {
return when_unlocked(&accounts).map(DispatchResult::Value); return dispatch::execute(&*client, &*miner, &*accounts, payload, None).map(DispatchResult::Value);
} }
take_weak!(self.signer).add_request(payload()) take_weak!(self.signer).add_request(payload)
.map(DispatchResult::Promise) .map(DispatchResult::Promise)
.map_err(|_| errors::request_rejected_limit()) .map_err(|_| errors::request_rejected_limit())
} }
fn handle_dispatch(&self, res: Result<DispatchResult, Error>, ready: Ready) { fn dispatch(&self, payload: RpcConfirmationPayload) -> Result<DispatchResult, Error> {
match res { let client = take_weak!(self.client);
Ok(DispatchResult::Value(v)) => ready.ready(Ok(v)), let miner = take_weak!(self.miner);
Ok(DispatchResult::Promise(promise)) => {
promise.wait_for_result(move |result| {
ready.ready(result.unwrap_or_else(|| Err(errors::request_rejected())))
})
},
Err(e) => ready.ready(Err(e)),
}
}
fn dispatch_sign(&self, params: Params) -> Result<DispatchResult, Error> { let payload = dispatch::from_rpc(payload, &*client, &*miner);
from_params::<(RpcH160, RpcH256)>(params).and_then(|(address, msg)| { self.add_to_queue(payload)
let address: Address = address.into();
let msg: H256 = msg.into();
self.add_to_queue(
address,
|accounts| sign(accounts, address, None, msg.clone()),
|| ConfirmationPayload::Sign(address, msg.clone()),
)
})
}
fn dispatch_transaction(&self, params: Params) -> Result<DispatchResult, Error> {
from_params::<(TransactionRequest, )>(params).and_then(|(request, )| {
let request: TRequest = request.into();
let (client, miner) = (take_weak!(self.client), take_weak!(self.miner));
self.add_to_queue(
request.from,
|accounts| sign_and_dispatch(&*client, &*miner, accounts, request.clone(), None).map(to_value),
|| {
let request = fill_optional_fields(request.clone(), &*client, &*miner);
ConfirmationPayload::Transaction(request)
}
)
})
} }
} }
@ -128,62 +119,59 @@ impl<C: 'static, M: 'static> ParitySigning for SigningQueueClient<C, M> where
C: MiningBlockChainClient, C: MiningBlockChainClient,
M: MinerService, M: MinerService,
{ {
fn post_sign(&self, params: Params) -> Result<Value, Error> { fn post_sign(&self, address: RpcH160, hash: RpcH256) -> Result<RpcEither<RpcU256, RpcConfirmationResponse>, Error> {
try!(self.active()); try!(self.active());
self.dispatch_sign(params).map(|result| match result { self.dispatch(RpcConfirmationPayload::Signature((address, hash).into()))
DispatchResult::Value(v) => v, .map(|result| match result {
DispatchResult::Promise(promise) => { DispatchResult::Value(v) => RpcEither::Or(v),
let id = promise.id(); DispatchResult::Promise(promise) => {
self.pending.lock().insert(id, promise); let id = promise.id();
to_value(&RpcU256::from(id)) self.pending.lock().insert(id, promise);
}, RpcEither::Either(id.into())
}) },
})
} }
fn post_transaction(&self, params: Params) -> Result<Value, Error> { fn post_transaction(&self, request: RpcTransactionRequest) -> Result<RpcEither<RpcU256, RpcConfirmationResponse>, Error> {
try!(self.active()); try!(self.active());
self.dispatch_transaction(params).map(|result| match result { self.dispatch(RpcConfirmationPayload::SendTransaction(request))
DispatchResult::Value(v) => v, .map(|result| match result {
DispatchResult::Promise(promise) => { DispatchResult::Value(v) => RpcEither::Or(v),
let id = promise.id(); DispatchResult::Promise(promise) => {
self.pending.lock().insert(id, promise); let id = promise.id();
to_value(&RpcU256::from(id)) self.pending.lock().insert(id, promise);
}, RpcEither::Either(id.into())
}) },
})
} }
fn check_request(&self, params: Params) -> Result<Value, Error> { fn check_request(&self, id: RpcU256) -> Result<Option<RpcConfirmationResponse>, Error> {
try!(self.active()); try!(self.active());
let mut pending = self.pending.lock(); let mut pending = self.pending.lock();
from_params::<(RpcU256, )>(params).and_then(|(id, )| { let id: U256 = id.into();
let id: U256 = id.into(); let res = match pending.get(&id) {
let res = match pending.get(&id) { Some(ref promise) => match promise.result() {
Some(ref promise) => match promise.result() { ConfirmationResult::Waiting => { return Ok(None); }
ConfirmationResult::Waiting => { return Ok(Value::Null); } ConfirmationResult::Rejected => Err(errors::request_rejected()),
ConfirmationResult::Rejected => Err(errors::request_rejected()), ConfirmationResult::Confirmed(rpc_response) => rpc_response.map(Some),
ConfirmationResult::Confirmed(rpc_response) => rpc_response, },
}, _ => { return Err(errors::request_not_found()); }
_ => { return Err(errors::request_not_found()); } };
}; pending.remove(&id);
pending.remove(&id); res
res
})
} }
fn decrypt_message(&self, params: Params, ready: Ready) { fn decrypt_message(&self, ready: Ready<RpcBytes>, address: RpcH160, data: RpcBytes) {
let res = self.active() let res = self.active()
.and_then(|_| from_params::<(RpcH160, RpcBytes)>(params)) .and_then(|_| self.dispatch(RpcConfirmationPayload::Decrypt((address, data).into())));
.and_then(|(address, msg)| { // TODO [todr] typed handle_dispatch
let address: Address = address.into(); self.handle_dispatch(res, |response| {
match response {
self.add_to_queue( Ok(RpcConfirmationResponse::Decrypt(data)) => ready.ready(Ok(data)),
address, Err(e) => ready.ready(Err(e)),
|accounts| decrypt(accounts, address, None, msg.clone().into()), e => ready.ready(Err(errors::internal("Unexpected result.", e))),
|| ConfirmationPayload::Decrypt(address, msg.clone().into()) }
) });
});
self.handle_dispatch(res, ready);
} }
} }
@ -191,26 +179,36 @@ impl<C: 'static, M: 'static> EthSigning for SigningQueueClient<C, M> where
C: MiningBlockChainClient, C: MiningBlockChainClient,
M: MinerService, M: MinerService,
{ {
fn sign(&self, params: Params, ready: Ready) { fn sign(&self, ready: Ready<RpcH520>, address: RpcH160, hash: RpcH256) {
let res = self.active().and_then(|_| self.dispatch_sign(params)); let res = self.active().and_then(|_| self.dispatch(RpcConfirmationPayload::Signature((address, hash).into())));
self.handle_dispatch(res, ready); self.handle_dispatch(res, |response| {
match response {
Ok(RpcConfirmationResponse::Signature(signature)) => ready.ready(Ok(signature)),
Err(e) => ready.ready(Err(e)),
e => ready.ready(Err(errors::internal("Unexpected result.", e))),
}
});
} }
fn send_transaction(&self, params: Params, ready: Ready) { fn send_transaction(&self, ready: Ready<RpcH256>, request: RpcTransactionRequest) {
let res = self.active().and_then(|_| self.dispatch_transaction(params)); let res = self.active().and_then(|_| self.dispatch(RpcConfirmationPayload::SendTransaction(request)));
self.handle_dispatch(res, ready); self.handle_dispatch(res, |response| {
match response {
Ok(RpcConfirmationResponse::SendTransaction(hash)) => ready.ready(Ok(hash)),
Err(e) => ready.ready(Err(e)),
e => ready.ready(Err(errors::internal("Unexpected result.", e))),
}
});
} }
}
fn fill_optional_fields<C, M>(request: TRequest, client: &C, miner: &M) -> FilledRequest fn sign_transaction(&self, ready: Ready<RpcBytes>, request: RpcTransactionRequest) {
where C: MiningBlockChainClient, M: MinerService { let res = self.active().and_then(|_| self.dispatch(RpcConfirmationPayload::SignTransaction(request)));
FilledRequest { self.handle_dispatch(res, |response| {
from: request.from, match response {
to: request.to, Ok(RpcConfirmationResponse::SignTransaction(rlp)) => ready.ready(Ok(rlp)),
nonce: request.nonce, Err(e) => ready.ready(Err(e)),
gas_price: request.gas_price.unwrap_or_else(|| default_gas_price(client, miner)), e => ready.ready(Err(errors::internal("Unexpected result.", e))),
gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()), }
value: request.value.unwrap_or_else(|| 0.into()), });
data: request.data.unwrap_or_else(Vec::new),
} }
} }

View File

@ -22,11 +22,19 @@ use ethcore::account_provider::AccountProvider;
use ethcore::miner::MinerService; use ethcore::miner::MinerService;
use ethcore::client::MiningBlockChainClient; use ethcore::client::MiningBlockChainClient;
use jsonrpc_core::*; use jsonrpc_core::Error;
use v1::helpers::auto_args::Ready;
use v1::helpers::errors; use v1::helpers::errors;
use v1::helpers::dispatch::{sign_and_dispatch, sign, decrypt}; use v1::helpers::dispatch;
use v1::traits::{EthSigning, ParitySigning}; use v1::traits::{EthSigning, ParitySigning};
use v1::types::{TransactionRequest, H160 as RpcH160, H256 as RpcH256, Bytes as RpcBytes}; use v1::types::{
U256 as RpcU256,
H160 as RpcH160, H256 as RpcH256, H520 as RpcH520, Bytes as RpcBytes,
Either as RpcEither,
TransactionRequest as RpcTransactionRequest,
ConfirmationPayload as RpcConfirmationPayload,
ConfirmationResponse as RpcConfirmationResponse,
};
/// Implementation of functions that require signing when no trusted signer is used. /// Implementation of functions that require signing when no trusted signer is used.
pub struct SigningUnsafeClient<C, M> where pub struct SigningUnsafeClient<C, M> where
@ -58,26 +66,47 @@ impl<C, M> SigningUnsafeClient<C, M> where
take_weak!(self.client).keep_alive(); take_weak!(self.client).keep_alive();
Ok(()) Ok(())
} }
fn handle(&self, payload: RpcConfirmationPayload) -> Result<RpcConfirmationResponse, Error> {
try!(self.active());
let client = take_weak!(self.client);
let miner = take_weak!(self.miner);
let accounts = take_weak!(self.accounts);
let payload = dispatch::from_rpc(payload, &*client, &*miner);
dispatch::execute(&*client, &*miner, &*accounts, payload, None)
}
} }
impl<C: 'static, M: 'static> EthSigning for SigningUnsafeClient<C, M> where impl<C: 'static, M: 'static> EthSigning for SigningUnsafeClient<C, M> where
C: MiningBlockChainClient, C: MiningBlockChainClient,
M: MinerService, M: MinerService,
{ {
fn sign(&self, params: Params, ready: Ready) { fn sign(&self, ready: Ready<RpcH520>, address: RpcH160, hash: RpcH256) {
ready.ready(self.active() let result = match self.handle(RpcConfirmationPayload::Signature((address, hash).into())) {
.and_then(|_| from_params::<(RpcH160, RpcH256)>(params)) Ok(RpcConfirmationResponse::Signature(signature)) => Ok(signature),
.and_then(|(address, msg)| { Err(e) => Err(e),
sign(&*take_weak!(self.accounts), address.into(), None, msg.into()) e => Err(errors::internal("Unexpected result", e)),
})) };
ready.ready(result);
} }
fn send_transaction(&self, params: Params, ready: Ready) { fn send_transaction(&self, ready: Ready<RpcH256>, request: RpcTransactionRequest) {
ready.ready(self.active() let result = match self.handle(RpcConfirmationPayload::SendTransaction(request)) {
.and_then(|_| from_params::<(TransactionRequest, )>(params)) Ok(RpcConfirmationResponse::SendTransaction(hash)) => Ok(hash),
.and_then(|(request, )| { Err(e) => Err(e),
sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), &*take_weak!(self.accounts), request.into(), None).map(to_value) e => Err(errors::internal("Unexpected result", e)),
})) };
ready.ready(result);
}
fn sign_transaction(&self, ready: Ready<RpcBytes>, request: RpcTransactionRequest) {
let result = match self.handle(RpcConfirmationPayload::SignTransaction(request)) {
Ok(RpcConfirmationResponse::SignTransaction(rlp)) => Ok(rlp),
Err(e) => Err(e),
e => Err(errors::internal("Unexpected result", e)),
};
ready.ready(result);
} }
} }
@ -85,25 +114,26 @@ impl<C: 'static, M: 'static> ParitySigning for SigningUnsafeClient<C, M> where
C: MiningBlockChainClient, C: MiningBlockChainClient,
M: MinerService, M: MinerService,
{ {
fn decrypt_message(&self, params: Params, ready: Ready) { fn decrypt_message(&self, ready: Ready<RpcBytes>, address: RpcH160, data: RpcBytes) {
ready.ready(self.active() let result = match self.handle(RpcConfirmationPayload::Decrypt((address, data).into())) {
.and_then(|_| from_params::<(RpcH160, RpcBytes)>(params)) Ok(RpcConfirmationResponse::Decrypt(data)) => Ok(data),
.and_then(|(address, ciphertext)| { Err(e) => Err(e),
decrypt(&*take_weak!(self.accounts), address.into(), None, ciphertext.0) e => Err(errors::internal("Unexpected result", e)),
})) };
ready.ready(result);
} }
fn post_sign(&self, _: Params) -> Result<Value, Error> { fn post_sign(&self, _: RpcH160, _: RpcH256) -> Result<RpcEither<RpcU256, RpcConfirmationResponse>, Error> {
// We don't support this in non-signer mode. // We don't support this in non-signer mode.
Err(errors::signer_disabled()) Err(errors::signer_disabled())
} }
fn post_transaction(&self, _: Params) -> Result<Value, Error> { fn post_transaction(&self, _: RpcTransactionRequest) -> Result<RpcEither<RpcU256, RpcConfirmationResponse>, Error> {
// We don't support this in non-signer mode. // We don't support this in non-signer mode.
Err(errors::signer_disabled()) Err(errors::signer_disabled())
} }
fn check_request(&self, _: Params) -> Result<Value, Error> { fn check_request(&self, _: RpcU256) -> Result<Option<RpcConfirmationResponse>, Error> {
// We don't support this in non-signer mode. // We don't support this in non-signer mode.
Err(errors::signer_disabled()) Err(errors::signer_disabled())
} }

View File

@ -18,6 +18,7 @@ use std::str::FromStr;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Instant, Duration}; use std::time::{Instant, Duration};
use rlp;
use jsonrpc_core::IoHandler; use jsonrpc_core::IoHandler;
use util::{Uint, U256, Address, H256, FixedHash, Mutex}; use util::{Uint, U256, Address, H256, FixedHash, Mutex};
use ethcore::account_provider::AccountProvider; use ethcore::account_provider::AccountProvider;
@ -761,6 +762,44 @@ fn rpc_eth_send_transaction() {
assert_eq!(tester.io.handle_request_sync(&request), Some(response)); assert_eq!(tester.io.handle_request_sync(&request), Some(response));
} }
#[test]
fn rpc_eth_sign_transaction() {
let tester = EthTester::default();
let address = tester.accounts_provider.new_account("").unwrap();
tester.accounts_provider.unlock_account_permanently(address, "".into()).unwrap();
let request = r#"{
"jsonrpc": "2.0",
"method": "eth_signTransaction",
"params": [{
"from": ""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"",
"to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
"gas": "0x76c0",
"gasPrice": "0x9184e72a000",
"value": "0x9184e72a"
}],
"id": 1
}"#;
let t = Transaction {
nonce: U256::one(),
gas_price: U256::from(0x9184e72a000u64),
gas: U256::from(0x76c0),
action: Action::Call(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()),
value: U256::from(0x9184e72au64),
data: vec![]
};
let signature = tester.accounts_provider.sign(address, None, t.hash(None)).unwrap();
let t = t.with_signature(signature, None);
let rlp = rlp::encode(&t);
let response = r#"{"jsonrpc":"2.0","result":"0x"#.to_owned() + &rlp.to_hex() + r#"","id":1}"#;
tester.miner.last_nonces.write().insert(address.clone(), U256::zero());
assert_eq!(tester.io.handle_request_sync(&request), Some(response));
}
#[test] #[test]
fn rpc_eth_send_transaction_with_bad_to() { fn rpc_eth_send_transaction_with_bad_to() {
let tester = EthTester::default(); let tester = EthTester::default();
@ -839,7 +878,7 @@ fn rpc_eth_send_raw_transaction() {
let signature = tester.accounts_provider.sign(address, None, t.hash(None)).unwrap(); let signature = tester.accounts_provider.sign(address, None, t.hash(None)).unwrap();
let t = t.with_signature(signature, None); let t = t.with_signature(signature, None);
let rlp = ::rlp::encode(&t).to_vec().to_hex(); let rlp = rlp::encode(&t).to_vec().to_hex();
let req = r#"{ let req = r#"{
"jsonrpc": "2.0", "jsonrpc": "2.0",

View File

@ -71,7 +71,7 @@ fn signer_tester() -> SignerTester {
fn should_return_list_of_items_to_confirm() { fn should_return_list_of_items_to_confirm() {
// given // given
let tester = signer_tester(); let tester = signer_tester();
tester.signer.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest { tester.signer.add_request(ConfirmationPayload::SendTransaction(FilledTransactionRequest {
from: Address::from(1), from: Address::from(1),
to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()),
gas_price: U256::from(10_000), gas_price: U256::from(10_000),
@ -80,7 +80,7 @@ fn should_return_list_of_items_to_confirm() {
data: vec![], data: vec![],
nonce: None, nonce: None,
})).unwrap(); })).unwrap();
tester.signer.add_request(ConfirmationPayload::Sign(1.into(), 5.into())).unwrap(); tester.signer.add_request(ConfirmationPayload::Signature(1.into(), 5.into())).unwrap();
// when // when
let request = r#"{"jsonrpc":"2.0","method":"signer_requestsToConfirm","params":[],"id":1}"#; let request = r#"{"jsonrpc":"2.0","method":"signer_requestsToConfirm","params":[],"id":1}"#;
@ -100,7 +100,7 @@ fn should_return_list_of_items_to_confirm() {
fn should_reject_transaction_from_queue_without_dispatching() { fn should_reject_transaction_from_queue_without_dispatching() {
// given // given
let tester = signer_tester(); let tester = signer_tester();
tester.signer.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest { tester.signer.add_request(ConfirmationPayload::SendTransaction(FilledTransactionRequest {
from: Address::from(1), from: Address::from(1),
to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()),
gas_price: U256::from(10_000), gas_price: U256::from(10_000),
@ -125,7 +125,7 @@ fn should_reject_transaction_from_queue_without_dispatching() {
fn should_not_remove_transaction_if_password_is_invalid() { fn should_not_remove_transaction_if_password_is_invalid() {
// given // given
let tester = signer_tester(); let tester = signer_tester();
tester.signer.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest { tester.signer.add_request(ConfirmationPayload::SendTransaction(FilledTransactionRequest {
from: Address::from(1), from: Address::from(1),
to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()),
gas_price: U256::from(10_000), gas_price: U256::from(10_000),
@ -149,7 +149,7 @@ fn should_not_remove_transaction_if_password_is_invalid() {
fn should_not_remove_sign_if_password_is_invalid() { fn should_not_remove_sign_if_password_is_invalid() {
// given // given
let tester = signer_tester(); let tester = signer_tester();
tester.signer.add_request(ConfirmationPayload::Sign(0.into(), 5.into())).unwrap(); tester.signer.add_request(ConfirmationPayload::Signature(0.into(), 5.into())).unwrap();
assert_eq!(tester.signer.requests().len(), 1); assert_eq!(tester.signer.requests().len(), 1);
// when // when
@ -167,7 +167,7 @@ fn should_confirm_transaction_and_dispatch() {
let tester = signer_tester(); let tester = signer_tester();
let address = tester.accounts.new_account("test").unwrap(); let address = tester.accounts.new_account("test").unwrap();
let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(); let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap();
tester.signer.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest { tester.signer.add_request(ConfirmationPayload::SendTransaction(FilledTransactionRequest {
from: address, from: address,
to: Some(recipient), to: Some(recipient),
gas_price: U256::from(10_000), gas_price: U256::from(10_000),

View File

@ -16,15 +16,17 @@
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use jsonrpc_core::{IoHandler, to_value, Success}; use rlp;
use jsonrpc_core::{IoHandler, Success};
use v1::impls::SigningQueueClient; use v1::impls::SigningQueueClient;
use v1::traits::{EthSigning, ParitySigning, Parity}; use v1::traits::{EthSigning, ParitySigning, Parity};
use v1::helpers::{SignerService, SigningQueue}; use v1::helpers::{SignerService, SigningQueue};
use v1::types::{H256 as RpcH256, H520 as RpcH520, Bytes}; use v1::types::ConfirmationResponse;
use v1::tests::helpers::TestMinerService; use v1::tests::helpers::TestMinerService;
use v1::tests::mocked::parity; use v1::tests::mocked::parity;
use util::{Address, FixedHash, Uint, U256, H256, H520}; use util::{Address, FixedHash, Uint, U256, H256, ToPretty};
use ethcore::account_provider::AccountProvider; use ethcore::account_provider::AccountProvider;
use ethcore::client::TestBlockChainClient; use ethcore::client::TestBlockChainClient;
use ethcore::transaction::{Transaction, Action}; use ethcore::transaction::{Transaction, Action};
@ -88,7 +90,7 @@ fn should_add_sign_to_queue() {
let async_result = tester.io.handle_request(&request).unwrap(); let async_result = tester.io.handle_request(&request).unwrap();
assert_eq!(tester.signer.requests().len(), 1); assert_eq!(tester.signer.requests().len(), 1);
// respond // respond
tester.signer.request_confirmed(U256::from(1), Ok(to_value(&RpcH520::from(H520::default())))); tester.signer.request_confirmed(1.into(), Ok(ConfirmationResponse::Signature(0.into())));
assert!(async_result.on_result(move |res| { assert!(async_result.on_result(move |res| {
assert_eq!(res, response.to_owned()); assert_eq!(res, response.to_owned());
})); }));
@ -162,7 +164,7 @@ fn should_check_status_of_request_when_its_resolved() {
"id": 1 "id": 1
}"#; }"#;
tester.io.handle_request_sync(&request).expect("Sent"); tester.io.handle_request_sync(&request).expect("Sent");
tester.signer.request_confirmed(U256::from(1), Ok(to_value(&"Hello World!"))); tester.signer.request_confirmed(1.into(), Ok(ConfirmationResponse::Signature(1.into())));
// when // when
let request = r#"{ let request = r#"{
@ -171,7 +173,7 @@ fn should_check_status_of_request_when_its_resolved() {
"params": ["0x1"], "params": ["0x1"],
"id": 1 "id": 1
}"#; }"#;
let response = r#"{"jsonrpc":"2.0","result":"Hello World!","id":1}"#; let response = r#"{"jsonrpc":"2.0","result":"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001","id":1}"#;
// then // then
assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned())); assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned()));
@ -228,7 +230,54 @@ fn should_add_transaction_to_queue() {
let async_result = tester.io.handle_request(&request).unwrap(); let async_result = tester.io.handle_request(&request).unwrap();
assert_eq!(tester.signer.requests().len(), 1); assert_eq!(tester.signer.requests().len(), 1);
// respond // respond
tester.signer.request_confirmed(U256::from(1), Ok(to_value(&RpcH256::from(H256::default())))); tester.signer.request_confirmed(1.into(), Ok(ConfirmationResponse::SendTransaction(0.into())));
assert!(async_result.on_result(move |res| {
assert_eq!(res, response.to_owned());
}));
}
#[test]
fn should_add_sign_transaction_to_the_queue() {
// given
let tester = eth_signing();
let address = tester.accounts.new_account("test").unwrap();
assert_eq!(tester.signer.requests().len(), 0);
// when
let request = r#"{
"jsonrpc": "2.0",
"method": "eth_signTransaction",
"params": [{
"from": ""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"",
"to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
"gas": "0x76c0",
"gasPrice": "0x9184e72a000",
"value": "0x9184e72a"
}],
"id": 1
}"#;
let t = Transaction {
nonce: U256::one(),
gas_price: U256::from(0x9184e72a000u64),
gas: U256::from(0x76c0),
action: Action::Call(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()),
value: U256::from(0x9184e72au64),
data: vec![]
};
let signature = tester.accounts.sign(address, Some("test".into()), t.hash(None)).unwrap();
let t = t.with_signature(signature, None);
let rlp = rlp::encode(&t);
let response = r#"{"jsonrpc":"2.0","result":"0x"#.to_owned() + &rlp.to_hex() + r#"","id":1}"#;
// then
tester.miner.last_nonces.write().insert(address.clone(), U256::zero());
let async_result = tester.io.handle_request(&request).unwrap();
assert_eq!(tester.signer.requests().len(), 1);
// respond
tester.signer.request_confirmed(1.into(), Ok(ConfirmationResponse::SignTransaction(rlp.to_vec().into())));
assert!(async_result.on_result(move |res| { assert!(async_result.on_result(move |res| {
assert_eq!(res, response.to_owned()); assert_eq!(res, response.to_owned());
})); }));
@ -325,7 +374,7 @@ fn should_add_decryption_to_the_queue() {
let async_result = tester.io.handle_request(&request).unwrap(); let async_result = tester.io.handle_request(&request).unwrap();
assert_eq!(tester.signer.requests().len(), 1); assert_eq!(tester.signer.requests().len(), 1);
// respond // respond
tester.signer.request_confirmed(U256::from(1), Ok(to_value(Bytes(vec![0x1, 0x2])))); tester.signer.request_confirmed(1.into(), Ok(ConfirmationResponse::Decrypt(vec![0x1, 0x2].into())));
assert!(async_result.on_result(move |res| { assert!(async_result.on_result(move |res| {
assert_eq!(res, response.to_owned()); assert_eq!(res, response.to_owned());
})); }));

View File

@ -15,7 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Eth rpc interface. //! Eth rpc interface.
use jsonrpc_core::*; use jsonrpc_core::Error;
use v1::types::{RichBlock, BlockNumber, Bytes, CallRequest, Filter, FilterChanges, Index}; use v1::types::{RichBlock, BlockNumber, Bytes, CallRequest, Filter, FilterChanges, Index};
use v1::types::{Log, Receipt, SyncStatus, Transaction, Work}; use v1::types::{Log, Receipt, SyncStatus, Transaction, Work};

View File

@ -15,26 +15,27 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Eth rpc interface. //! Eth rpc interface.
use std::sync::Arc;
use jsonrpc_core::*;
/// Signing methods implementation relying on unlocked accounts. use v1::helpers::auto_args::{WrapAsync, Ready};
pub trait EthSigning: Sized + Send + Sync + 'static { use v1::types::{H160, H256, H520, TransactionRequest, Bytes};
/// Signs the data with given address signature.
fn sign(&self, _: Params, _: Ready);
/// Sends transaction; will block for 20s to try to return the build_rpc_trait! {
/// transaction hash. /// Signing methods implementation relying on unlocked accounts.
/// If it cannot yet be signed, it will return a transaction ID for pub trait EthSigning {
/// later use with check_transaction. /// Signs the data with given address signature.
fn send_transaction(&self, _: Params, _: Ready); #[rpc(async, name = "eth_sign")]
fn sign(&self, Ready<H520>, H160, H256);
/// Should be used to convert object to io delegate. /// Sends transaction; will block waiting for signer to return the
fn to_delegate(self) -> IoDelegate<Self> { /// transaction hash.
let mut delegate = IoDelegate::new(Arc::new(self)); /// If Signer is disable it will require the account to be unlocked.
delegate.add_async_method("eth_sign", EthSigning::sign); #[rpc(async, name = "eth_sendTransaction")]
delegate.add_async_method("eth_sendTransaction", EthSigning::send_transaction); fn send_transaction(&self, Ready<H256>, TransactionRequest);
delegate /// Signs transactions without dispatching it to the network.
/// Returns signed transaction RLP representation.
/// It can be later submitted using `eth_sendRawTransaction`.
#[rpc(async, name = "eth_signTransaction")]
fn sign_transaction(&self, Ready<Bytes>, TransactionRequest);
} }
} }

View File

@ -15,38 +15,32 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! ParitySigning rpc interface. //! ParitySigning rpc interface.
use std::sync::Arc; use jsonrpc_core::Error;
use jsonrpc_core::*;
/// Signing methods implementation relying on unlocked accounts. use v1::helpers::auto_args::{Wrap, WrapAsync, Ready};
pub trait ParitySigning: Sized + Send + Sync + 'static { use v1::types::{U256, H160, H256, Bytes, ConfirmationResponse, TransactionRequest, Either};
/// Posts sign request asynchronously.
/// Will return a confirmation ID for later use with check_transaction.
fn post_sign(&self, _: Params) -> Result<Value, Error>;
/// Posts transaction asynchronously. build_rpc_trait! {
/// Will return a transaction ID for later use with check_transaction. /// Signing methods implementation.
fn post_transaction(&self, _: Params) -> Result<Value, Error>; pub trait ParitySigning {
/// Posts sign request asynchronously.
/// Will return a confirmation ID for later use with check_transaction.
#[rpc(name = "parity_postSign")]
fn post_sign(&self, H160, H256) -> Result<Either<U256, ConfirmationResponse>, Error>;
/// Checks the progress of a previously posted request (transaction/sign). /// Posts transaction asynchronously.
/// Should be given a valid send_transaction ID. /// Will return a transaction ID for later use with check_transaction.
/// Returns the transaction hash, the zero hash (not yet available), #[rpc(name = "parity_postTransaction")]
/// or the signature, fn post_transaction(&self, TransactionRequest) -> Result<Either<U256, ConfirmationResponse>, Error>;
/// or an error.
fn check_request(&self, _: Params) -> Result<Value, Error>;
/// Decrypt some ECIES-encrypted message. /// Checks the progress of a previously posted request (transaction/sign).
/// First parameter is the address with which it is encrypted, second is the ciphertext. /// Should be given a valid send_transaction ID.
fn decrypt_message(&self, _: Params, _: Ready); #[rpc(name = "parity_checkRequest")]
fn check_request(&self, U256) -> Result<Option<ConfirmationResponse>, Error>;
/// Should be used to convert object to io delegate. /// Decrypt some ECIES-encrypted message.
fn to_delegate(self) -> IoDelegate<Self> { /// First parameter is the address with which it is encrypted, second is the ciphertext.
let mut delegate = IoDelegate::new(Arc::new(self)); #[rpc(async, name = "parity_decryptMessage")]
delegate.add_method("parity_postSign", ParitySigning::post_sign); fn decrypt_message(&self, Ready<Bytes>, H160, Bytes);
delegate.add_method("parity_postTransaction", ParitySigning::post_transaction);
delegate.add_method("parity_checkRequest", ParitySigning::check_request);
delegate.add_async_method("parity_decryptMessage", ParitySigning::decrypt_message);
delegate
} }
} }

View File

@ -15,10 +15,10 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Parity Signer-related rpc interface. //! Parity Signer-related rpc interface.
use jsonrpc_core::{Value, Error}; use jsonrpc_core::Error;
use v1::helpers::auto_args::Wrap; use v1::helpers::auto_args::Wrap;
use v1::types::{U256, TransactionModification, ConfirmationRequest}; use v1::types::{U256, TransactionModification, ConfirmationRequest, ConfirmationResponse};
build_rpc_trait! { build_rpc_trait! {
@ -31,7 +31,7 @@ build_rpc_trait! {
/// Confirm specific request. /// Confirm specific request.
#[rpc(name = "signer_confirmRequest")] #[rpc(name = "signer_confirmRequest")]
fn confirm_request(&self, U256, TransactionModification, String) -> Result<Value, Error>; fn confirm_request(&self, U256, TransactionModification, String) -> Result<ConfirmationResponse, Error>;
/// Reject the confirmation request. /// Reject the confirmation request.
#[rpc(name = "signer_rejectRequest")] #[rpc(name = "signer_rejectRequest")]

View File

@ -16,10 +16,11 @@
//! Types used in Confirmations queue (Trusted Signer) //! Types used in Confirmations queue (Trusted Signer)
use v1::types::{U256, TransactionRequest, H160, H256, Bytes}; use std::fmt;
use serde::{Serialize, Serializer};
use v1::types::{U256, TransactionRequest, H160, H256, H520, Bytes};
use v1::helpers; use v1::helpers;
/// Confirmation waiting in a queue /// Confirmation waiting in a queue
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)] #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)]
pub struct ConfirmationRequest { pub struct ConfirmationRequest {
@ -47,6 +48,15 @@ pub struct SignRequest {
pub hash: H256, pub hash: H256,
} }
impl From<(H160, H256)> for SignRequest {
fn from(tuple: (H160, H256)) -> Self {
SignRequest {
address: tuple.0,
hash: tuple.1,
}
}
}
/// Decrypt request /// Decrypt request
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)] #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)]
pub struct DecryptRequest { pub struct DecryptRequest {
@ -56,15 +66,53 @@ pub struct DecryptRequest {
pub msg: Bytes, pub msg: Bytes,
} }
impl From<(H160, Bytes)> for DecryptRequest {
fn from(tuple: (H160, Bytes)) -> Self {
DecryptRequest {
address: tuple.0,
msg: tuple.1,
}
}
}
/// Confirmation response for particular payload
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum ConfirmationResponse {
/// Transaction Hash
SendTransaction(H256),
/// Transaction RLP
SignTransaction(Bytes),
/// Signature
Signature(H520),
/// Decrypted data
Decrypt(Bytes),
}
impl Serialize for ConfirmationResponse {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: Serializer
{
match *self {
ConfirmationResponse::SendTransaction(ref hash) => hash.serialize(serializer),
ConfirmationResponse::SignTransaction(ref rlp) => rlp.serialize(serializer),
ConfirmationResponse::Signature(ref signature) => signature.serialize(serializer),
ConfirmationResponse::Decrypt(ref data) => data.serialize(serializer),
}
}
}
/// Confirmation payload, i.e. the thing to be confirmed /// Confirmation payload, i.e. the thing to be confirmed
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)] #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)]
pub enum ConfirmationPayload { pub enum ConfirmationPayload {
/// Transaction /// Send Transaction
#[serde(rename="transaction")] #[serde(rename="transaction")]
Transaction(TransactionRequest), SendTransaction(TransactionRequest),
/// Sign Transaction
#[serde(rename="transaction")]
SignTransaction(TransactionRequest),
/// Signature /// Signature
#[serde(rename="sign")] #[serde(rename="sign")]
Sign(SignRequest), Signature(SignRequest),
/// Decryption /// Decryption
#[serde(rename="decrypt")] #[serde(rename="decrypt")]
Decrypt(DecryptRequest), Decrypt(DecryptRequest),
@ -73,8 +121,9 @@ pub enum ConfirmationPayload {
impl From<helpers::ConfirmationPayload> for ConfirmationPayload { impl From<helpers::ConfirmationPayload> for ConfirmationPayload {
fn from(c: helpers::ConfirmationPayload) -> Self { fn from(c: helpers::ConfirmationPayload) -> Self {
match c { match c {
helpers::ConfirmationPayload::Transaction(t) => ConfirmationPayload::Transaction(t.into()), helpers::ConfirmationPayload::SendTransaction(t) => ConfirmationPayload::SendTransaction(t.into()),
helpers::ConfirmationPayload::Sign(address, hash) => ConfirmationPayload::Sign(SignRequest { helpers::ConfirmationPayload::SignTransaction(t) => ConfirmationPayload::SignTransaction(t.into()),
helpers::ConfirmationPayload::Signature(address, hash) => ConfirmationPayload::Signature(SignRequest {
address: address.into(), address: address.into(),
hash: hash.into(), hash: hash.into(),
}), }),
@ -94,6 +143,41 @@ pub struct TransactionModification {
pub gas_price: Option<U256>, pub gas_price: Option<U256>,
} }
/// Represents two possible return values.
#[derive(Debug, Clone)]
pub enum Either<A, B> where
A: fmt::Debug + Clone,
B: fmt::Debug + Clone,
{
/// Primary value
Either(A),
/// Secondary value
Or(B),
}
impl<A, B> From<A> for Either<A, B> where
A: fmt::Debug + Clone,
B: fmt::Debug + Clone,
{
fn from(a: A) -> Self {
Either::Either(a)
}
}
impl<A, B> Serialize for Either<A, B> where
A: Serialize + fmt::Debug + Clone,
B: Serialize + fmt::Debug + Clone,
{
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: Serializer
{
match *self {
Either::Either(ref a) => a.serialize(serializer),
Either::Or(ref b) => b.serialize(serializer),
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::str::FromStr; use std::str::FromStr;
@ -107,7 +191,7 @@ mod tests {
// given // given
let request = helpers::ConfirmationRequest { let request = helpers::ConfirmationRequest {
id: 15.into(), id: 15.into(),
payload: helpers::ConfirmationPayload::Sign(1.into(), 5.into()), payload: helpers::ConfirmationPayload::Signature(1.into(), 5.into()),
}; };
// when // when
@ -123,7 +207,7 @@ mod tests {
// given // given
let request = helpers::ConfirmationRequest { let request = helpers::ConfirmationRequest {
id: 15.into(), id: 15.into(),
payload: helpers::ConfirmationPayload::Transaction(helpers::FilledTransactionRequest { payload: helpers::ConfirmationPayload::SendTransaction(helpers::FilledTransactionRequest {
from: 0.into(), from: 0.into(),
to: None, to: None,
gas: 15_000.into(), gas: 15_000.into(),

View File

@ -38,7 +38,7 @@ pub use self::bytes::Bytes;
pub use self::block::{RichBlock, Block, BlockTransactions}; pub use self::block::{RichBlock, Block, BlockTransactions};
pub use self::block_number::BlockNumber; pub use self::block_number::BlockNumber;
pub use self::call_request::CallRequest; pub use self::call_request::CallRequest;
pub use self::confirmations::{ConfirmationPayload, ConfirmationRequest, TransactionModification}; pub use self::confirmations::{ConfirmationPayload, ConfirmationRequest, ConfirmationResponse, TransactionModification, SignRequest, DecryptRequest, Either};
pub use self::filter::{Filter, FilterChanges}; pub use self::filter::{Filter, FilterChanges};
pub use self::hash::{H64, H160, H256, H512, H520, H2048}; pub use self::hash::{H64, H160, H256, H512, H520, H2048};
pub use self::index::Index; pub use self::index::Index;