diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index 5b25c3124..96c38d931 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -58,7 +58,7 @@ pub trait Dispatcher: Send + Sync + Clone { // type Out: IntoFuture /// Fill optional fields of a transaction request, fetching gas price but not nonce. - fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address) + fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address, force_nonce: bool) -> BoxFuture; /// Sign the given transaction request without dispatching, fetching appropriate nonce. @@ -96,17 +96,30 @@ impl Clone for FullDispatcher { } } +impl FullDispatcher { + fn fill_nonce(nonce: Option, from: &Address, miner: &M, client: &C) -> U256 { + nonce + .or_else(|| miner.last_nonce(from).map(|nonce| nonce + U256::one())) + .unwrap_or_else(|| client.latest_nonce(from)) + } +} + impl Dispatcher for FullDispatcher { - fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address) + fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address, force_nonce: bool) -> BoxFuture { let (client, miner) = (take_weakf!(self.client), take_weakf!(self.miner)); let request = request; + let from = request.from.unwrap_or(default_sender); + let nonce = match force_nonce { + false => request.nonce, + true => Some(Self::fill_nonce(request.nonce, &from, &miner, &client)), + }; future::ok(FilledTransactionRequest { - from: request.from.unwrap_or(default_sender), + from: from, used_default_from: request.from.is_none(), to: request.to, - nonce: request.nonce, + nonce: nonce, gas_price: request.gas_price.unwrap_or_else(|| default_gas_price(&*client, &*miner)), gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()), value: request.value.unwrap_or_else(|| 0.into()), @@ -123,12 +136,7 @@ impl Dispatcher for FullDispatcher BoxFuture { const DEFAULT_GAS_PRICE: U256 = U256([0, 0, 0, 21_000_000]); let gas_limit = self.client.best_block_header().gas_limit(); let request_gas_price = request.gas_price.clone(); + let request_nonce = request.nonce.clone(); + let from = request.from.unwrap_or(default_sender); let with_gas_price = move |gas_price| { let request = request; FilledTransactionRequest { - from: request.from.unwrap_or(default_sender), + from: from.clone(), used_default_from: request.from.is_none(), to: request.to, nonce: request.nonce, @@ -312,7 +322,7 @@ impl Dispatcher for LightDispatcher { }; // fast path for known gas price. - match request_gas_price { + let gas_price = match request_gas_price { Some(gas_price) => future::ok(with_gas_price(gas_price)).boxed(), None => fetch_gas_price_corpus( self.sync.clone(), @@ -323,6 +333,20 @@ impl Dispatcher for LightDispatcher { Some(median) => future::ok(*median), None => future::ok(DEFAULT_GAS_PRICE), // fall back to default on error. }).map(with_gas_price).boxed() + }; + + match (request_nonce, force_nonce) { + (_, false) | (Some(_), true) => gas_price, + (None, true) => { + let next_nonce = self.next_nonce(from); + gas_price.and_then(move |mut filled| next_nonce + .map_err(|_| errors::no_light_peers()) + .map(move |nonce| { + filled.nonce = Some(nonce); + filled + }) + ).boxed() + }, } } @@ -563,12 +587,12 @@ pub fn from_rpc(payload: RpcConfirmationPayload, default_account: Address, di { match payload { RpcConfirmationPayload::SendTransaction(request) => { - dispatcher.fill_optional_fields(request.into(), default_account) + dispatcher.fill_optional_fields(request.into(), default_account, false) .map(ConfirmationPayload::SendTransaction) .boxed() }, RpcConfirmationPayload::SignTransaction(request) => { - dispatcher.fill_optional_fields(request.into(), default_account) + dispatcher.fill_optional_fields(request.into(), default_account, false) .map(ConfirmationPayload::SignTransaction) .boxed() }, diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 26551df45..6e2be5ecc 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -115,7 +115,7 @@ impl Personal for PersonalClient { Err(e) => return future::err(e).boxed(), }; - dispatcher.fill_optional_fields(request.into(), default) + dispatcher.fill_optional_fields(request.into(), default, false) .and_then(move |filled| { let condition = filled.condition.clone().map(Into::into); dispatcher.sign(accounts, filled, SignWith::Password(password)) diff --git a/rpc/src/v1/impls/signing.rs b/rpc/src/v1/impls/signing.rs index 376418ead..ccbaeac78 100644 --- a/rpc/src/v1/impls/signing.rs +++ b/rpc/src/v1/impls/signing.rs @@ -27,7 +27,7 @@ use jsonrpc_core::Error; use v1::helpers::{ errors, oneshot, DefaultAccount, - SIGNING_QUEUE_LIMIT, SigningQueue, ConfirmationPromise, ConfirmationResult, SignerService + SIGNING_QUEUE_LIMIT, SigningQueue, ConfirmationPromise, ConfirmationResult, SignerService, }; use v1::helpers::dispatch::{self, Dispatcher}; use v1::helpers::accounts::unwrap_provider; @@ -137,6 +137,12 @@ impl SigningQueueClient { impl ParitySigning for SigningQueueClient { type Metadata = Metadata; + fn compose_transaction(&self, meta: Metadata, transaction: RpcTransactionRequest) -> BoxFuture { + let accounts = try_bf!(self.account_provider()); + let default_account = accounts.dapp_default_address(meta.dapp_id().into()).ok().unwrap_or_default(); + self.dispatcher.fill_optional_fields(transaction.into(), default_account, true).map(Into::into).boxed() + } + fn post_sign(&self, meta: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture, Error> { let pending = self.pending.clone(); self.dispatch( diff --git a/rpc/src/v1/impls/signing_unsafe.rs b/rpc/src/v1/impls/signing_unsafe.rs index 6fb483c5e..0cb50ac3c 100644 --- a/rpc/src/v1/impls/signing_unsafe.rs +++ b/rpc/src/v1/impls/signing_unsafe.rs @@ -111,6 +111,12 @@ impl EthSigning for SigningUnsafeClient impl ParitySigning for SigningUnsafeClient { type Metadata = Metadata; + fn compose_transaction(&self, meta: Metadata, transaction: RpcTransactionRequest) -> BoxFuture { + let accounts = try_bf!(self.account_provider()); + let default_account = accounts.dapp_default_address(meta.dapp_id().into()).ok().unwrap_or_default(); + self.dispatcher.fill_optional_fields(transaction.into(), default_account, true).map(Into::into).boxed() + } + fn decrypt_message(&self, _: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture { self.handle(RpcConfirmationPayload::Decrypt((address.clone(), data).into()), address.into()) .then(|res| match res { diff --git a/rpc/src/v1/tests/mocked/signing.rs b/rpc/src/v1/tests/mocked/signing.rs index 9307d95b6..6f6380abb 100644 --- a/rpc/src/v1/tests/mocked/signing.rs +++ b/rpc/src/v1/tests/mocked/signing.rs @@ -439,3 +439,29 @@ fn should_add_decryption_to_the_queue() { let res = promise.wait().unwrap(); assert_eq!(res, Some(response.to_owned())); } + +#[test] +fn should_compose_transaction() { + // given + let tester = eth_signing(); + let acc = Random.generate().unwrap(); + assert_eq!(tester.signer.requests().len(), 0); + let from = format!("{:?}", acc.address()); + + // when + let request = r#"{ + "jsonrpc": "2.0", + "method": "parity_composeTransaction", + "params": [{"from":"0x"#.to_owned() + &from + r#"","value":"0x5"}], + "id": 1 + }"#; + + let response = r#"{"jsonrpc":"2.0","result":{"condition":null,"data":"0x","from":"0x"#.to_owned() + + &from + + r#"","gas":"0x5208","gasPrice":"0x4a817c800","nonce":"0x0","to":null,"value":"0x5"},"id":1}"#; + + + // then + let res = tester.io.handle_request(&request).wait().unwrap(); + assert_eq!(res, Some(response.to_owned())); +} diff --git a/rpc/src/v1/traits/parity_signing.rs b/rpc/src/v1/traits/parity_signing.rs index 372c31fb2..344a477c4 100644 --- a/rpc/src/v1/traits/parity_signing.rs +++ b/rpc/src/v1/traits/parity_signing.rs @@ -25,6 +25,11 @@ build_rpc_trait! { pub trait ParitySigning { type Metadata; + /// Given partial transaction request produces transaction with all fields filled in. + /// Such transaction can be then signed externally. + #[rpc(meta, name = "parity_composeTransaction")] + fn compose_transaction(&self, Self::Metadata, TransactionRequest) -> BoxFuture; + /// Posts sign request asynchronously. /// Will return a confirmation ID for later use with check_transaction. #[rpc(meta, name = "parity_postSign")]