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

View File

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

View File

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

View File

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

View File

@ -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()));
}

View File

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