From 9fb56235699da1557e9247c1dfe6483ebee31e8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 3 Aug 2016 10:36:54 +0200 Subject: [PATCH] Supporting eth_sign in Signer (#1787) * Making ConfirmationsQueue a bit more generic [WiP] * Generalizing cofirmations * New confirmations types - tests * Separating transaction type in queue. Closes #1310 * Handling sign requests * Speeding up tests * Renaming methods * eth_postSign * Bumping ui --- Cargo.lock | 10 +- rpc/src/v1/helpers/mod.rs | 2 +- rpc/src/v1/helpers/requests.rs | 58 ++++++-- rpc/src/v1/helpers/signing_queue.rs | 109 ++++++++------- rpc/src/v1/impls/eth_signing.rs | 79 +++++++---- rpc/src/v1/impls/ethcore.rs | 8 +- rpc/src/v1/impls/mod.rs | 21 ++- rpc/src/v1/impls/personal.rs | 3 +- rpc/src/v1/impls/personal_signer.rs | 42 +++--- rpc/src/v1/tests/mocked/eth_signing.rs | 78 ++++++++++- rpc/src/v1/tests/mocked/personal_signer.rs | 84 +++++++----- rpc/src/v1/traits/eth.rs | 11 +- rpc/src/v1/traits/personal.rs | 20 +-- rpc/src/v1/types/confirmations.rs | 150 +++++++++++++++++++++ rpc/src/v1/types/mod.rs.in | 8 +- rpc/src/v1/types/transaction_request.rs | 74 +++------- 16 files changed, 538 insertions(+), 219 deletions(-) create mode 100644 rpc/src/v1/types/confirmations.rs diff --git a/Cargo.lock b/Cargo.lock index a346f1ab1..eb147e6c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -924,7 +924,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "parity-dapps" version = "0.6.0" -source = "git+https://github.com/ethcore/parity-ui.git#7120546d08d4d9eb648e255c04935002223d362f" +source = "git+https://github.com/ethcore/parity-ui.git#697e860dedc45003909602a002e7743478ab173a" dependencies = [ "aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -938,7 +938,7 @@ dependencies = [ [[package]] name = "parity-dapps-home" version = "0.6.0" -source = "git+https://github.com/ethcore/parity-ui.git#7120546d08d4d9eb648e255c04935002223d362f" +source = "git+https://github.com/ethcore/parity-ui.git#697e860dedc45003909602a002e7743478ab173a" dependencies = [ "parity-dapps 0.6.0 (git+https://github.com/ethcore/parity-ui.git)", ] @@ -946,7 +946,7 @@ dependencies = [ [[package]] name = "parity-dapps-signer" version = "0.6.0" -source = "git+https://github.com/ethcore/parity-ui.git#7120546d08d4d9eb648e255c04935002223d362f" +source = "git+https://github.com/ethcore/parity-ui.git#697e860dedc45003909602a002e7743478ab173a" dependencies = [ "parity-dapps 0.6.0 (git+https://github.com/ethcore/parity-ui.git)", ] @@ -954,7 +954,7 @@ dependencies = [ [[package]] name = "parity-dapps-status" version = "0.6.0" -source = "git+https://github.com/ethcore/parity-ui.git#7120546d08d4d9eb648e255c04935002223d362f" +source = "git+https://github.com/ethcore/parity-ui.git#697e860dedc45003909602a002e7743478ab173a" dependencies = [ "parity-dapps 0.6.0 (git+https://github.com/ethcore/parity-ui.git)", ] @@ -962,7 +962,7 @@ dependencies = [ [[package]] name = "parity-dapps-wallet" version = "0.6.0" -source = "git+https://github.com/ethcore/parity-ui.git#7120546d08d4d9eb648e255c04935002223d362f" +source = "git+https://github.com/ethcore/parity-ui.git#697e860dedc45003909602a002e7743478ab173a" dependencies = [ "parity-dapps 0.6.0 (git+https://github.com/ethcore/parity-ui.git)", ] diff --git a/rpc/src/v1/helpers/mod.rs b/rpc/src/v1/helpers/mod.rs index e38a77a79..4d3bd1207 100644 --- a/rpc/src/v1/helpers/mod.rs +++ b/rpc/src/v1/helpers/mod.rs @@ -21,5 +21,5 @@ mod signing_queue; pub use self::poll_manager::PollManager; pub use self::poll_filter::PollFilter; -pub use self::requests::{TransactionRequest, TransactionConfirmation, CallRequest}; +pub use self::requests::{TransactionRequest, FilledTransactionRequest, ConfirmationRequest, ConfirmationPayload, CallRequest}; pub use self::signing_queue::{ConfirmationsQueue, ConfirmationPromise, ConfirmationResult, SigningQueue, QueueEvent}; diff --git a/rpc/src/v1/helpers/requests.rs b/rpc/src/v1/helpers/requests.rs index d162774d9..6a30d80bb 100644 --- a/rpc/src/v1/helpers/requests.rs +++ b/rpc/src/v1/helpers/requests.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use util::{Address, U256}; +use util::{Address, U256, Bytes, H256}; /// Transaction request coming from RPC #[derive(Debug, Clone, Default, Eq, PartialEq, Hash)] @@ -30,18 +30,42 @@ pub struct TransactionRequest { /// Value of transaction in wei pub value: Option, /// Additional data sent with transaction - pub data: Option>, + pub data: Option, /// Transaction's nonce pub nonce: Option, } -/// Transaction confirmation waiting in a queue +/// Transaction request coming from RPC with default values filled in. #[derive(Debug, Clone, Default, Eq, PartialEq, Hash)] -pub struct TransactionConfirmation { - /// Id of this confirmation - pub id: U256, - /// TransactionRequest - pub transaction: TransactionRequest, +pub struct FilledTransactionRequest { + /// Sender + pub from: Address, + /// Recipient + pub to: Option
, + /// Gas Price + pub gas_price: U256, + /// Gas + pub gas: U256, + /// Value of transaction in wei + pub value: U256, + /// Additional data sent with transaction + pub data: Bytes, + /// Transaction's nonce + pub nonce: Option, +} + +impl From for TransactionRequest { + fn from(r: FilledTransactionRequest) -> Self { + TransactionRequest { + from: r.from, + to: r.to, + gas_price: Some(r.gas_price), + gas: Some(r.gas), + value: Some(r.value), + data: Some(r.data), + nonce: r.nonce, + } + } } /// Call request @@ -62,3 +86,21 @@ pub struct CallRequest { /// Nonce pub nonce: Option, } + +/// Confirmation object +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct ConfirmationRequest { + /// Id of this confirmation + pub id: U256, + /// Payload to confirm + pub payload: ConfirmationPayload, +} + +/// Payload to confirm in Trusted Signer +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum ConfirmationPayload { + /// Transaction + Transaction(FilledTransactionRequest), + /// Sign request + Sign(Address, H256), +} diff --git a/rpc/src/v1/helpers/signing_queue.rs b/rpc/src/v1/helpers/signing_queue.rs index 96c546052..616d35364 100644 --- a/rpc/src/v1/helpers/signing_queue.rs +++ b/rpc/src/v1/helpers/signing_queue.rs @@ -17,10 +17,10 @@ use std::thread; use std::time::{Instant, Duration}; use std::sync::{mpsc, Arc}; -use std::collections::HashMap; +use std::collections::BTreeMap; use jsonrpc_core; use util::{Mutex, RwLock, U256}; -use v1::helpers::{TransactionRequest, TransactionConfirmation}; +use v1::helpers::{ConfirmationRequest, ConfirmationPayload}; /// Result that can be returned from JSON RPC. pub type RpcResult = Result; @@ -54,41 +54,41 @@ pub type QueueEventReceiver = mpsc::Receiver; pub trait SigningQueue: Send + Sync { /// Add new request to the queue. /// Returns a `ConfirmationPromise` that can be used to await for resolution of given request. - fn add_request(&self, transaction: TransactionRequest) -> ConfirmationPromise; + fn add_request(&self, request: ConfirmationPayload) -> ConfirmationPromise; /// Removes a request from the queue. - /// Notifies possible token holders that transaction was rejected. - fn request_rejected(&self, id: U256) -> Option; + /// Notifies possible token holders that request was rejected. + fn request_rejected(&self, id: U256) -> Option; /// Removes a request from the queue. - /// Notifies possible token holders that transaction was confirmed and given hash was assigned. - fn request_confirmed(&self, id: U256, result: RpcResult) -> Option; + /// Notifies possible token holders that request was confirmed and given hash was assigned. + fn request_confirmed(&self, id: U256, result: RpcResult) -> Option; /// Returns a request if it is contained in the queue. - fn peek(&self, id: &U256) -> Option; + fn peek(&self, id: &U256) -> Option; /// Return copy of all the requests in the queue. - fn requests(&self) -> Vec; + fn requests(&self) -> Vec; - /// Returns number of transactions awaiting confirmation. + /// Returns number of requests awaiting confirmation. fn len(&self) -> usize; - /// Returns true if there are no transactions awaiting confirmation. + /// Returns true if there are no requests awaiting confirmation. fn is_empty(&self) -> bool; } #[derive(Debug, Clone, PartialEq)] -/// Result of a pending transaction. +/// Result of a pending confirmation request. pub enum ConfirmationResult { - /// The transaction has not yet been confirmed nor rejected. + /// The request has not yet been confirmed nor rejected. Waiting, - /// The transaction has been rejected. + /// The request has been rejected. Rejected, - /// The transaction has been confirmed. + /// The request has been confirmed. Confirmed(RpcResult), } -/// Time you need to confirm the transaction in UI. +/// Time you need to confirm the request in UI. /// This is the amount of time token holder will wait before /// returning `None`. /// Unless we have a multi-threaded RPC this will lock @@ -100,12 +100,14 @@ const QUEUE_TIMEOUT_DURATION_SEC : u64 = 20; pub struct ConfirmationToken { result: Arc>, handle: thread::Thread, - request: TransactionConfirmation, + request: ConfirmationRequest, + timeout: Duration, } pub struct ConfirmationPromise { id: U256, result: Arc>, + timeout: Duration, } impl ConfirmationToken { @@ -121,6 +123,7 @@ impl ConfirmationToken { ConfirmationPromise { id: self.request.id, result: self.result.clone(), + timeout: self.timeout, } } } @@ -134,8 +137,7 @@ impl ConfirmationPromise { /// Returns `None` if transaction was rejected or timeout reached. /// Returns `Some(result)` if transaction was confirmed. pub fn wait_with_timeout(&self) -> Option { - let timeout = Duration::from_secs(QUEUE_TIMEOUT_DURATION_SEC); - let res = self.wait_until(Instant::now() + timeout); + let res = self.wait_until(Instant::now() + self.timeout); match res { ConfirmationResult::Confirmed(h) => Some(h), ConfirmationResult::Rejected | ConfirmationResult::Waiting => None, @@ -146,16 +148,16 @@ impl ConfirmationPromise { pub fn result(&self) -> ConfirmationResult { self.wait_until(Instant::now()) } /// Blocks current thread and awaits for - /// resolution of the transaction (rejected / confirmed) - /// Returns `None` if transaction was rejected or timeout reached. - /// Returns `Some(result)` if transaction was confirmed. + /// resolution of the request (rejected / confirmed) + /// Returns `None` if request was rejected or timeout reached. + /// Returns `Some(result)` if request was confirmed. pub fn wait_until(&self, deadline: Instant) -> ConfirmationResult { - trace!(target: "own_tx", "Signer: Awaiting transaction confirmation... ({:?}).", self.id); + trace!(target: "own_tx", "Signer: Awaiting confirmation... ({:?}).", self.id); loop { let now = Instant::now(); // Check the result... match *self.result.lock() { - // Waiting and deadline not yet passed continue looping. + // Waiting and deadline not yet passed continue looping. ConfirmationResult::Waiting if now < deadline => {} // Anything else - return. ref a => return a.clone(), @@ -166,12 +168,13 @@ impl ConfirmationPromise { } } -/// Queue for all unconfirmed transactions. +/// Queue for all unconfirmed requests. pub struct ConfirmationsQueue { id: Mutex, - queue: RwLock>, + queue: RwLock>, sender: Mutex>, receiver: Mutex>>, + timeout: Duration, } impl Default for ConfirmationsQueue { @@ -180,14 +183,23 @@ impl Default for ConfirmationsQueue { ConfirmationsQueue { id: Mutex::new(U256::from(0)), - queue: RwLock::new(HashMap::new()), + queue: RwLock::new(BTreeMap::new()), sender: Mutex::new(send), receiver: Mutex::new(Some(recv)), + timeout: Duration::from_secs(QUEUE_TIMEOUT_DURATION_SEC), } } } impl ConfirmationsQueue { + #[cfg(test)] + /// Creates new confirmations queue with specified timeout + pub fn with_timeout(timeout: Duration) -> Self { + let mut queue = Self::default(); + queue.timeout = timeout; + queue + } + /// Blocks the thread and starts listening for notifications regarding all actions in the queue. /// For each event, `listener` callback will be invoked. /// This method can be used only once (only single consumer of events can exist). @@ -221,9 +233,9 @@ impl ConfirmationsQueue { let _ = self.sender.lock().send(message); } - /// Removes transaction from this queue and notifies `ConfirmationPromise` holders about the result. + /// Removes requests from this queue and notifies `ConfirmationPromise` holders about the result. /// Notifies also a receiver about that event. - fn remove(&self, id: U256, result: Option) -> Option { + fn remove(&self, id: U256, result: Option) -> Option { let token = self.queue.write().remove(&id); if let Some(token) = token { @@ -248,7 +260,7 @@ impl Drop for ConfirmationsQueue { } impl SigningQueue for ConfirmationsQueue { - fn add_request(&self, transaction: TransactionRequest) -> ConfirmationPromise { + fn add_request(&self, request: ConfirmationPayload) -> ConfirmationPromise { // Increment id let id = { let mut last_id = self.id.lock(); @@ -257,16 +269,19 @@ impl SigningQueue for ConfirmationsQueue { }; // Add request to queue let res = { + debug!(target: "own_tx", "Signer: New entry ({:?}) in confirmation queue.", id); + trace!(target: "own_tx", "Signer: ({:?}) : {:?}", id, request); + let mut queue = self.queue.write(); queue.insert(id, ConfirmationToken { result: Arc::new(Mutex::new(ConfirmationResult::Waiting)), handle: thread::current(), - request: TransactionConfirmation { + request: ConfirmationRequest { id: id, - transaction: transaction, + payload: request, }, + timeout: self.timeout, }); - debug!(target: "own_tx", "Signer: New transaction ({:?}) in confirmation queue.", id); queue.get(&id).map(|token| token.as_promise()).expect("Token was just inserted.") }; // Notify listeners @@ -275,21 +290,21 @@ impl SigningQueue for ConfirmationsQueue { } - fn peek(&self, id: &U256) -> Option { + fn peek(&self, id: &U256) -> Option { self.queue.read().get(id).map(|token| token.request.clone()) } - fn request_rejected(&self, id: U256) -> Option { - debug!(target: "own_tx", "Signer: Transaction rejected ({:?}).", id); + fn request_rejected(&self, id: U256) -> Option { + debug!(target: "own_tx", "Signer: Request rejected ({:?}).", id); self.remove(id, None) } - fn request_confirmed(&self, id: U256, result: RpcResult) -> Option { + fn request_confirmed(&self, id: U256, result: RpcResult) -> Option { debug!(target: "own_tx", "Signer: Transaction confirmed ({:?}).", id); self.remove(id, Some(result)) } - fn requests(&self) -> Vec { + fn requests(&self) -> Vec { let queue = self.queue.read(); queue.values().map(|token| token.request.clone()).collect() } @@ -312,20 +327,20 @@ mod test { use std::thread; use std::sync::Arc; use util::{Address, U256, H256, Mutex}; - use v1::helpers::{SigningQueue, ConfirmationsQueue, QueueEvent, TransactionRequest}; + use v1::helpers::{SigningQueue, ConfirmationsQueue, QueueEvent, FilledTransactionRequest, ConfirmationPayload}; use v1::types::H256 as NH256; use jsonrpc_core::to_value; - fn request() -> TransactionRequest { - TransactionRequest { + fn request() -> ConfirmationPayload { + ConfirmationPayload::Transaction(FilledTransactionRequest { from: Address::from(1), to: Some(Address::from(2)), - gas_price: None, - gas: None, - value: Some(U256::from(10_000_000)), - data: None, + gas_price: 0.into(), + gas: 10_000.into(), + value: 10_000_000.into(), + data: vec![], nonce: None, - } + }) } #[test] @@ -391,6 +406,6 @@ mod test { assert_eq!(all.len(), 1); let el = all.get(0).unwrap(); assert_eq!(el.id, U256::from(1)); - assert_eq!(el.transaction, request); + assert_eq!(el.payload, request); } } diff --git a/rpc/src/v1/impls/eth_signing.rs b/rpc/src/v1/impls/eth_signing.rs index 61817c89f..c4eb187fe 100644 --- a/rpc/src/v1/impls/eth_signing.rs +++ b/rpc/src/v1/impls/eth_signing.rs @@ -23,24 +23,21 @@ use ethcore::client::MiningBlockChainClient; use util::{U256, Address, H256, Mutex}; use transient_hashmap::TransientHashMap; use ethcore::account_provider::AccountProvider; -use v1::helpers::{SigningQueue, ConfirmationPromise, ConfirmationResult, ConfirmationsQueue, TransactionRequest as TRequest}; +use v1::helpers::{SigningQueue, ConfirmationPromise, ConfirmationResult, ConfirmationsQueue, ConfirmationPayload, TransactionRequest as TRequest, FilledTransactionRequest as FilledRequest}; use v1::traits::EthSigning; use v1::types::{TransactionRequest, H160 as RpcH160, H256 as RpcH256, H520 as RpcH520, U256 as RpcU256}; -use v1::impls::{default_gas_price, sign_and_dispatch, transaction_rejected_error}; +use v1::impls::{default_gas_price, sign_and_dispatch, transaction_rejected_error, signer_disabled_error}; -fn fill_optional_fields(request: &mut TRequest, client: &C, miner: &M) +fn fill_optional_fields(request: TRequest, client: &C, miner: &M) -> FilledRequest where C: MiningBlockChainClient, M: MinerService { - if request.value.is_none() { - request.value = Some(U256::from(0)); - } - if request.gas.is_none() { - request.gas = Some(miner.sensible_gas_limit()); - } - if request.gas_price.is_none() { - request.gas_price = Some(default_gas_price(client, miner)); - } - if request.data.is_none() { - request.data = Some(Vec::new()); + FilledRequest { + from: request.from, + to: request.to, + nonce: request.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()), + data: request.data.unwrap_or_else(Vec::new), } } @@ -74,10 +71,26 @@ impl EthSigningQueueClient where C: MiningBlockChainClient, M: Miner Ok(()) } - fn dispatch Result>(&self, params: Params, f: F) -> Result { + fn dispatch_sign Result>(&self, params: Params, f: F) -> Result { + from_params::<(RpcH160, RpcH256)>(params).and_then(|(address, msg)| { + let address: Address = address.into(); + let msg: H256 = msg.into(); + + let accounts = take_weak!(self.accounts); + if accounts.is_unlocked(address) { + return to_value(&accounts.sign(address, msg).ok().map_or_else(RpcH520::default, Into::into)); + } + + let queue = take_weak!(self.queue); + let promise = queue.add_request(ConfirmationPayload::Sign(address, msg)); + f(promise) + }) + } + + fn dispatch_transaction Result>(&self, params: Params, f: F) -> Result { from_params::<(TransactionRequest, )>(params) .and_then(|(request, )| { - let mut request: TRequest = request.into(); + let request: TRequest = request.into(); let accounts = take_weak!(self.accounts); let (client, miner) = (take_weak!(self.client), take_weak!(self.miner)); @@ -87,8 +100,8 @@ impl EthSigningQueueClient where C: MiningBlockChainClient, M: Miner } let queue = take_weak!(self.queue); - fill_optional_fields(&mut request, &*client, &*miner); - let promise = queue.add_request(request); + let request = fill_optional_fields(request, &*client, &*miner); + let promise = queue.add_request(ConfirmationPayload::Transaction(request)); f(promise) }) } @@ -98,23 +111,32 @@ impl EthSigning for EthSigningQueueClient where C: MiningBlockChainClient + 'static, M: MinerService + 'static { - fn sign(&self, _params: Params) -> Result { + fn sign(&self, params: Params) -> Result { try!(self.active()); - warn!("Invoking eth_sign is not yet supported with signer enabled."); - // TODO [ToDr] Implement sign when rest of the signing queue is ready. - rpc_unimplemented!() + self.dispatch_sign(params, |promise| { + promise.wait_with_timeout().unwrap_or_else(|| to_value(&RpcH520::default())) + }) + } + + fn post_sign(&self, params: Params) -> Result { + try!(self.active()); + self.dispatch_sign(params, |promise| { + let id = promise.id(); + self.pending.lock().insert(id, promise); + to_value(&RpcU256::from(id)) + }) } fn send_transaction(&self, params: Params) -> Result { try!(self.active()); - self.dispatch(params, |promise| { + self.dispatch_transaction(params, |promise| { promise.wait_with_timeout().unwrap_or_else(|| to_value(&RpcH256::default())) }) } fn post_transaction(&self, params: Params) -> Result { try!(self.active()); - self.dispatch(params, |promise| { + self.dispatch_transaction(params, |promise| { let id = promise.id(); self.pending.lock().insert(id, promise); to_value(&RpcU256::from(id)) @@ -193,13 +215,18 @@ impl EthSigning for EthSigningUnsafeClient where }) } + fn post_sign(&self, _: Params) -> Result { + // We don't support this in non-signer mode. + Err(signer_disabled_error()) + } + fn post_transaction(&self, _: Params) -> Result { // We don't support this in non-signer mode. - Err(Error::invalid_params()) + Err(signer_disabled_error()) } fn check_transaction(&self, _: Params) -> Result { // We don't support this in non-signer mode. - Err(Error::invalid_params()) + Err(signer_disabled_error()) } } diff --git a/rpc/src/v1/impls/ethcore.rs b/rpc/src/v1/impls/ethcore.rs index b62e21b0c..acbb72b3d 100644 --- a/rpc/src/v1/impls/ethcore.rs +++ b/rpc/src/v1/impls/ethcore.rs @@ -27,7 +27,7 @@ use ethcore::miner::MinerService; use v1::traits::Ethcore; use v1::types::{Bytes, U256}; use v1::helpers::{SigningQueue, ConfirmationsQueue}; -use v1::impls::error_codes; +use v1::impls::signer_disabled_error; /// Ethcore implementation. pub struct EthcoreClient where @@ -152,11 +152,7 @@ impl Ethcore for EthcoreClient where M: MinerService + 'static, C: M fn unsigned_transactions_count(&self, _params: Params) -> Result { try!(self.active()); match self.confirmations_queue { - None => Err(Error { - code: ErrorCode::ServerError(error_codes::SIGNER_DISABLED), - message: "Trusted Signer is disabled. This API is not available.".into(), - data: None - }), + None => Err(signer_disabled_error()), Some(ref queue) => to_value(&queue.len()), } } diff --git a/rpc/src/v1/impls/mod.rs b/rpc/src/v1/impls/mod.rs index 54136cfea..8240e8c0c 100644 --- a/rpc/src/v1/impls/mod.rs +++ b/rpc/src/v1/impls/mod.rs @@ -54,7 +54,7 @@ pub use self::traces::TracesClient; pub use self::rpc::RpcClient; use v1::helpers::TransactionRequest; -use v1::types::H256 as NH256; +use v1::types::{H256 as RpcH256, H520 as RpcH520}; use ethcore::error::Error as EthcoreError; use ethcore::miner::MinerService; use ethcore::client::MiningBlockChainClient; @@ -80,7 +80,7 @@ mod error_codes { fn dispatch_transaction(client: &C, miner: &M, signed_transaction: SignedTransaction) -> Result where C: MiningBlockChainClient, M: MinerService { - let hash = NH256::from(signed_transaction.hash()); + let hash = RpcH256::from(signed_transaction.hash()); let import = miner.import_own_transaction(client, signed_transaction); @@ -89,6 +89,12 @@ fn dispatch_transaction(client: &C, miner: &M, signed_transaction: SignedT .and_then(|_| to_value(&hash)) } +fn signature_with_password(accounts: &AccountProvider, address: Address, hash: H256, pass: String) -> Result { + accounts.sign_with_password(address, pass, hash) + .map_err(password_error) + .and_then(|hash| to_value(&RpcH520::from(hash))) +} + fn prepare_transaction(client: &C, miner: &M, request: TransactionRequest) -> Transaction where C: MiningBlockChainClient, M: MinerService { Transaction { nonce: request.nonce @@ -105,9 +111,10 @@ fn prepare_transaction(client: &C, miner: &M, request: TransactionRequest) } } -fn unlock_sign_and_dispatch(client: &C, miner: &M, request: TransactionRequest, account_provider: &AccountProvider, address: Address, password: String) -> Result +fn unlock_sign_and_dispatch(client: &C, miner: &M, request: TransactionRequest, account_provider: &AccountProvider, password: String) -> Result where C: MiningBlockChainClient, M: MinerService { + let address = request.from; let signed_transaction = { let t = prepare_transaction(client, miner, request); let hash = t.hash(); @@ -140,6 +147,14 @@ fn default_gas_price(client: &C, miner: &M) -> U256 where C: MiningBlockCh .unwrap_or_else(|_| miner.sensible_gas_price()) } +fn signer_disabled_error() -> Error { + Error { + code: ErrorCode::ServerError(error_codes::SIGNER_DISABLED), + message: "Trusted Signer is disabled. This API is not available.".into(), + data: None + } +} + fn signing_error(error: AccountError) -> Error { Error { code: ErrorCode::ServerError(error_codes::ACCOUNT_LOCKED), diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 15624c7d3..eacb6525a 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -105,10 +105,9 @@ impl Personal for PersonalClient where C: MiningBl from_params::<(TransactionRequest, String)>(params) .and_then(|(request, password)| { let request: TRequest = request.into(); - let sender = request.from; let accounts = take_weak!(self.accounts); - unlock_sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), request, &*accounts, sender, password) + unlock_sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), request, &*accounts, password) }) } diff --git a/rpc/src/v1/impls/personal_signer.rs b/rpc/src/v1/impls/personal_signer.rs index 823e20577..2dfce57af 100644 --- a/rpc/src/v1/impls/personal_signer.rs +++ b/rpc/src/v1/impls/personal_signer.rs @@ -22,9 +22,9 @@ use ethcore::account_provider::AccountProvider; use ethcore::client::MiningBlockChainClient; use ethcore::miner::MinerService; use v1::traits::PersonalSigner; -use v1::types::{TransactionModification, TransactionConfirmation, U256}; -use v1::impls::unlock_sign_and_dispatch; -use v1::helpers::{SigningQueue, ConfirmationsQueue}; +use v1::types::{TransactionModification, ConfirmationRequest, U256}; +use v1::impls::{unlock_sign_and_dispatch, signature_with_password}; +use v1::helpers::{SigningQueue, ConfirmationsQueue, ConfirmationPayload}; /// Transactions confirmation (personal) rpc implementation. pub struct SignerClient where C: MiningBlockChainClient, M: MinerService { @@ -55,14 +55,16 @@ impl SignerClient where C: MiningBlockChainClient, impl PersonalSigner for SignerClient where C: MiningBlockChainClient, M: MinerService { - fn transactions_to_confirm(&self, _params: Params) -> Result { + fn requests_to_confirm(&self, _params: Params) -> Result { try!(self.active()); let queue = take_weak!(self.queue); - to_value(&queue.requests().into_iter().map(From::from).collect::>()) + to_value(&queue.requests().into_iter().map(From::from).collect::>()) } - fn confirm_transaction(&self, params: Params) -> Result { + fn confirm_request(&self, params: Params) -> Result { try!(self.active()); + // TODO [ToDr] TransactionModification is redundant for some calls + // might be better to replace it in future from_params::<(U256, TransactionModification, String)>(params).and_then( |(id, modification, pass)| { let id = id.into(); @@ -70,17 +72,23 @@ impl PersonalSigner for SignerClient where C: Mini let queue = take_weak!(self.queue); let client = take_weak!(self.client); let miner = take_weak!(self.miner); - queue.peek(&id).map(|confirmation| { - let mut request = confirmation.transaction; - // apply modification - if let Some(gas_price) = modification.gas_price { - request.gas_price = Some(gas_price.into()); - } - let sender = request.from; - let result = unlock_sign_and_dispatch(&*client, &*miner, request, &*accounts, sender, pass); - if let Ok(ref hash) = result { - queue.request_confirmed(id, Ok(hash.clone())); + queue.peek(&id).map(|confirmation| { + let result = match confirmation.payload { + ConfirmationPayload::Transaction(mut request) => { + // apply modification + if let Some(gas_price) = modification.gas_price { + request.gas_price = gas_price.into(); + } + + unlock_sign_and_dispatch(&*client, &*miner, request.into(), &*accounts, pass) + }, + ConfirmationPayload::Sign(address, hash) => { + signature_with_password(&*accounts, address, hash, pass) + } + }; + if let Ok(ref response) = result { + queue.request_confirmed(id, Ok(response.clone())); } result }).unwrap_or_else(|| Err(Error::invalid_params())) @@ -88,7 +96,7 @@ impl PersonalSigner for SignerClient where C: Mini ) } - fn reject_transaction(&self, params: Params) -> Result { + fn reject_request(&self, params: Params) -> Result { try!(self.active()); from_params::<(U256, )>(params).and_then( |(id, )| { diff --git a/rpc/src/v1/tests/mocked/eth_signing.rs b/rpc/src/v1/tests/mocked/eth_signing.rs index 794d5fc93..69a21cab5 100644 --- a/rpc/src/v1/tests/mocked/eth_signing.rs +++ b/rpc/src/v1/tests/mocked/eth_signing.rs @@ -16,13 +16,14 @@ use std::str::FromStr; use std::sync::Arc; +use std::time::Duration; use jsonrpc_core::IoHandler; use v1::impls::EthSigningQueueClient; use v1::traits::EthSigning; use v1::helpers::{ConfirmationsQueue, SigningQueue}; use v1::tests::helpers::TestMinerService; use util::{Address, FixedHash}; -use util::numbers::{Uint, U256}; +use util::numbers::{Uint, U256, H256}; use ethcore::account_provider::AccountProvider; use ethcore::client::TestBlockChainClient; use ethcore::transaction::{Transaction, Action}; @@ -37,7 +38,7 @@ struct EthSigningTester { impl Default for EthSigningTester { fn default() -> Self { - let queue = Arc::new(ConfirmationsQueue::default()); + let queue = Arc::new(ConfirmationsQueue::with_timeout(Duration::from_millis(1))); let client = Arc::new(TestBlockChainClient::default()); let miner = Arc::new(TestMinerService::default()); let accounts = Arc::new(AccountProvider::transient_provider()); @@ -58,6 +59,78 @@ fn eth_signing() -> EthSigningTester { EthSigningTester::default() } +#[test] +fn should_add_sign_to_queue() { + // given + let tester = eth_signing(); + let address = Address::random(); + assert_eq!(tester.queue.requests().len(), 0); + + // when + let request = r#"{ + "jsonrpc": "2.0", + "method": "eth_sign", + "params": [ + ""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", + "0x0000000000000000000000000000000000000000000000000000000000000005" + ], + "id": 1 + }"#; + let response = r#"{"jsonrpc":"2.0","result":"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","id":1}"#; + + // then + assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); + assert_eq!(tester.queue.requests().len(), 1); +} + +#[test] +fn should_post_sign_to_queue() { + // given + let tester = eth_signing(); + let address = Address::random(); + assert_eq!(tester.queue.requests().len(), 0); + + // when + let request = r#"{ + "jsonrpc": "2.0", + "method": "eth_postSign", + "params": [ + ""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", + "0x0000000000000000000000000000000000000000000000000000000000000005" + ], + "id": 1 + }"#; + let response = r#"{"jsonrpc":"2.0","result":"0x01","id":1}"#; + + // then + assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); + assert_eq!(tester.queue.requests().len(), 1); +} + +#[test] +fn should_sign_if_account_is_unlocked() { + // given + let tester = eth_signing(); + let hash: H256 = 5.into(); + let acc = tester.accounts.new_account("test").unwrap(); + tester.accounts.unlock_account_permanently(acc, "test".into()).unwrap(); + + let signature = tester.accounts.sign(acc, hash).unwrap(); + + // when + let request = r#"{ + "jsonrpc": "2.0", + "method": "eth_sign", + "params": [ + ""#.to_owned() + format!("0x{:?}", acc).as_ref() + r#"", + ""# + format!("0x{:?}", hash).as_ref() + r#"" + ], + "id": 1 + }"#; + let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", signature).as_ref() + r#"","id":1}"#; + assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); + assert_eq!(tester.queue.requests().len(), 0); +} #[test] fn should_add_transaction_to_queue() { @@ -81,7 +154,6 @@ fn should_add_transaction_to_queue() { }"#; let response = r#"{"jsonrpc":"2.0","result":"0x0000000000000000000000000000000000000000000000000000000000000000","id":1}"#; - // then assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); assert_eq!(tester.queue.requests().len(), 1); diff --git a/rpc/src/v1/tests/mocked/personal_signer.rs b/rpc/src/v1/tests/mocked/personal_signer.rs index c3a0c070d..b0d3ec735 100644 --- a/rpc/src/v1/tests/mocked/personal_signer.rs +++ b/rpc/src/v1/tests/mocked/personal_signer.rs @@ -23,7 +23,7 @@ use ethcore::client::TestBlockChainClient; use ethcore::transaction::{Transaction, Action}; use v1::{SignerClient, PersonalSigner}; use v1::tests::helpers::TestMinerService; -use v1::helpers::{SigningQueue, ConfirmationsQueue, TransactionRequest}; +use v1::helpers::{SigningQueue, ConfirmationsQueue, FilledTransactionRequest, ConfirmationPayload}; struct PersonalSignerTester { queue: Arc, @@ -68,22 +68,28 @@ fn signer_tester() -> PersonalSignerTester { #[test] -fn should_return_list_of_transactions_in_queue() { +fn should_return_list_of_items_to_confirm() { // given let tester = signer_tester(); - tester.queue.add_request(TransactionRequest { + tester.queue.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest { from: Address::from(1), to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), - gas_price: Some(U256::from(10_000)), - gas: Some(U256::from(10_000_000)), - value: Some(U256::from(1)), - data: None, + gas_price: U256::from(10_000), + gas: U256::from(10_000_000), + value: U256::from(1), + data: vec![], nonce: None, - }); + })); + tester.queue.add_request(ConfirmationPayload::Sign(1.into(), 5.into())); // when - let request = r#"{"jsonrpc":"2.0","method":"personal_transactionsToConfirm","params":[],"id":1}"#; - let response = r#"{"jsonrpc":"2.0","result":[{"id":"0x01","transaction":{"data":null,"from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x01"}}],"id":1}"#; + let request = r#"{"jsonrpc":"2.0","method":"personal_requestsToConfirm","params":[],"id":1}"#; + let response = concat!( + r#"{"jsonrpc":"2.0","result":["#, + r#"{"id":"0x01","payload":{"transaction":{"data":"0x","from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x01"}}},"#, + r#"{"id":"0x02","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","hash":"0x0000000000000000000000000000000000000000000000000000000000000005"}}}"#, + r#"],"id":1}"# + ); // then assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); @@ -94,19 +100,19 @@ fn should_return_list_of_transactions_in_queue() { fn should_reject_transaction_from_queue_without_dispatching() { // given let tester = signer_tester(); - tester.queue.add_request(TransactionRequest { + tester.queue.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest { from: Address::from(1), to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), - gas_price: Some(U256::from(10_000)), - gas: Some(U256::from(10_000_000)), - value: Some(U256::from(1)), - data: None, + gas_price: U256::from(10_000), + gas: U256::from(10_000_000), + value: U256::from(1), + data: vec![], nonce: None, - }); + })); assert_eq!(tester.queue.requests().len(), 1); // when - let request = r#"{"jsonrpc":"2.0","method":"personal_rejectTransaction","params":["0x01"],"id":1}"#; + let request = r#"{"jsonrpc":"2.0","method":"personal_rejectRequest","params":["0x01"],"id":1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; // then @@ -119,19 +125,35 @@ fn should_reject_transaction_from_queue_without_dispatching() { fn should_not_remove_transaction_if_password_is_invalid() { // given let tester = signer_tester(); - tester.queue.add_request(TransactionRequest { + tester.queue.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest { from: Address::from(1), to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), - gas_price: Some(U256::from(10_000)), - gas: Some(U256::from(10_000_000)), - value: Some(U256::from(1)), - data: None, + gas_price: U256::from(10_000), + gas: U256::from(10_000_000), + value: U256::from(1), + data: vec![], nonce: None, - }); + })); assert_eq!(tester.queue.requests().len(), 1); // when - let request = r#"{"jsonrpc":"2.0","method":"personal_confirmTransaction","params":["0x01",{},"xxx"],"id":1}"#; + let request = r#"{"jsonrpc":"2.0","method":"personal_confirmRequest","params":["0x01",{},"xxx"],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","error":{"code":-32021,"message":"Account password is invalid or account does not exist.","data":"SStore(InvalidAccount)"},"id":1}"#; + + // then + assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); + assert_eq!(tester.queue.requests().len(), 1); +} + +#[test] +fn should_not_remove_sign_if_password_is_invalid() { + // given + let tester = signer_tester(); + tester.queue.add_request(ConfirmationPayload::Sign(0.into(), 5.into())); + assert_eq!(tester.queue.requests().len(), 1); + + // when + let request = r#"{"jsonrpc":"2.0","method":"personal_confirmRequest","params":["0x01",{},"xxx"],"id":1}"#; let response = r#"{"jsonrpc":"2.0","error":{"code":-32021,"message":"Account password is invalid or account does not exist.","data":"SStore(InvalidAccount)"},"id":1}"#; // then @@ -145,15 +167,15 @@ fn should_confirm_transaction_and_dispatch() { let tester = signer_tester(); let address = tester.accounts.new_account("test").unwrap(); let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(); - tester.queue.add_request(TransactionRequest { + tester.queue.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest { from: address, to: Some(recipient), - gas_price: Some(U256::from(10_000)), - gas: Some(U256::from(10_000_000)), - value: Some(U256::from(1)), - data: None, + gas_price: U256::from(10_000), + gas: U256::from(10_000_000), + value: U256::from(1), + data: vec![], nonce: None, - }); + })); let t = Transaction { nonce: U256::zero(), @@ -172,7 +194,7 @@ fn should_confirm_transaction_and_dispatch() { // when let request = r#"{ "jsonrpc":"2.0", - "method":"personal_confirmTransaction", + "method":"personal_confirmRequest", "params":["0x01", {"gasPrice":"0x1000"}, "test"], "id":1 }"#; diff --git a/rpc/src/v1/traits/eth.rs b/rpc/src/v1/traits/eth.rs index 60d39d8ba..72dc17962 100644 --- a/rpc/src/v1/traits/eth.rs +++ b/rpc/src/v1/traits/eth.rs @@ -206,26 +206,31 @@ pub trait EthSigning: Sized + Send + Sync + 'static { /// Signs the data with given address signature. fn sign(&self, _: Params) -> Result; + /// Posts sign request asynchronously. + /// Will return a confirmation ID for later use with check_transaction. + fn post_sign(&self, _: Params) -> Result; + /// Sends transaction; will block for 20s to try to return the /// transaction hash. /// If it cannot yet be signed, it will return a transaction ID for - /// later use with check_transaction. + /// later use with check_transaction. fn send_transaction(&self, _: Params) -> Result; /// Posts transaction asynchronously. - /// Will return a transaction ID for later use with check_transaction. + /// Will return a transaction ID for later use with check_transaction. fn post_transaction(&self, _: Params) -> Result; /// Checks the progress of a previously posted transaction. /// Should be given a valid send_transaction ID. /// Returns the transaction hash, the zero hash (not yet available), - /// or an error. + /// or an error. fn check_transaction(&self, _: Params) -> Result; /// Should be used to convert object to io delegate. fn to_delegate(self) -> IoDelegate { let mut delegate = IoDelegate::new(Arc::new(self)); delegate.add_method("eth_sign", EthSigning::sign); + delegate.add_method("eth_postSign", EthSigning::post_sign); delegate.add_method("eth_sendTransaction", EthSigning::send_transaction); delegate.add_method("eth_postTransaction", EthSigning::post_transaction); delegate.add_method("eth_checkTransaction", EthSigning::check_transaction); diff --git a/rpc/src/v1/traits/personal.rs b/rpc/src/v1/traits/personal.rs index 948ae6b26..9c16f692f 100644 --- a/rpc/src/v1/traits/personal.rs +++ b/rpc/src/v1/traits/personal.rs @@ -61,24 +61,24 @@ pub trait Personal: Sized + Send + Sync + 'static { } } -/// Personal extension for transactions confirmations rpc interface. +/// Personal extension for confirmations rpc interface. pub trait PersonalSigner: Sized + Send + Sync + 'static { - /// Returns a list of transactions to confirm. - fn transactions_to_confirm(&self, _: Params) -> Result; + /// Returns a list of items to confirm. + fn requests_to_confirm(&self, _: Params) -> Result; - /// Confirm and send a specific transaction. - fn confirm_transaction(&self, _: Params) -> Result; + /// Confirm specific request. + fn confirm_request(&self, _: Params) -> Result; - /// Reject the transaction request. - fn reject_transaction(&self, _: Params) -> Result; + /// Reject the confirmation request. + fn reject_request(&self, _: Params) -> Result; /// Should be used to convert object to io delegate. fn to_delegate(self) -> IoDelegate { let mut delegate = IoDelegate::new(Arc::new(self)); - delegate.add_method("personal_transactionsToConfirm", PersonalSigner::transactions_to_confirm); - delegate.add_method("personal_confirmTransaction", PersonalSigner::confirm_transaction); - delegate.add_method("personal_rejectTransaction", PersonalSigner::reject_transaction); + delegate.add_method("personal_requestsToConfirm", PersonalSigner::requests_to_confirm); + delegate.add_method("personal_confirmRequest", PersonalSigner::confirm_request); + delegate.add_method("personal_rejectRequest", PersonalSigner::reject_request); delegate } } diff --git a/rpc/src/v1/types/confirmations.rs b/rpc/src/v1/types/confirmations.rs new file mode 100644 index 000000000..e2cea32de --- /dev/null +++ b/rpc/src/v1/types/confirmations.rs @@ -0,0 +1,150 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Types used in Confirmations queue (Trusted Signer) + +use v1::types::{U256, TransactionRequest, H160, H256}; +use v1::helpers; + + +/// Confirmation waiting in a queue +#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)] +pub struct ConfirmationRequest { + /// Id of this confirmation + pub id: U256, + /// Payload + pub payload: ConfirmationPayload, +} + +impl From for ConfirmationRequest { + fn from(c: helpers::ConfirmationRequest) -> Self { + ConfirmationRequest { + id: c.id.into(), + payload: c.payload.into(), + } + } +} + +/// Sign request +#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)] +pub struct SignRequest { + /// Address + pub address: H160, + /// Hash to sign + pub hash: H256, +} + +/// Confirmation payload, i.e. the thing to be confirmed +#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)] +pub enum ConfirmationPayload { + /// Transaction + #[serde(rename="transaction")] + Transaction(TransactionRequest), + /// Signature + #[serde(rename="sign")] + Sign(SignRequest), +} + +impl From for ConfirmationPayload { + fn from(c: helpers::ConfirmationPayload) -> Self { + match c { + helpers::ConfirmationPayload::Transaction(t) => ConfirmationPayload::Transaction(t.into()), + helpers::ConfirmationPayload::Sign(address, hash) => ConfirmationPayload::Sign(SignRequest { + address: address.into(), + hash: hash.into(), + }), + } + } +} + +/// Possible modifications to the confirmed transaction sent by `Trusted Signer` +#[derive(Debug, PartialEq, Deserialize)] +pub struct TransactionModification { + /// Modified gas price + #[serde(rename="gasPrice")] + pub gas_price: Option, +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + use serde_json; + use v1::types::U256; + use v1::helpers; + use super::*; + + #[test] + fn should_serialize_sign_confirmation() { + // given + let request = helpers::ConfirmationRequest { + id: 15.into(), + payload: helpers::ConfirmationPayload::Sign(1.into(), 5.into()), + }; + + // when + let res = serde_json::to_string(&ConfirmationRequest::from(request)); + let expected = r#"{"id":"0x0f","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","hash":"0x0000000000000000000000000000000000000000000000000000000000000005"}}}"#; + + // then + assert_eq!(res.unwrap(), expected.to_owned()); + } + + #[test] + fn should_serialize_transaction_confirmation() { + // given + let request = helpers::ConfirmationRequest { + id: 15.into(), + payload: helpers::ConfirmationPayload::Transaction(helpers::FilledTransactionRequest { + from: 0.into(), + to: None, + gas: 15_000.into(), + gas_price: 10_000.into(), + value: 100_000.into(), + data: vec![1, 2, 3], + nonce: Some(1.into()), + }), + }; + + // when + let res = serde_json::to_string(&ConfirmationRequest::from(request)); + let expected = r#"{"id":"0x0f","payload":{"transaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x0186a0","data":"0x010203","nonce":"0x01"}}}"#; + + // then + assert_eq!(res.unwrap(), expected.to_owned()); + } + + #[test] + fn should_deserialize_modification() { + // given + let s1 = r#"{ + "gasPrice":"0x0ba43b7400" + }"#; + let s2 = r#"{}"#; + + // when + let res1: TransactionModification = serde_json::from_str(s1).unwrap(); + let res2: TransactionModification = serde_json::from_str(s2).unwrap(); + + // then + assert_eq!(res1, TransactionModification { + gas_price: Some(U256::from_str("0ba43b7400").unwrap()), + }); + assert_eq!(res2, TransactionModification { + gas_price: None, + }); + } +} + diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index 75f78906b..f51271123 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -17,6 +17,8 @@ mod bytes; mod block; mod block_number; +mod call_request; +mod confirmations; mod filter; mod hash; mod index; @@ -24,7 +26,6 @@ mod log; mod sync; mod transaction; mod transaction_request; -mod call_request; mod receipt; mod trace; mod trace_filter; @@ -33,14 +34,15 @@ mod uint; pub use self::bytes::Bytes; pub use self::block::{Block, BlockTransactions}; pub use self::block_number::BlockNumber; +pub use self::call_request::CallRequest; +pub use self::confirmations::{ConfirmationPayload, ConfirmationRequest, TransactionModification}; pub use self::filter::Filter; pub use self::hash::{H64, H160, H256, H520, H2048}; pub use self::index::Index; pub use self::log::Log; pub use self::sync::{SyncStatus, SyncInfo}; pub use self::transaction::Transaction; -pub use self::transaction_request::{TransactionRequest, TransactionConfirmation, TransactionModification}; -pub use self::call_request::CallRequest; +pub use self::transaction_request::TransactionRequest; pub use self::receipt::Receipt; pub use self::trace::{LocalizedTrace, TraceResults}; pub use self::trace_filter::TraceFilter; diff --git a/rpc/src/v1/types/transaction_request.rs b/rpc/src/v1/types/transaction_request.rs index 12e2c81fe..b7ee1f47d 100644 --- a/rpc/src/v1/types/transaction_request.rs +++ b/rpc/src/v1/types/transaction_request.rs @@ -17,7 +17,7 @@ //! `TransactionRequest` type use v1::types::{Bytes, H160, U256}; -use v1::helpers::{TransactionRequest as Request, TransactionConfirmation as Confirmation}; +use v1::helpers; /// Transaction request coming from RPC #[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)] @@ -39,8 +39,8 @@ pub struct TransactionRequest { pub nonce: Option, } -impl From for TransactionRequest { - fn from(r: Request) -> Self { +impl From for TransactionRequest { + fn from(r: helpers::TransactionRequest) -> Self { TransactionRequest { from: r.from.into(), to: r.to.map(Into::into), @@ -53,9 +53,23 @@ impl From for TransactionRequest { } } -impl Into for TransactionRequest { - fn into(self) -> Request { - Request { +impl From for TransactionRequest { + fn from(r: helpers::FilledTransactionRequest) -> Self { + TransactionRequest { + from: r.from.into(), + to: r.to.map(Into::into), + gas_price: Some(r.gas_price.into()), + gas: Some(r.gas.into()), + value: Some(r.value.into()), + data: Some(r.data.into()), + nonce: r.nonce.map(Into::into), + } + } +} + +impl Into for TransactionRequest { + fn into(self) -> helpers::TransactionRequest { + helpers::TransactionRequest { from: self.from.into(), to: self.to.map(Into::into), gas_price: self.gas_price.map(Into::into), @@ -67,32 +81,6 @@ impl Into for TransactionRequest { } } -/// Transaction confirmation waiting in a queue -#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize)] -pub struct TransactionConfirmation { - /// Id of this confirmation - pub id: U256, - /// TransactionRequest - pub transaction: TransactionRequest, -} - -impl From for TransactionConfirmation { - fn from(c: Confirmation) -> Self { - TransactionConfirmation { - id: c.id.into(), - transaction: c.transaction.into(), - } - } -} - -/// Possible modifications to the confirmed transaction sent by `SignerUI` -#[derive(Debug, PartialEq, Deserialize)] -pub struct TransactionModification { - /// Modified gas price - #[serde(rename="gasPrice")] - pub gas_price: Option, -} - #[cfg(test)] mod tests { @@ -188,7 +176,6 @@ mod tests { }); } - #[test] fn transaction_request_deserialize_error() { let s = r#"{ @@ -203,26 +190,5 @@ mod tests { assert!(deserialized.is_err(), "Should be error because to is empty"); } - - #[test] - fn should_deserialize_modification() { - // given - let s1 = r#"{ - "gasPrice":"0x0ba43b7400" - }"#; - let s2 = r#"{}"#; - - // when - let res1: TransactionModification = serde_json::from_str(s1).unwrap(); - let res2: TransactionModification = serde_json::from_str(s2).unwrap(); - - // then - assert_eq!(res1, TransactionModification { - gas_price: Some(U256::from_str("0ba43b7400").unwrap()), - }); - assert_eq!(res2, TransactionModification { - gas_price: None, - }); - } }