dispatcher abstraction, port most things to it
This commit is contained in:
parent
4bb45c4f64
commit
e73ea80954
@ -472,6 +472,12 @@ impl PendingTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for PendingTransaction {
|
||||
type Target = SignedTransaction;
|
||||
|
||||
fn deref(&self) -> &SignedTransaction { &self.transaction }
|
||||
}
|
||||
|
||||
impl From<SignedTransaction> for PendingTransaction {
|
||||
fn from(t: SignedTransaction) -> Self {
|
||||
PendingTransaction {
|
||||
|
@ -16,6 +16,9 @@
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Weak;
|
||||
|
||||
use futures::{future, Future, BoxFuture};
|
||||
use rlp;
|
||||
use util::{Address, H520, H256, U256, Uint, Bytes};
|
||||
use util::bytes::ToPretty;
|
||||
@ -38,6 +41,118 @@ use v1::types::{
|
||||
DecryptRequest as RpcDecryptRequest,
|
||||
};
|
||||
|
||||
/// Has the capability to dispatch, sign, and decrypt.
|
||||
///
|
||||
/// Requires a clone implementation, with the implication that it be cheap;
|
||||
/// usually just bumping a reference count or two.
|
||||
pub trait Dispatcher: Send + Sync + Clone {
|
||||
// TODO: when ATC exist, use zero-cost
|
||||
// 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)
|
||||
-> BoxFuture<FilledTransactionRequest, Error>;
|
||||
|
||||
/// Sign the given transaction request without dispatching, fetching appropriate nonce.
|
||||
fn sign(&self, accounts: &AccountProvider, filled: FilledTransactionRequest, password: SignWith)
|
||||
-> BoxFuture<WithToken<SignedTransaction>, Error>;
|
||||
|
||||
/// "Dispatch" a local transaction.
|
||||
fn dispatch_transaction(&self, signed_transaction: PendingTransaction) -> Result<H256, Error>;
|
||||
}
|
||||
|
||||
/// A dispatcher which uses references to a client and miner in order to sign
|
||||
/// requests locally.
|
||||
#[derive(Debug)]
|
||||
pub struct FullDispatcher<C, M> {
|
||||
client: Weak<C>,
|
||||
miner: Weak<M>,
|
||||
}
|
||||
|
||||
impl<C, M> FullDispatcher<C, M> {
|
||||
/// Create a `FullDispatcher` from weak references to a client and miner.
|
||||
pub fn new(client: Weak<C>, miner: Weak<M>) -> Self {
|
||||
FullDispatcher {
|
||||
client: client,
|
||||
miner: miner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, M> Clone for FullDispatcher<C, M> {
|
||||
fn clone(&self) -> Self {
|
||||
FullDispatcher {
|
||||
client: self.client.clone(),
|
||||
miner: self.miner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: MiningBlockChainClient, M: MinerService> Dispatcher for FullDispatcher<C, M> {
|
||||
fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address)
|
||||
-> BoxFuture<FilledTransactionRequest, Error>
|
||||
{
|
||||
let inner = move || {
|
||||
let (client, miner) = (take_weak!(self.client), take_weak!(self.miner));
|
||||
Ok(FilledTransactionRequest {
|
||||
from: request.from.unwrap_or(default_sender),
|
||||
used_default_from: request.from.is_none(),
|
||||
to: request.to,
|
||||
nonce: request.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()),
|
||||
data: request.data.unwrap_or_else(Vec::new),
|
||||
condition: request.condition,
|
||||
})
|
||||
};
|
||||
future::done(inner()).boxed()
|
||||
}
|
||||
|
||||
fn sign(&self, accounts: &AccountProvider, filled: FilledTransactionRequest, password: SignWith)
|
||||
-> BoxFuture<WithToken<SignedTransaction>, Error>
|
||||
{
|
||||
let inner = move || {
|
||||
let (client, miner) = (take_weak!(self.client), take_weak!(self.miner));
|
||||
let network_id = client.signing_network_id();
|
||||
let address = filled.from;
|
||||
let signed_transaction = {
|
||||
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)),
|
||||
|
||||
action: filled.to.map_or(Action::Create, Action::Call),
|
||||
gas: filled.gas,
|
||||
gas_price: filled.gas_price,
|
||||
value: filled.value,
|
||||
data: filled.data,
|
||||
};
|
||||
|
||||
let hash = t.hash(network_id);
|
||||
let signature = signature(accounts, address, hash, password)?;
|
||||
signature.map(|sig| {
|
||||
SignedTransaction::new(t.with_signature(sig, network_id))
|
||||
.expect("Transaction was signed by AccountsProvider; it never produces invalid signatures; qed")
|
||||
})
|
||||
};
|
||||
Ok(signed_transaction)
|
||||
};
|
||||
|
||||
future::done(inner()).boxed()
|
||||
}
|
||||
|
||||
fn dispatch_transaction(&self, signed_transaction: PendingTransaction) -> Result<H256, Error> {
|
||||
let hash = signed_transaction.transaction.hash();
|
||||
|
||||
take_weak!(self.miner).import_own_transaction(take_weak!(self.client), signed_transaction)
|
||||
.map_err(errors::from_transaction_error)
|
||||
.map(|_| hash)
|
||||
}
|
||||
}
|
||||
|
||||
pub const DEFAULT_MAC: [u8; 2] = [0, 0];
|
||||
|
||||
type AccountToken = String;
|
||||
@ -83,6 +198,14 @@ impl<T: Debug> WithToken<T> {
|
||||
WithToken::Yes(v, _) => v,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the `WithToken` into a tuple.
|
||||
pub fn into_tuple(self) -> (T, Option<AccountToken>) {
|
||||
match self {
|
||||
WithToken::No(v) => (v, None),
|
||||
WithToken::Yes(v, token) => (v, Some(token))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> From<(T, AccountToken)> for WithToken<T> {
|
||||
@ -91,26 +214,44 @@ impl<T: Debug> From<(T, AccountToken)> for WithToken<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute<C, M>(client: &C, miner: &M, accounts: &AccountProvider, payload: ConfirmationPayload, pass: SignWith) -> Result<WithToken<ConfirmationResponse>, Error>
|
||||
where C: MiningBlockChainClient, M: MinerService
|
||||
{
|
||||
impl<T: Debug> From<(T, Option<AccountToken>)> for WithToken<T> {
|
||||
fn from(tuple: (T, Option<AccountToken>)) -> Self {
|
||||
match tuple.1 {
|
||||
Some(token) => WithToken::Yes(tuple.0, token),
|
||||
None => WithToken::No(tuple.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute<D: Dispatcher>(
|
||||
dispatcher: D,
|
||||
accounts: &AccountProvider,
|
||||
payload: ConfirmationPayload,
|
||||
pass: SignWith
|
||||
) -> BoxFuture<WithToken<ConfirmationResponse>, Error> {
|
||||
match payload {
|
||||
ConfirmationPayload::SendTransaction(request) => {
|
||||
sign_and_dispatch(client, miner, accounts, request, pass)
|
||||
.map(|result| result
|
||||
.map(RpcH256::from)
|
||||
.map(ConfirmationResponse::SendTransaction)
|
||||
)
|
||||
let condition = request.condition.clone();
|
||||
dispatcher.sign(accounts, request, pass)
|
||||
.map(move |v| v.map(move |tx| PendingTransaction::new(tx, condition)))
|
||||
.map(WithToken::into_tuple)
|
||||
.map(|(tx, token)| (tx, token, dispatcher))
|
||||
.and_then(|(tx, tok, dispatcher)| {
|
||||
dispatcher.dispatch_transaction(tx)
|
||||
.map(RpcH256::from)
|
||||
.map(ConfirmationResponse::SendTransaction)
|
||||
.map(move |h| WithToken::from((h, tok)))
|
||||
}).boxed()
|
||||
},
|
||||
ConfirmationPayload::SignTransaction(request) => {
|
||||
sign_no_dispatch(client, miner, accounts, request, pass)
|
||||
dispatcher.sign(accounts, request, pass)
|
||||
.map(|result| result
|
||||
.map(RpcRichRawTransaction::from)
|
||||
.map(ConfirmationResponse::SignTransaction)
|
||||
)
|
||||
).boxed()
|
||||
},
|
||||
ConfirmationPayload::Signature(address, data) => {
|
||||
signature(accounts, address, data.sha3(), pass)
|
||||
let res = signature(accounts, address, data.sha3(), pass)
|
||||
.map(|result| result
|
||||
.map(|rsv| {
|
||||
let mut vrs = [0u8; 65];
|
||||
@ -122,14 +263,16 @@ pub fn execute<C, M>(client: &C, miner: &M, accounts: &AccountProvider, payload:
|
||||
})
|
||||
.map(RpcH520::from)
|
||||
.map(ConfirmationResponse::Signature)
|
||||
)
|
||||
);
|
||||
future::done(res).boxed()
|
||||
},
|
||||
ConfirmationPayload::Decrypt(address, data) => {
|
||||
decrypt(accounts, address, data, pass)
|
||||
let res = decrypt(accounts, address, data, pass)
|
||||
.map(|result| result
|
||||
.map(RpcBytes)
|
||||
.map(ConfirmationResponse::Decrypt)
|
||||
)
|
||||
);
|
||||
future::done(res).boxed()
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -156,105 +299,34 @@ fn decrypt(accounts: &AccountProvider, address: Address, msg: Bytes, password: S
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dispatch_transaction<C, M>(client: &C, miner: &M, signed_transaction: PendingTransaction) -> Result<H256, Error>
|
||||
where C: MiningBlockChainClient, M: MinerService {
|
||||
let hash = signed_transaction.transaction.hash();
|
||||
|
||||
miner.import_own_transaction(client, signed_transaction)
|
||||
.map_err(errors::from_transaction_error)
|
||||
.map(|_| hash)
|
||||
}
|
||||
|
||||
pub fn sign_no_dispatch<C, M>(client: &C, miner: &M, accounts: &AccountProvider, filled: FilledTransactionRequest, password: SignWith) -> Result<WithToken<SignedTransaction>, Error>
|
||||
where C: MiningBlockChainClient, M: MinerService {
|
||||
|
||||
let network_id = client.signing_network_id();
|
||||
let address = filled.from;
|
||||
let signed_transaction = {
|
||||
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)),
|
||||
|
||||
action: filled.to.map_or(Action::Create, Action::Call),
|
||||
gas: filled.gas,
|
||||
gas_price: filled.gas_price,
|
||||
value: filled.value,
|
||||
data: filled.data,
|
||||
};
|
||||
|
||||
let hash = t.hash(network_id);
|
||||
let signature = signature(accounts, address, hash, password)?;
|
||||
signature.map(|sig| {
|
||||
SignedTransaction::new(t.with_signature(sig, network_id))
|
||||
.expect("Transaction was signed by AccountsProvider; it never produces invalid signatures; qed")
|
||||
})
|
||||
};
|
||||
Ok(signed_transaction)
|
||||
}
|
||||
|
||||
pub fn sign_and_dispatch<C, M>(client: &C, miner: &M, accounts: &AccountProvider, filled: FilledTransactionRequest, password: SignWith) -> Result<WithToken<H256>, Error>
|
||||
where C: MiningBlockChainClient, M: MinerService
|
||||
{
|
||||
|
||||
let network_id = client.signing_network_id();
|
||||
let condition = filled.condition.clone();
|
||||
let signed_transaction = sign_no_dispatch(client, miner, accounts, filled, password)?;
|
||||
|
||||
let (signed_transaction, token) = match signed_transaction {
|
||||
WithToken::No(signed_transaction) => (signed_transaction, None),
|
||||
WithToken::Yes(signed_transaction, token) => (signed_transaction, Some(token)),
|
||||
};
|
||||
|
||||
trace!(target: "miner", "send_transaction: dispatching tx: {} for network ID {:?}", rlp::encode(&signed_transaction).to_vec().pretty(), network_id);
|
||||
let pending_transaction = PendingTransaction::new(signed_transaction, condition.map(Into::into));
|
||||
dispatch_transaction(&*client, &*miner, pending_transaction).map(|hash| {
|
||||
match token {
|
||||
Some(ref token) => WithToken::Yes(hash, token.clone()),
|
||||
None => WithToken::No(hash),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fill_optional_fields<C, M>(request: TransactionRequest, default_sender: Address, client: &C, miner: &M) -> FilledTransactionRequest
|
||||
where C: MiningBlockChainClient, M: MinerService
|
||||
{
|
||||
FilledTransactionRequest {
|
||||
from: request.from.unwrap_or(default_sender),
|
||||
used_default_from: request.from.is_none(),
|
||||
to: request.to,
|
||||
nonce: request.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()),
|
||||
data: request.data.unwrap_or_else(Vec::new),
|
||||
condition: request.condition,
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract default gas price from a client and miner.
|
||||
pub fn default_gas_price<C, M>(client: &C, miner: &M) -> U256
|
||||
where C: MiningBlockChainClient, M: MinerService
|
||||
{
|
||||
client.gas_price_median(100).unwrap_or_else(|| miner.sensible_gas_price())
|
||||
}
|
||||
|
||||
pub fn from_rpc<C, M>(payload: RpcConfirmationPayload, default_account: Address, client: &C, miner: &M) -> ConfirmationPayload
|
||||
where C: MiningBlockChainClient, M: MinerService {
|
||||
|
||||
/// Convert RPC confirmation payload to signer confirmation payload.
|
||||
/// May need to resolve in the future to fetch things like gas price.
|
||||
pub fn from_rpc<D>(payload: RpcConfirmationPayload, default_account: Address, dispatcher: &D) -> BoxFuture<ConfirmationPayload, Error>
|
||||
where D: Dispatcher
|
||||
{
|
||||
match payload {
|
||||
RpcConfirmationPayload::SendTransaction(request) => {
|
||||
ConfirmationPayload::SendTransaction(fill_optional_fields(request.into(), default_account, client, miner))
|
||||
dispatcher.fill_optional_fields(request.into(), default_account)
|
||||
.map(ConfirmationPayload::SendTransaction)
|
||||
.boxed()
|
||||
},
|
||||
RpcConfirmationPayload::SignTransaction(request) => {
|
||||
ConfirmationPayload::SignTransaction(fill_optional_fields(request.into(), default_account, client, miner))
|
||||
dispatcher.fill_optional_fields(request.into(), default_account)
|
||||
.map(ConfirmationPayload::SignTransaction)
|
||||
.boxed()
|
||||
},
|
||||
RpcConfirmationPayload::Decrypt(RpcDecryptRequest { address, msg }) => {
|
||||
ConfirmationPayload::Decrypt(address.into(), msg.into())
|
||||
future::ok(ConfirmationPayload::Decrypt(address.into(), msg.into())).boxed()
|
||||
},
|
||||
RpcConfirmationPayload::Signature(RpcSignRequest { address, data }) => {
|
||||
ConfirmationPayload::Signature(address.into(), data.into())
|
||||
future::ok(ConfirmationPayload::Signature(address.into(), data.into())).boxed()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -16,15 +16,6 @@
|
||||
|
||||
//! Ethereum rpc interface implementation.
|
||||
|
||||
macro_rules! take_weak {
|
||||
($weak: expr) => {
|
||||
match $weak.upgrade() {
|
||||
Some(arc) => arc,
|
||||
None => return Err(Error::internal_error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod eth;
|
||||
mod eth_filter;
|
||||
mod net;
|
||||
|
@ -20,46 +20,37 @@ use std::sync::{Arc, Weak};
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use ethcore::client::MiningBlockChainClient;
|
||||
use ethcore::miner::MinerService;
|
||||
use util::{Address, U128, Uint};
|
||||
use ethcore::transaction::PendingTransaction;
|
||||
|
||||
use futures::{self, Future, BoxFuture};
|
||||
use util::{Address, U128, Uint, ToPretty};
|
||||
|
||||
use futures::{self, future, Future, BoxFuture};
|
||||
use jsonrpc_core::Error;
|
||||
use v1::helpers::errors;
|
||||
use v1::helpers::dispatch::{self, sign_and_dispatch};
|
||||
use v1::helpers::dispatch::{Dispatcher, SignWith};
|
||||
use v1::traits::Personal;
|
||||
use v1::types::{H160 as RpcH160, H256 as RpcH256, U128 as RpcU128, TransactionRequest};
|
||||
use v1::metadata::Metadata;
|
||||
|
||||
/// Account management (personal) rpc implementation.
|
||||
pub struct PersonalClient<C, M> where
|
||||
C: MiningBlockChainClient,
|
||||
M: MinerService,
|
||||
{
|
||||
pub struct PersonalClient<D: Dispatcher> {
|
||||
accounts: Weak<AccountProvider>,
|
||||
client: Weak<C>,
|
||||
miner: Weak<M>,
|
||||
dispatcher: D,
|
||||
allow_perm_unlock: bool,
|
||||
}
|
||||
|
||||
impl<C, M> PersonalClient<C, M> where
|
||||
C: MiningBlockChainClient,
|
||||
M: MinerService,
|
||||
{
|
||||
impl<D: Dispatcher> PersonalClient<D> {
|
||||
/// Creates new PersonalClient
|
||||
pub fn new(store: &Arc<AccountProvider>, client: &Arc<C>, miner: &Arc<M>, allow_perm_unlock: bool) -> Self {
|
||||
pub fn new(store: &Arc<AccountProvider>, dispatcher: D, allow_perm_unlock: bool) -> Self {
|
||||
PersonalClient {
|
||||
accounts: Arc::downgrade(store),
|
||||
client: Arc::downgrade(client),
|
||||
miner: Arc::downgrade(miner),
|
||||
dispatcher: dispatcher,
|
||||
allow_perm_unlock: allow_perm_unlock,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, M> Personal for PersonalClient<C, M> where
|
||||
C: MiningBlockChainClient + 'static,
|
||||
M: MinerService + 'static,
|
||||
{
|
||||
impl<D: Dispatcher + 'static> Personal for PersonalClient<D> {
|
||||
type Metadata = Metadata;
|
||||
|
||||
fn accounts(&self) -> Result<Vec<RpcH160>, Error> {
|
||||
@ -106,28 +97,41 @@ impl<C, M> Personal for PersonalClient<C, M> where
|
||||
}
|
||||
|
||||
fn send_transaction(&self, meta: Metadata, request: TransactionRequest, password: String) -> BoxFuture<RpcH256, Error> {
|
||||
let sign_and_send = move || {
|
||||
let client = take_weak!(self.client);
|
||||
let miner = take_weak!(self.miner);
|
||||
let setup = || {
|
||||
let dispatcher = self.dispatcher.clone();
|
||||
let accounts = take_weak!(self.accounts);
|
||||
|
||||
let default_account = match request.from {
|
||||
Some(ref account) => account.clone().into(),
|
||||
None => accounts
|
||||
.default_address(meta.dapp_id.unwrap_or_default().into())
|
||||
.map_err(|e| errors::account("Cannot find default account.", e))?,
|
||||
};
|
||||
|
||||
let request = dispatch::fill_optional_fields(request.into(), default_account, &*client, &*miner);
|
||||
sign_and_dispatch(
|
||||
&*client,
|
||||
&*miner,
|
||||
&*accounts,
|
||||
request,
|
||||
dispatch::SignWith::Password(password)
|
||||
).map(|v| v.into_value().into())
|
||||
Ok((accounts, dispatcher))
|
||||
};
|
||||
|
||||
futures::done(sign_and_send()).boxed()
|
||||
future::done(setup())
|
||||
.and_then(move |(accounts, dispatcher)| {
|
||||
let default = match request.from.as_ref() {
|
||||
Some(account) => Ok(account.clone().into()),
|
||||
None => accounts
|
||||
.default_address(meta.dapp_id.unwrap_or_default().into())
|
||||
.map_err(|e| errors::account("Cannot find default account.", e)),
|
||||
};
|
||||
|
||||
let dis = dispatcher.clone();
|
||||
future::done(default)
|
||||
.and_then(move |default| dis.fill_optional_fields(request.into(), default))
|
||||
.map(move |tx| (tx, accounts, dispatcher))
|
||||
})
|
||||
.and_then(move |(filled, accounts, dispatcher)| {
|
||||
let condition = filled.condition.clone().map(Into::into);
|
||||
dispatcher.sign(&accounts, filled, SignWith::Password(password))
|
||||
.map(|tx| tx.into_value())
|
||||
.map(move |tx| PendingTransaction::new(tx, condition))
|
||||
.map(move |tx| (tx, dispatcher))
|
||||
})
|
||||
.and_then(move |(pending_tx, dispatcher)| {
|
||||
let network_id = pending_tx.network_id();
|
||||
trace!(target: "miner", "send_transaction: dispatching tx: {} for network ID {:?}",
|
||||
::rlp::encode(&*pending_tx).to_vec().pretty(), network_id);
|
||||
|
||||
dispatcher.dispatch_transaction(pending_tx).map(Into::into)
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
@ -24,13 +24,14 @@ use ethcore::account_provider::AccountProvider;
|
||||
use ethcore::miner::MinerService;
|
||||
use ethcore::client::MiningBlockChainClient;
|
||||
|
||||
use futures::{self, BoxFuture, Future};
|
||||
use futures::{self, future, BoxFuture, Future};
|
||||
use jsonrpc_core::Error;
|
||||
use v1::helpers::{
|
||||
errors, dispatch,
|
||||
errors,
|
||||
DefaultAccount,
|
||||
SigningQueue, ConfirmationPromise, ConfirmationResult, ConfirmationPayload, SignerService
|
||||
};
|
||||
use v1::helpers::dispatch::{self, Dispatcher};
|
||||
use v1::metadata::Metadata;
|
||||
use v1::traits::{EthSigning, ParitySigning};
|
||||
use v1::types::{
|
||||
@ -50,105 +51,97 @@ enum DispatchResult {
|
||||
}
|
||||
|
||||
/// Implementation of functions that require signing when no trusted signer is used.
|
||||
pub struct SigningQueueClient<C, M> where C: MiningBlockChainClient, M: MinerService {
|
||||
pub struct SigningQueueClient<D> {
|
||||
signer: Weak<SignerService>,
|
||||
accounts: Weak<AccountProvider>,
|
||||
client: Weak<C>,
|
||||
miner: Weak<M>,
|
||||
|
||||
pending: Mutex<TransientHashMap<U256, ConfirmationPromise>>,
|
||||
dispatcher: D,
|
||||
pending: Arc<Mutex<TransientHashMap<U256, ConfirmationPromise>>>,
|
||||
}
|
||||
|
||||
impl<C, M> SigningQueueClient<C, M> where
|
||||
C: MiningBlockChainClient,
|
||||
M: MinerService,
|
||||
fn handle_dispatch<OnResponse>(res: Result<DispatchResult, Error>, on_response: OnResponse)
|
||||
where OnResponse: FnOnce(Result<RpcConfirmationResponse, Error>) + Send + 'static
|
||||
{
|
||||
match res {
|
||||
Ok(DispatchResult::Value(result)) => on_response(Ok(result)),
|
||||
Ok(DispatchResult::Promise(promise)) => {
|
||||
promise.wait_for_result(move |result| {
|
||||
on_response(result.unwrap_or_else(|| Err(errors::request_rejected())))
|
||||
})
|
||||
},
|
||||
Err(e) => on_response(Err(e)),
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Dispatcher> SigningQueueClient<D> {
|
||||
/// Creates a new signing queue client given shared signing queue.
|
||||
pub fn new(signer: &Arc<SignerService>, client: &Arc<C>, miner: &Arc<M>, accounts: &Arc<AccountProvider>) -> Self {
|
||||
pub fn new(signer: &Arc<SignerService>, dispatcher: D, accounts: &Arc<AccountProvider>) -> Self {
|
||||
SigningQueueClient {
|
||||
signer: Arc::downgrade(signer),
|
||||
accounts: Arc::downgrade(accounts),
|
||||
client: Arc::downgrade(client),
|
||||
miner: Arc::downgrade(miner),
|
||||
pending: Mutex::new(TransientHashMap::new(MAX_PENDING_DURATION)),
|
||||
dispatcher: dispatcher,
|
||||
pending: Arc::new(Mutex::new(TransientHashMap::new(MAX_PENDING_DURATION))),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_dispatch<OnResponse>(&self, res: Result<DispatchResult, Error>, on_response: OnResponse)
|
||||
where OnResponse: FnOnce(Result<RpcConfirmationResponse, Error>) + Send + 'static
|
||||
{
|
||||
match res {
|
||||
Ok(DispatchResult::Value(result)) => on_response(Ok(result)),
|
||||
Ok(DispatchResult::Promise(promise)) => {
|
||||
promise.wait_for_result(move |result| {
|
||||
on_response(result.unwrap_or_else(|| Err(errors::request_rejected())))
|
||||
})
|
||||
},
|
||||
Err(e) => on_response(Err(e)),
|
||||
}
|
||||
}
|
||||
fn dispatch(&self, payload: RpcConfirmationPayload, default_account: DefaultAccount) -> BoxFuture<DispatchResult, Error> {
|
||||
let setup = || {
|
||||
let accounts = take_weak!(self.accounts);
|
||||
let default_account = match default_account {
|
||||
DefaultAccount::Provided(acc) => acc,
|
||||
DefaultAccount::ForDapp(dapp) => accounts.default_address(dapp).ok().unwrap_or_default(),
|
||||
};
|
||||
|
||||
fn add_to_queue(&self, payload: ConfirmationPayload) -> Result<DispatchResult, Error> {
|
||||
let client = take_weak!(self.client);
|
||||
let miner = take_weak!(self.miner);
|
||||
let accounts = take_weak!(self.accounts);
|
||||
|
||||
let sender = payload.sender();
|
||||
if accounts.is_unlocked(sender) {
|
||||
return dispatch::execute(&*client, &*miner, &*accounts, payload, dispatch::SignWith::Nothing)
|
||||
.map(|v| v.into_value())
|
||||
.map(DispatchResult::Value);
|
||||
}
|
||||
|
||||
take_weak!(self.signer).add_request(payload)
|
||||
.map(DispatchResult::Promise)
|
||||
.map_err(|_| errors::request_rejected_limit())
|
||||
}
|
||||
|
||||
fn dispatch(&self, payload: RpcConfirmationPayload, default_account: DefaultAccount) -> Result<DispatchResult, Error> {
|
||||
let client = take_weak!(self.client);
|
||||
let miner = take_weak!(self.miner);
|
||||
|
||||
let default_account = match default_account {
|
||||
DefaultAccount::Provided(acc) => acc,
|
||||
DefaultAccount::ForDapp(dapp) => take_weak!(self.accounts).default_address(dapp).ok().unwrap_or_default(),
|
||||
(self.dispatcher.clone(), accounts, default_account)
|
||||
};
|
||||
let payload = dispatch::from_rpc(payload, default_account, &*client, &*miner);
|
||||
self.add_to_queue(payload)
|
||||
|
||||
let weak_signer = self.signer.clone();
|
||||
future::done(setup)
|
||||
.and_then(move |(dispatcher, accounts, default_account)| {
|
||||
dispatch::from_rpc(payload, default_account, &dispatcher)
|
||||
.and_then(move |payload| {
|
||||
let sender = payload.sender();
|
||||
if accounts.is_unlocked(sender) {
|
||||
dispatch::execute(dispatcher, &accounts, payload, dispatch::SignWith::Nothing)
|
||||
.boxed()
|
||||
} else {
|
||||
future::lazy(move || take_weak!(weak_signer).add_request(payload))
|
||||
.map(DispatchResult::Promise)
|
||||
.map_err(|_| errors::request_rejected_limit())
|
||||
.boxed()
|
||||
}
|
||||
})
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: 'static, M: 'static> ParitySigning for SigningQueueClient<C, M> where
|
||||
C: MiningBlockChainClient,
|
||||
M: MinerService,
|
||||
{
|
||||
impl<D: Dispatcher + 'static> ParitySigning for SigningQueueClient<D> {
|
||||
type Metadata = Metadata;
|
||||
|
||||
fn post_sign(&self, address: RpcH160, data: RpcBytes) -> Result<RpcEither<RpcU256, RpcConfirmationResponse>, Error> {
|
||||
fn post_sign(&self, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcEither<RpcU256, RpcConfirmationResponse>, Error> {
|
||||
let pending = self.pending.clone();
|
||||
self.dispatch(RpcConfirmationPayload::Signature((address.clone(), data).into()), DefaultAccount::Provided(address.into()))
|
||||
.map(|result| match result {
|
||||
.map(move |result| match result {
|
||||
DispatchResult::Value(v) => RpcEither::Or(v),
|
||||
DispatchResult::Promise(promise) => {
|
||||
let id = promise.id();
|
||||
self.pending.lock().insert(id, promise);
|
||||
pending.lock().insert(id, promise);
|
||||
RpcEither::Either(id.into())
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn post_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture<RpcEither<RpcU256, RpcConfirmationResponse>, Error> {
|
||||
let post_transaction = move || {
|
||||
self.dispatch(RpcConfirmationPayload::SendTransaction(request), meta.into())
|
||||
.map(|result| match result {
|
||||
DispatchResult::Value(v) => RpcEither::Or(v),
|
||||
DispatchResult::Promise(promise) => {
|
||||
let id = promise.id();
|
||||
self.pending.lock().insert(id, promise);
|
||||
RpcEither::Either(id.into())
|
||||
},
|
||||
})
|
||||
};
|
||||
futures::done(post_transaction()).boxed()
|
||||
let pending = self.pending.clone();
|
||||
self.dispatch(RpcConfirmationPayload::SendTransaction(request), meta.into())
|
||||
.map(move |result| match result {
|
||||
DispatchResult::Value(v) => RpcEither::Or(v),
|
||||
DispatchResult::Promise(promise) => {
|
||||
let id = promise.id();
|
||||
pending.lock().insert(id, promise);
|
||||
RpcEither::Either(id.into())
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn check_request(&self, id: RpcU256) -> Result<Option<RpcConfirmationResponse>, Error> {
|
||||
@ -170,67 +163,78 @@ impl<C: 'static, M: 'static> ParitySigning for SigningQueueClient<C, M> where
|
||||
let res = self.dispatch(RpcConfirmationPayload::Decrypt((address.clone(), data).into()), address.into());
|
||||
|
||||
let (ready, p) = futures::oneshot();
|
||||
// TODO [todr] typed handle_dispatch
|
||||
self.handle_dispatch(res, |response| {
|
||||
match response {
|
||||
Ok(RpcConfirmationResponse::Decrypt(data)) => ready.complete(Ok(data)),
|
||||
Err(e) => ready.complete(Err(e)),
|
||||
e => ready.complete(Err(errors::internal("Unexpected result.", e))),
|
||||
}
|
||||
});
|
||||
|
||||
p.then(|result| futures::done(result.expect("Ready is never dropped nor canceled."))).boxed()
|
||||
// when dispatch is complete
|
||||
res.then(move |res| {
|
||||
// register callback via the oneshot sender.
|
||||
handle_dispatch(res, move |response| {
|
||||
match response {
|
||||
Ok(RpcConfirmationResponse::Decrypt(data)) => ready.complete(Ok(data)),
|
||||
Err(e) => ready.complete(Err(e)),
|
||||
e => ready.complete(Err(errors::internal("Unexpected result.", e))),
|
||||
}
|
||||
});
|
||||
|
||||
// and wait for that to resolve.
|
||||
p.then(|result| futures::done(result.expect("Ready is never dropped nor canceled."))).boxed()
|
||||
}).boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: 'static, M: 'static> EthSigning for SigningQueueClient<C, M> where
|
||||
C: MiningBlockChainClient,
|
||||
M: MinerService,
|
||||
{
|
||||
impl<D: Dispatcher + 'static> EthSigning for SigningQueueClient<D> {
|
||||
type Metadata = Metadata;
|
||||
|
||||
fn sign(&self, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcH520, Error> {
|
||||
let res = self.dispatch(RpcConfirmationPayload::Signature((address.clone(), data).into()), address.into());
|
||||
|
||||
let (ready, p) = futures::oneshot();
|
||||
self.handle_dispatch(res, |response| {
|
||||
match response {
|
||||
Ok(RpcConfirmationResponse::Signature(signature)) => ready.complete(Ok(signature)),
|
||||
Err(e) => ready.complete(Err(e)),
|
||||
e => ready.complete(Err(errors::internal("Unexpected result.", e))),
|
||||
}
|
||||
});
|
||||
|
||||
p.then(|result| futures::done(result.expect("Ready is never dropped nor canceled."))).boxed()
|
||||
res.then(move |res| {
|
||||
handle_dispatch(res, move |response| {
|
||||
match response {
|
||||
Ok(RpcConfirmationResponse::Signature(sig)) => ready.complete(Ok(sig)),
|
||||
Err(e) => ready.complete(Err(e)),
|
||||
e => ready.complete(Err(errors::internal("Unexpected result.", e))),
|
||||
}
|
||||
});
|
||||
|
||||
p.then(|result| futures::done(result.expect("Ready is never dropped nor canceled."))).boxed()
|
||||
}).boxed()
|
||||
}
|
||||
|
||||
fn send_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture<RpcH256, Error> {
|
||||
let res = self.dispatch(RpcConfirmationPayload::SendTransaction(request), meta.into());
|
||||
|
||||
let (ready, p) = futures::oneshot();
|
||||
self.handle_dispatch(res, |response| {
|
||||
match response {
|
||||
Ok(RpcConfirmationResponse::SendTransaction(hash)) => ready.complete(Ok(hash)),
|
||||
Err(e) => ready.complete(Err(e)),
|
||||
e => ready.complete(Err(errors::internal("Unexpected result.", e))),
|
||||
}
|
||||
});
|
||||
|
||||
p.then(|result| futures::done(result.expect("Ready is never dropped nor canceled."))).boxed()
|
||||
res.then(move |res| {
|
||||
handle_dispatch(res, move |response| {
|
||||
match response {
|
||||
Ok(RpcConfirmationResponse::SendTransaction(hash)) => ready.complete(Ok(hash)),
|
||||
Err(e) => ready.complete(Err(e)),
|
||||
e => ready.complete(Err(errors::internal("Unexpected result.", e))),
|
||||
}
|
||||
});
|
||||
|
||||
p.then(|result| futures::done(result.expect("Ready is never dropped nor canceled."))).boxed()
|
||||
}).boxed()
|
||||
}
|
||||
|
||||
fn sign_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture<RpcRichRawTransaction, Error> {
|
||||
let res = self.dispatch(RpcConfirmationPayload::SignTransaction(request), meta.into());
|
||||
|
||||
let (ready, p) = futures::oneshot();
|
||||
self.handle_dispatch(res, |response| {
|
||||
match response {
|
||||
Ok(RpcConfirmationResponse::SignTransaction(tx)) => ready.complete(Ok(tx)),
|
||||
Err(e) => ready.complete(Err(e)),
|
||||
e => ready.complete(Err(errors::internal("Unexpected result.", e))),
|
||||
}
|
||||
});
|
||||
|
||||
p.then(|result| futures::done(result.expect("Ready is never dropped nor canceled."))).boxed()
|
||||
res.then(move |res| {
|
||||
handle_dispatch(res, move |response| {
|
||||
match response {
|
||||
Ok(RpcConfirmationResponse::SignTransaction(tx)) => ready.complete(Ok(tx)),
|
||||
Err(e) => ready.complete(Err(e)),
|
||||
e => ready.complete(Err(errors::internal("Unexpected result.", e))),
|
||||
}
|
||||
});
|
||||
|
||||
p.then(|result| futures::done(result.expect("Ready is never dropped nor canceled."))).boxed()
|
||||
}).boxed()
|
||||
}
|
||||
}
|
||||
|
@ -22,9 +22,10 @@ use ethcore::account_provider::AccountProvider;
|
||||
use ethcore::miner::MinerService;
|
||||
use ethcore::client::MiningBlockChainClient;
|
||||
|
||||
use futures::{self, BoxFuture, Future};
|
||||
use futures::{self, future, BoxFuture, Future};
|
||||
use jsonrpc_core::Error;
|
||||
use v1::helpers::{errors, dispatch, DefaultAccount};
|
||||
use v1::helpers::{errors, DefaultAccount};
|
||||
use v1::helpers::dispatch::{self, Dispatcher};
|
||||
use v1::metadata::Metadata;
|
||||
use v1::traits::{EthSigning, ParitySigning};
|
||||
use v1::types::{
|
||||
@ -38,106 +39,100 @@ use v1::types::{
|
||||
};
|
||||
|
||||
/// Implementation of functions that require signing when no trusted signer is used.
|
||||
pub struct SigningUnsafeClient<C, M> where
|
||||
C: MiningBlockChainClient,
|
||||
M: MinerService,
|
||||
{
|
||||
pub struct SigningUnsafeClient<D> {
|
||||
accounts: Weak<AccountProvider>,
|
||||
client: Weak<C>,
|
||||
miner: Weak<M>,
|
||||
dispatcher: D,
|
||||
}
|
||||
|
||||
impl<C, M> SigningUnsafeClient<C, M> where
|
||||
C: MiningBlockChainClient,
|
||||
M: MinerService,
|
||||
{
|
||||
|
||||
impl<D: Dispatcher> SigningUnsafeClient<D> {
|
||||
/// Creates new SigningUnsafeClient.
|
||||
pub fn new(client: &Arc<C>, accounts: &Arc<AccountProvider>, miner: &Arc<M>)
|
||||
-> Self {
|
||||
pub fn new(accounts: &Arc<AccountProvider>, dispatcher: D) -> Self {
|
||||
SigningUnsafeClient {
|
||||
client: Arc::downgrade(client),
|
||||
miner: Arc::downgrade(miner),
|
||||
accounts: Arc::downgrade(accounts),
|
||||
dispatcher: dispatcher,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle(&self, payload: RpcConfirmationPayload, account: DefaultAccount) -> Result<RpcConfirmationResponse, Error> {
|
||||
let client = take_weak!(self.client);
|
||||
let miner = take_weak!(self.miner);
|
||||
let accounts = take_weak!(self.accounts);
|
||||
fn handle(&self, payload: RpcConfirmationPayload, account: DefaultAccount) -> BoxFuture<RpcConfirmationResponse, Error> {
|
||||
let setup = move || {
|
||||
let accounts = take_weak!(self.accounts);
|
||||
let default_account = match account {
|
||||
DefaultAccount::Provided(acc) => acc,
|
||||
DefaultAccount::ForDapp(dapp) => accounts.default_address(dapp).ok().unwrap_or_default(),
|
||||
};
|
||||
|
||||
let default_account = match account {
|
||||
DefaultAccount::Provided(acc) => acc,
|
||||
DefaultAccount::ForDapp(dapp) => accounts.default_address(dapp).ok().unwrap_or_default(),
|
||||
(accounts, default_account)
|
||||
};
|
||||
let payload = dispatch::from_rpc(payload, default_account, &*client, &*miner);
|
||||
dispatch::execute(&*client, &*miner, &*accounts, payload, dispatch::SignWith::Nothing)
|
||||
.map(|v| v.into_value())
|
||||
|
||||
let dis = self.dispatcher.clone();
|
||||
future::done(setup())
|
||||
.and_then(move |(accounts, default)| {
|
||||
dispatch::from_rpc(payload, default, &dis)
|
||||
.and_then(|payload| {
|
||||
dispatch::execute(&dis, &accounts, payload, dispatch::SignWith::Nothing)
|
||||
})
|
||||
.map(|v| v.into_value())
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: 'static, M: 'static> EthSigning for SigningUnsafeClient<C, M> where
|
||||
C: MiningBlockChainClient,
|
||||
M: MinerService,
|
||||
impl<D: Dispatcher + 'static> EthSigning for SigningUnsafeClient<D>
|
||||
{
|
||||
type Metadata = Metadata;
|
||||
|
||||
fn sign(&self, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcH520, Error> {
|
||||
let result = match self.handle(RpcConfirmationPayload::Signature((address.clone(), data).into()), address.into()) {
|
||||
Ok(RpcConfirmationResponse::Signature(signature)) => Ok(signature),
|
||||
Err(e) => Err(e),
|
||||
e => Err(errors::internal("Unexpected result", e)),
|
||||
};
|
||||
|
||||
futures::done(result).boxed()
|
||||
self.handle(RpcConfirmationPayload::Signature((address.clone(), data).into()), address.into())
|
||||
.then(|res| match res {
|
||||
Ok(RpcConfirmationResponse::Signature(signature)) => Ok(signature),
|
||||
Err(e) => Err(e),
|
||||
e => Err(errors::internal("Unexpected result", e)),
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn send_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture<RpcH256, Error> {
|
||||
let result = match self.handle(RpcConfirmationPayload::SendTransaction(request), meta.into()) {
|
||||
Ok(RpcConfirmationResponse::SendTransaction(hash)) => Ok(hash),
|
||||
Err(e) => Err(e),
|
||||
e => Err(errors::internal("Unexpected result", e)),
|
||||
};
|
||||
|
||||
futures::done(result).boxed()
|
||||
self.handle(RpcConfirmationPayload::SendTransaction(request), meta.into())
|
||||
.then(|res| match res {
|
||||
Ok(RpcConfirmationResponse::SendTransaction(hash)) => Ok(hash),
|
||||
Err(e) => Err(e),
|
||||
e => Err(errors::internal("Unexpected result", e)),
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn sign_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture<RpcRichRawTransaction, Error> {
|
||||
let result = match self.handle(RpcConfirmationPayload::SignTransaction(request), meta.into()) {
|
||||
Ok(RpcConfirmationResponse::SignTransaction(tx)) => Ok(tx),
|
||||
Err(e) => Err(e),
|
||||
e => Err(errors::internal("Unexpected result", e)),
|
||||
};
|
||||
|
||||
futures::done(result).boxed()
|
||||
self.handle(RpcConfirmationPayload::SignTransaction(request), meta.into())
|
||||
.then(|res| match res {
|
||||
Ok(RpcConfirmationResponse::SignTransaction(tx)) => Ok(tx),
|
||||
Err(e) => Err(e),
|
||||
e => Err(errors::internal("Unexpected result", e)),
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: 'static, M: 'static> ParitySigning for SigningUnsafeClient<C, M> where
|
||||
C: MiningBlockChainClient,
|
||||
M: MinerService,
|
||||
{
|
||||
impl<D: Dispatcher + 'static> ParitySigning for SigningUnsafeClient<D> {
|
||||
type Metadata = Metadata;
|
||||
|
||||
fn decrypt_message(&self, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcBytes, Error> {
|
||||
let result = match self.handle(RpcConfirmationPayload::Decrypt((address.clone(), data).into()), address.into()) {
|
||||
Ok(RpcConfirmationResponse::Decrypt(data)) => Ok(data),
|
||||
Err(e) => Err(e),
|
||||
e => Err(errors::internal("Unexpected result", e)),
|
||||
};
|
||||
|
||||
futures::done(result).boxed()
|
||||
self.handle(RpcConfirmationPayload::Decrypt((address.clone(), data).into()), address.into())
|
||||
.then(|res| match res {
|
||||
Ok(RpcConfirmationResponse::Decrypt(data)) => Ok(data),
|
||||
Err(e) => Err(e),
|
||||
e => Err(errors::internal("Unexpected result", e)),
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn post_sign(&self, _: RpcH160, _: RpcBytes) -> Result<RpcEither<RpcU256, RpcConfirmationResponse>, Error> {
|
||||
fn post_sign(&self, _: RpcH160, _: RpcBytes) -> BoxFuture<RpcEither<RpcU256, RpcConfirmationResponse>, Error> {
|
||||
// We don't support this in non-signer mode.
|
||||
Err(errors::signer_disabled())
|
||||
future::err(errors::signer_disabled()).boxed()
|
||||
}
|
||||
|
||||
fn post_transaction(&self, _: Metadata, _: RpcTransactionRequest) -> BoxFuture<RpcEither<RpcU256, RpcConfirmationResponse>, Error> {
|
||||
// We don't support this in non-signer mode.
|
||||
futures::done(Err(errors::signer_disabled())).boxed()
|
||||
future::err((errors::signer_disabled())).boxed()
|
||||
}
|
||||
|
||||
fn check_request(&self, _: RpcU256) -> Result<Option<RpcConfirmationResponse>, Error> {
|
||||
|
@ -18,6 +18,15 @@
|
||||
//!
|
||||
//! Compliant with ethereum rpc.
|
||||
|
||||
macro_rules! take_weak {
|
||||
($weak: expr) => {
|
||||
match $weak.upgrade() {
|
||||
Some(arc) => arc,
|
||||
None => return Err(Error::internal_error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_use]
|
||||
mod helpers;
|
||||
mod impls;
|
||||
|
@ -27,8 +27,8 @@ build_rpc_trait! {
|
||||
|
||||
/// Posts sign request asynchronously.
|
||||
/// Will return a confirmation ID for later use with check_transaction.
|
||||
#[rpc(name = "parity_postSign")]
|
||||
fn post_sign(&self, H160, Bytes) -> Result<Either<U256, ConfirmationResponse>, Error>;
|
||||
#[rpc(async, name = "parity_postSign")]
|
||||
fn post_sign(&self, H160, Bytes) -> BoxFuture<Either<U256, ConfirmationResponse>, Error>;
|
||||
|
||||
/// Posts transaction asynchronously.
|
||||
/// Will return a transaction ID for later use with check_transaction.
|
||||
|
@ -164,7 +164,7 @@ mod tests {
|
||||
logs_bloom: H2048::default(),
|
||||
timestamp: U256::default(),
|
||||
difficulty: U256::default(),
|
||||
total_difficulty: U256::default(),
|
||||
total_difficulty: Some(U256::default()),
|
||||
seal_fields: vec![Bytes::default(), Bytes::default()],
|
||||
uncles: vec![],
|
||||
transactions: BlockTransactions::Hashes(vec![].into()),
|
||||
|
Loading…
Reference in New Issue
Block a user