Compose transaction RPC. (#5524)
This commit is contained in:
parent
81c449fc99
commit
c39da9643e
@ -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()
|
||||
},
|
||||
|
@ -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))
|
||||
|
@ -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(
|
||||
|
@ -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 {
|
||||
|
@ -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()));
|
||||
}
|
||||
|
@ -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")]
|
||||
|
Loading…
Reference in New Issue
Block a user