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>
|
// 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()
|
||||||
},
|
},
|
||||||
|
@ -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))
|
||||||
|
@ -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(
|
||||||
|
@ -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 {
|
||||||
|
@ -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()));
|
||||||
|
}
|
||||||
|
@ -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")]
|
||||||
|
Loading…
Reference in New Issue
Block a user