Compose transaction RPC. (#5524)

This commit is contained in:
Tomasz Drwięga 2017-05-02 11:39:48 +02:00 committed by Gav Wood
parent 81c449fc99
commit c39da9643e
6 changed files with 84 additions and 17 deletions

View File

@ -58,7 +58,7 @@ pub trait Dispatcher: Send + Sync + Clone {
// type Out<T>: IntoFuture<T, Error> // type Out<T>: IntoFuture<T, Error>
/// Fill optional fields of a transaction request, fetching gas price but not nonce. /// Fill optional fields of a transaction request, fetching gas price but not nonce.
fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address) fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address, force_nonce: bool)
-> BoxFuture<FilledTransactionRequest, Error>; -> BoxFuture<FilledTransactionRequest, Error>;
/// Sign the given transaction request without dispatching, fetching appropriate nonce. /// Sign the given transaction request without dispatching, fetching appropriate nonce.
@ -96,17 +96,30 @@ impl<C, M> Clone for FullDispatcher<C, M> {
} }
} }
impl<C: MiningBlockChainClient, M: MinerService> FullDispatcher<C, M> {
fn fill_nonce(nonce: Option<U256>, from: &Address, miner: &M, client: &C) -> U256 {
nonce
.or_else(|| miner.last_nonce(from).map(|nonce| nonce + U256::one()))
.unwrap_or_else(|| client.latest_nonce(from))
}
}
impl<C: MiningBlockChainClient, M: MinerService> Dispatcher for FullDispatcher<C, M> { impl<C: MiningBlockChainClient, M: MinerService> Dispatcher for FullDispatcher<C, M> {
fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address) fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address, force_nonce: bool)
-> BoxFuture<FilledTransactionRequest, Error> -> BoxFuture<FilledTransactionRequest, Error>
{ {
let (client, miner) = (take_weakf!(self.client), take_weakf!(self.miner)); let (client, miner) = (take_weakf!(self.client), take_weakf!(self.miner));
let request = request; 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 { future::ok(FilledTransactionRequest {
from: request.from.unwrap_or(default_sender), from: from,
used_default_from: request.from.is_none(), used_default_from: request.from.is_none(),
to: request.to, to: request.to,
nonce: request.nonce, nonce: nonce,
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)),
gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()), gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()),
value: request.value.unwrap_or_else(|| 0.into()), value: request.value.unwrap_or_else(|| 0.into()),
@ -123,12 +136,7 @@ impl<C: MiningBlockChainClient, M: MinerService> Dispatcher for FullDispatcher<C
let address = filled.from; let address = filled.from;
future::done({ future::done({
let t = Transaction { let t = Transaction {
nonce: filled.nonce nonce: Self::fill_nonce(filled.nonce, &filled.from, &miner, &client),
.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), action: filled.to.map_or(Action::Create, Action::Call),
gas: filled.gas, gas: filled.gas,
gas_price: filled.gas_price, gas_price: filled.gas_price,
@ -288,18 +296,20 @@ impl LightDispatcher {
} }
impl Dispatcher for LightDispatcher { impl Dispatcher for LightDispatcher {
fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address) fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address, force_nonce: bool)
-> BoxFuture<FilledTransactionRequest, Error> -> BoxFuture<FilledTransactionRequest, Error>
{ {
const DEFAULT_GAS_PRICE: U256 = U256([0, 0, 0, 21_000_000]); const DEFAULT_GAS_PRICE: U256 = U256([0, 0, 0, 21_000_000]);
let gas_limit = self.client.best_block_header().gas_limit(); let gas_limit = self.client.best_block_header().gas_limit();
let request_gas_price = request.gas_price.clone(); 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 with_gas_price = move |gas_price| {
let request = request; let request = request;
FilledTransactionRequest { FilledTransactionRequest {
from: request.from.unwrap_or(default_sender), from: from.clone(),
used_default_from: request.from.is_none(), used_default_from: request.from.is_none(),
to: request.to, to: request.to,
nonce: request.nonce, nonce: request.nonce,
@ -312,7 +322,7 @@ impl Dispatcher for LightDispatcher {
}; };
// fast path for known gas price. // 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(), Some(gas_price) => future::ok(with_gas_price(gas_price)).boxed(),
None => fetch_gas_price_corpus( None => fetch_gas_price_corpus(
self.sync.clone(), self.sync.clone(),
@ -323,6 +333,20 @@ impl Dispatcher for LightDispatcher {
Some(median) => future::ok(*median), Some(median) => future::ok(*median),
None => future::ok(DEFAULT_GAS_PRICE), // fall back to default on error. None => future::ok(DEFAULT_GAS_PRICE), // fall back to default on error.
}).map(with_gas_price).boxed() }).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<D>(payload: RpcConfirmationPayload, default_account: Address, di
{ {
match payload { match payload {
RpcConfirmationPayload::SendTransaction(request) => { RpcConfirmationPayload::SendTransaction(request) => {
dispatcher.fill_optional_fields(request.into(), default_account) dispatcher.fill_optional_fields(request.into(), default_account, false)
.map(ConfirmationPayload::SendTransaction) .map(ConfirmationPayload::SendTransaction)
.boxed() .boxed()
}, },
RpcConfirmationPayload::SignTransaction(request) => { RpcConfirmationPayload::SignTransaction(request) => {
dispatcher.fill_optional_fields(request.into(), default_account) dispatcher.fill_optional_fields(request.into(), default_account, false)
.map(ConfirmationPayload::SignTransaction) .map(ConfirmationPayload::SignTransaction)
.boxed() .boxed()
}, },

View File

@ -115,7 +115,7 @@ impl<D: Dispatcher + 'static> Personal for PersonalClient<D> {
Err(e) => return future::err(e).boxed(), 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| { .and_then(move |filled| {
let condition = filled.condition.clone().map(Into::into); let condition = filled.condition.clone().map(Into::into);
dispatcher.sign(accounts, filled, SignWith::Password(password)) dispatcher.sign(accounts, filled, SignWith::Password(password))

View File

@ -27,7 +27,7 @@ use jsonrpc_core::Error;
use v1::helpers::{ use v1::helpers::{
errors, oneshot, errors, oneshot,
DefaultAccount, DefaultAccount,
SIGNING_QUEUE_LIMIT, SigningQueue, ConfirmationPromise, ConfirmationResult, SignerService SIGNING_QUEUE_LIMIT, SigningQueue, ConfirmationPromise, ConfirmationResult, SignerService,
}; };
use v1::helpers::dispatch::{self, Dispatcher}; use v1::helpers::dispatch::{self, Dispatcher};
use v1::helpers::accounts::unwrap_provider; use v1::helpers::accounts::unwrap_provider;
@ -137,6 +137,12 @@ impl<D: Dispatcher + 'static> SigningQueueClient<D> {
impl<D: Dispatcher + 'static> ParitySigning for SigningQueueClient<D> { impl<D: Dispatcher + 'static> ParitySigning for SigningQueueClient<D> {
type Metadata = Metadata; type Metadata = Metadata;
fn compose_transaction(&self, meta: Metadata, transaction: RpcTransactionRequest) -> BoxFuture<RpcTransactionRequest, Error> {
let accounts = try_bf!(self.account_provider());
let default_account = accounts.dapp_default_address(meta.dapp_id().into()).ok().unwrap_or_default();
self.dispatcher.fill_optional_fields(transaction.into(), default_account, true).map(Into::into).boxed()
}
fn post_sign(&self, meta: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcEither<RpcU256, RpcConfirmationResponse>, Error> { fn post_sign(&self, meta: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcEither<RpcU256, RpcConfirmationResponse>, Error> {
let pending = self.pending.clone(); let pending = self.pending.clone();
self.dispatch( self.dispatch(

View File

@ -111,6 +111,12 @@ impl<D: Dispatcher + 'static> EthSigning for SigningUnsafeClient<D>
impl<D: Dispatcher + 'static> ParitySigning for SigningUnsafeClient<D> { impl<D: Dispatcher + 'static> ParitySigning for SigningUnsafeClient<D> {
type Metadata = Metadata; type Metadata = Metadata;
fn compose_transaction(&self, meta: Metadata, transaction: RpcTransactionRequest) -> BoxFuture<RpcTransactionRequest, Error> {
let accounts = try_bf!(self.account_provider());
let default_account = accounts.dapp_default_address(meta.dapp_id().into()).ok().unwrap_or_default();
self.dispatcher.fill_optional_fields(transaction.into(), default_account, true).map(Into::into).boxed()
}
fn decrypt_message(&self, _: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcBytes, Error> { fn decrypt_message(&self, _: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcBytes, Error> {
self.handle(RpcConfirmationPayload::Decrypt((address.clone(), data).into()), address.into()) self.handle(RpcConfirmationPayload::Decrypt((address.clone(), data).into()), address.into())
.then(|res| match res { .then(|res| match res {

View File

@ -439,3 +439,29 @@ fn should_add_decryption_to_the_queue() {
let res = promise.wait().unwrap(); let res = promise.wait().unwrap();
assert_eq!(res, Some(response.to_owned())); 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()));
}

View File

@ -25,6 +25,11 @@ build_rpc_trait! {
pub trait ParitySigning { pub trait ParitySigning {
type Metadata; type Metadata;
/// Given partial transaction request produces transaction with all fields filled in.
/// Such transaction can be then signed externally.
#[rpc(meta, name = "parity_composeTransaction")]
fn compose_transaction(&self, Self::Metadata, TransactionRequest) -> BoxFuture<TransactionRequest, Error>;
/// Posts sign request asynchronously. /// Posts sign request asynchronously.
/// Will return a confirmation ID for later use with check_transaction. /// Will return a confirmation ID for later use with check_transaction.
#[rpc(meta, name = "parity_postSign")] #[rpc(meta, name = "parity_postSign")]