Merge pull request #4501 from ethcore/light-txq
Light Client transaction queue, initial LightDispatcher
This commit is contained in:
@@ -18,14 +18,18 @@
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Weak;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use futures::{future, Future, BoxFuture};
|
||||
use light::client::LightChainClient;
|
||||
use light::on_demand::{request, OnDemand};
|
||||
use light::TransactionQueue as LightTransactionQueue;
|
||||
use rlp::{self, Stream};
|
||||
use util::{Address, H520, H256, U256, Uint, Bytes};
|
||||
use util::{Address, H520, H256, U256, Uint, Bytes, RwLock};
|
||||
use util::sha3::Hashable;
|
||||
|
||||
use ethkey::Signature;
|
||||
use ethsync::LightSync;
|
||||
use ethcore::miner::MinerService;
|
||||
use ethcore::client::MiningBlockChainClient;
|
||||
use ethcore::transaction::{Action, SignedTransaction, PendingTransaction, Transaction};
|
||||
@@ -55,7 +59,7 @@ pub trait Dispatcher: Send + Sync + Clone {
|
||||
-> BoxFuture<FilledTransactionRequest, Error>;
|
||||
|
||||
/// Sign the given transaction request without dispatching, fetching appropriate nonce.
|
||||
fn sign(&self, accounts: &AccountProvider, filled: FilledTransactionRequest, password: SignWith)
|
||||
fn sign(&self, accounts: Arc<AccountProvider>, filled: FilledTransactionRequest, password: SignWith)
|
||||
-> BoxFuture<WithToken<SignedTransaction>, Error>;
|
||||
|
||||
/// "Dispatch" a local transaction.
|
||||
@@ -108,13 +112,13 @@ impl<C: MiningBlockChainClient, M: MinerService> Dispatcher for FullDispatcher<C
|
||||
}).boxed()
|
||||
}
|
||||
|
||||
fn sign(&self, accounts: &AccountProvider, filled: FilledTransactionRequest, password: SignWith)
|
||||
fn sign(&self, accounts: Arc<AccountProvider>, filled: FilledTransactionRequest, password: SignWith)
|
||||
-> BoxFuture<WithToken<SignedTransaction>, Error>
|
||||
{
|
||||
let (client, miner) = (take_weakf!(self.client), take_weakf!(self.miner));
|
||||
let network_id = client.signing_network_id();
|
||||
let address = filled.from;
|
||||
future::ok({
|
||||
future::done({
|
||||
let t = Transaction {
|
||||
nonce: filled.nonce
|
||||
.or_else(|| miner
|
||||
@@ -129,31 +133,15 @@ impl<C: MiningBlockChainClient, M: MinerService> Dispatcher for FullDispatcher<C
|
||||
data: filled.data,
|
||||
};
|
||||
|
||||
let hash = t.hash(network_id);
|
||||
if accounts.is_hardware_address(address) {
|
||||
let mut stream = rlp::RlpStream::new();
|
||||
t.rlp_append_unsigned_transaction(&mut stream, network_id);
|
||||
let signature = try_bf!(
|
||||
accounts.sign_with_hardware(address, &stream.as_raw())
|
||||
.map_err(|e| {
|
||||
debug!(target: "miner", "Error signing transaction with hardware wallet: {}", e);
|
||||
errors::account("Error signing transaction with hardware wallet", e)
|
||||
})
|
||||
);
|
||||
let signed = try_bf!(
|
||||
SignedTransaction::new(t.with_signature(signature, network_id))
|
||||
.map_err(|e| {
|
||||
debug!(target: "miner", "Hardware wallet has produced invalid signature: {}", e);
|
||||
errors::account("Invalid signature generated", e)
|
||||
})
|
||||
);
|
||||
WithToken::No(signed)
|
||||
hardware_signature(&*accounts, address, t, network_id).map(WithToken::No)
|
||||
} else {
|
||||
let signature = try_bf!(signature(accounts, address, hash, password));
|
||||
signature.map(|sig| {
|
||||
let hash = t.hash(network_id);
|
||||
let signature = try_bf!(signature(&*accounts, address, hash, password));
|
||||
Ok(signature.map(|sig| {
|
||||
SignedTransaction::new(t.with_signature(sig, network_id))
|
||||
.expect("Transaction was signed by AccountsProvider; it never produces invalid signatures; qed")
|
||||
})
|
||||
}))
|
||||
}
|
||||
}).boxed()
|
||||
}
|
||||
@@ -167,6 +155,117 @@ impl<C: MiningBlockChainClient, M: MinerService> Dispatcher for FullDispatcher<C
|
||||
}
|
||||
}
|
||||
|
||||
/// Dispatcher for light clients -- fetches default gas price, next nonce, etc. from network.
|
||||
/// Light client `ETH` RPC.
|
||||
#[derive(Clone)]
|
||||
pub struct LightDispatcher {
|
||||
sync: Arc<LightSync>,
|
||||
client: Arc<LightChainClient>,
|
||||
on_demand: Arc<OnDemand>,
|
||||
transaction_queue: Arc<RwLock<LightTransactionQueue>>,
|
||||
}
|
||||
|
||||
impl LightDispatcher {
|
||||
/// Create a new `LightDispatcher` from its requisite parts.
|
||||
///
|
||||
/// For correct operation, the OnDemand service is assumed to be registered as a network handler,
|
||||
pub fn new(
|
||||
sync: Arc<LightSync>,
|
||||
client: Arc<LightChainClient>,
|
||||
on_demand: Arc<OnDemand>,
|
||||
transaction_queue: Arc<RwLock<LightTransactionQueue>>,
|
||||
) -> Self {
|
||||
LightDispatcher {
|
||||
sync: sync,
|
||||
client: client,
|
||||
on_demand: on_demand,
|
||||
transaction_queue: transaction_queue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatcher for LightDispatcher {
|
||||
fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address)
|
||||
-> BoxFuture<FilledTransactionRequest, Error>
|
||||
{
|
||||
let request = request;
|
||||
let gas_limit = self.client.best_block_header().gas_limit();
|
||||
|
||||
future::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(|| 21_000_000.into()), // TODO: fetch corpus from network.
|
||||
gas: request.gas.unwrap_or_else(|| gas_limit / 3.into()),
|
||||
value: request.value.unwrap_or_else(|| 0.into()),
|
||||
data: request.data.unwrap_or_else(Vec::new),
|
||||
condition: request.condition,
|
||||
}).boxed()
|
||||
}
|
||||
|
||||
fn sign(&self, accounts: Arc<AccountProvider>, filled: FilledTransactionRequest, password: SignWith)
|
||||
-> BoxFuture<WithToken<SignedTransaction>, Error>
|
||||
{
|
||||
let network_id = None; // TODO: fetch from client.
|
||||
let address = filled.from;
|
||||
let best_header = self.client.best_block_header();
|
||||
|
||||
let with_nonce = move |filled: FilledTransactionRequest, nonce| {
|
||||
let t = Transaction {
|
||||
nonce: nonce,
|
||||
action: filled.to.map_or(Action::Create, Action::Call),
|
||||
gas: filled.gas,
|
||||
gas_price: filled.gas_price,
|
||||
value: filled.value,
|
||||
data: filled.data,
|
||||
};
|
||||
|
||||
if accounts.is_hardware_address(address) {
|
||||
return hardware_signature(&*accounts, address, t, network_id).map(WithToken::No)
|
||||
}
|
||||
|
||||
let hash = t.hash(network_id);
|
||||
let signature = signature(&*accounts, address, hash, password)?;
|
||||
|
||||
Ok(signature.map(|sig| {
|
||||
SignedTransaction::new(t.with_signature(sig, network_id))
|
||||
.expect("Transaction was signed by AccountsProvider; it never produces invalid signatures; qed")
|
||||
}))
|
||||
};
|
||||
|
||||
// fast path where we don't go to network; nonce provided or can be gotten from queue.
|
||||
let maybe_nonce = filled.nonce.or_else(|| self.transaction_queue.read().next_nonce(&address));
|
||||
if let Some(nonce) = maybe_nonce {
|
||||
return future::done(with_nonce(filled, nonce)).boxed()
|
||||
}
|
||||
|
||||
let nonce_future = self.sync.with_context(|ctx| self.on_demand.account(ctx, request::Account {
|
||||
header: best_header,
|
||||
address: address,
|
||||
}));
|
||||
|
||||
let nonce_future = match nonce_future {
|
||||
Some(x) => x,
|
||||
None => return future::err(errors::no_light_peers()).boxed()
|
||||
};
|
||||
|
||||
nonce_future
|
||||
.map_err(|_| errors::no_light_peers())
|
||||
.and_then(move |acc| with_nonce(filled, acc.nonce))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn dispatch_transaction(&self, signed_transaction: PendingTransaction) -> Result<H256, Error> {
|
||||
let hash = signed_transaction.transaction.hash();
|
||||
|
||||
self.transaction_queue.write().import(signed_transaction)
|
||||
.map_err(Into::into)
|
||||
.map_err(errors::from_transaction_error)
|
||||
.map(|_| hash)
|
||||
}
|
||||
}
|
||||
|
||||
/// default MAC to use.
|
||||
pub const DEFAULT_MAC: [u8; 2] = [0, 0];
|
||||
|
||||
@@ -251,7 +350,7 @@ impl<T: Debug> From<(T, Option<AccountToken>)> for WithToken<T> {
|
||||
/// Execute a confirmation payload.
|
||||
pub fn execute<D: Dispatcher + 'static>(
|
||||
dispatcher: D,
|
||||
accounts: &AccountProvider,
|
||||
accounts: Arc<AccountProvider>,
|
||||
payload: ConfirmationPayload,
|
||||
pass: SignWith
|
||||
) -> BoxFuture<WithToken<ConfirmationResponse>, Error> {
|
||||
@@ -281,7 +380,7 @@ pub fn execute<D: Dispatcher + 'static>(
|
||||
format!("\x19Ethereum Signed Message:\n{}", data.len())
|
||||
.into_bytes();
|
||||
message_data.append(&mut data);
|
||||
let res = signature(accounts, address, message_data.sha3(), pass)
|
||||
let res = signature(&accounts, address, message_data.sha3(), pass)
|
||||
.map(|result| result
|
||||
.map(|rsv| {
|
||||
let mut vrs = [0u8; 65];
|
||||
@@ -297,7 +396,7 @@ pub fn execute<D: Dispatcher + 'static>(
|
||||
future::done(res).boxed()
|
||||
},
|
||||
ConfirmationPayload::Decrypt(address, data) => {
|
||||
let res = decrypt(accounts, address, data, pass)
|
||||
let res = decrypt(&accounts, address, data, pass)
|
||||
.map(|result| result
|
||||
.map(RpcBytes)
|
||||
.map(ConfirmationResponse::Decrypt)
|
||||
@@ -318,6 +417,27 @@ fn signature(accounts: &AccountProvider, address: Address, hash: H256, password:
|
||||
})
|
||||
}
|
||||
|
||||
// obtain a hardware signature from the given account.
|
||||
fn hardware_signature(accounts: &AccountProvider, address: Address, t: Transaction, network_id: Option<u64>)
|
||||
-> Result<SignedTransaction, Error>
|
||||
{
|
||||
debug_assert!(accounts.is_hardware_address(address));
|
||||
|
||||
let mut stream = rlp::RlpStream::new();
|
||||
t.rlp_append_unsigned_transaction(&mut stream, network_id);
|
||||
let signature = accounts.sign_with_hardware(address, &stream.as_raw())
|
||||
.map_err(|e| {
|
||||
debug!(target: "miner", "Error signing transaction with hardware wallet: {}", e);
|
||||
errors::account("Error signing transaction with hardware wallet", e)
|
||||
})?;
|
||||
|
||||
SignedTransaction::new(t.with_signature(signature, network_id))
|
||||
.map_err(|e| {
|
||||
debug!(target: "miner", "Hardware wallet has produced invalid signature: {}", e);
|
||||
errors::account("Invalid signature generated", e)
|
||||
})
|
||||
}
|
||||
|
||||
fn decrypt(accounts: &AccountProvider, address: Address, msg: Bytes, password: SignWith) -> Result<WithToken<Bytes>, Error> {
|
||||
match password.clone() {
|
||||
SignWith::Nothing => accounts.decrypt(address, None, &DEFAULT_MAC, &msg).map(WithToken::No),
|
||||
|
||||
@@ -49,6 +49,7 @@ mod codes {
|
||||
pub const COMPILATION_ERROR: i64 = -32050;
|
||||
pub const ENCRYPTION_ERROR: i64 = -32055;
|
||||
pub const FETCH_ERROR: i64 = -32060;
|
||||
pub const NO_LIGHT_PEERS: i64 = -32065;
|
||||
}
|
||||
|
||||
pub fn unimplemented(details: Option<String>) -> Error {
|
||||
@@ -308,3 +309,11 @@ pub fn unknown_block() -> Error {
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn no_light_peers() -> Error {
|
||||
Error {
|
||||
code: ErrorCode::ServerError(codes::NO_LIGHT_PEERS),
|
||||
message: "No light peers who can serve data".into(),
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,16 +25,18 @@ use jsonrpc_core::Error;
|
||||
use jsonrpc_macros::Trailing;
|
||||
|
||||
use light::client::Client as LightClient;
|
||||
use light::cht;
|
||||
use light::{cht, TransactionQueue};
|
||||
use light::on_demand::{request, OnDemand};
|
||||
|
||||
use ethcore::account_provider::{AccountProvider, DappId};
|
||||
use ethcore::basic_account::BasicAccount;
|
||||
use ethcore::encoded;
|
||||
use ethcore::ids::BlockId;
|
||||
use ethcore::transaction::SignedTransaction;
|
||||
use ethsync::LightSync;
|
||||
use rlp::{UntrustedRlp, View};
|
||||
use util::sha3::{SHA3_NULL_RLP, SHA3_EMPTY_LIST_RLP};
|
||||
use util::U256;
|
||||
use util::{RwLock, U256};
|
||||
|
||||
use futures::{future, Future, BoxFuture};
|
||||
use futures::sync::oneshot;
|
||||
@@ -56,6 +58,7 @@ pub struct EthClient {
|
||||
sync: Arc<LightSync>,
|
||||
client: Arc<LightClient>,
|
||||
on_demand: Arc<OnDemand>,
|
||||
transaction_queue: Arc<RwLock<TransactionQueue>>,
|
||||
accounts: Arc<AccountProvider>,
|
||||
}
|
||||
|
||||
@@ -76,12 +79,14 @@ impl EthClient {
|
||||
sync: Arc<LightSync>,
|
||||
client: Arc<LightClient>,
|
||||
on_demand: Arc<OnDemand>,
|
||||
transaction_queue: Arc<RwLock<TransactionQueue>>,
|
||||
accounts: Arc<AccountProvider>,
|
||||
) -> Self {
|
||||
EthClient {
|
||||
sync: sync,
|
||||
client: client,
|
||||
on_demand: on_demand,
|
||||
transaction_queue: transaction_queue,
|
||||
accounts: accounts,
|
||||
}
|
||||
}
|
||||
@@ -300,11 +305,27 @@ impl Eth for EthClient {
|
||||
}
|
||||
|
||||
fn send_raw_transaction(&self, raw: Bytes) -> Result<RpcH256, Error> {
|
||||
Err(errors::unimplemented(None))
|
||||
let best_header = self.client.best_block_header().decode();
|
||||
|
||||
UntrustedRlp::new(&raw.into_vec()).as_val()
|
||||
.map_err(errors::from_rlp_error)
|
||||
.and_then(|tx| {
|
||||
self.client.engine().verify_transaction_basic(&tx, &best_header)
|
||||
.map_err(errors::from_transaction_error)?;
|
||||
|
||||
let signed = SignedTransaction::new(tx).map_err(errors::from_transaction_error)?;
|
||||
let hash = signed.hash();
|
||||
|
||||
self.transaction_queue.write().import(signed.into())
|
||||
.map(|_| hash)
|
||||
.map_err(Into::into)
|
||||
.map_err(errors::from_transaction_error)
|
||||
})
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
fn submit_transaction(&self, raw: Bytes) -> Result<RpcH256, Error> {
|
||||
Err(errors::unimplemented(None))
|
||||
self.send_raw_transaction(raw)
|
||||
}
|
||||
|
||||
fn call(&self, req: CallRequest, num: Trailing<BlockNumber>) -> Result<Bytes, Error> {
|
||||
|
||||
@@ -113,7 +113,7 @@ impl<D: Dispatcher + 'static> Personal for PersonalClient<D> {
|
||||
dispatcher.fill_optional_fields(request.into(), default)
|
||||
.and_then(move |filled| {
|
||||
let condition = filled.condition.clone().map(Into::into);
|
||||
dispatcher.sign(&accounts, filled, SignWith::Password(password))
|
||||
dispatcher.sign(accounts, filled, SignWith::Password(password))
|
||||
.map(|tx| tx.into_value())
|
||||
.map(move |tx| PendingTransaction::new(tx, condition))
|
||||
.map(move |tx| (tx, dispatcher))
|
||||
|
||||
@@ -52,7 +52,7 @@ impl<D: Dispatcher + 'static> SignerClient<D> {
|
||||
}
|
||||
|
||||
fn confirm_internal<F, T>(&self, id: U256, modification: TransactionModification, f: F) -> BoxFuture<WithToken<ConfirmationResponse>, Error> where
|
||||
F: FnOnce(D, &AccountProvider, ConfirmationPayload) -> T,
|
||||
F: FnOnce(D, Arc<AccountProvider>, ConfirmationPayload) -> T,
|
||||
T: IntoFuture<Item=WithToken<ConfirmationResponse>, Error=Error>,
|
||||
T::Future: Send + 'static
|
||||
{
|
||||
@@ -87,7 +87,7 @@ impl<D: Dispatcher + 'static> SignerClient<D> {
|
||||
request.condition = condition.clone().map(Into::into);
|
||||
}
|
||||
}
|
||||
let fut = f(dispatcher, &*accounts, payload);
|
||||
let fut = f(dispatcher, accounts, payload);
|
||||
fut.into_future().then(move |result| {
|
||||
// Execute
|
||||
if let Ok(ref response) = result {
|
||||
|
||||
@@ -95,7 +95,7 @@ impl<D: Dispatcher + 'static> SigningQueueClient<D> {
|
||||
.and_then(move |payload| {
|
||||
let sender = payload.sender();
|
||||
if accounts.is_unlocked(sender) {
|
||||
dispatch::execute(dispatcher, &accounts, payload, dispatch::SignWith::Nothing)
|
||||
dispatch::execute(dispatcher, accounts, payload, dispatch::SignWith::Nothing)
|
||||
.map(|v| v.into_value())
|
||||
.map(DispatchResult::Value)
|
||||
.boxed()
|
||||
|
||||
@@ -61,7 +61,7 @@ impl<D: Dispatcher + 'static> SigningUnsafeClient<D> {
|
||||
let dis = self.dispatcher.clone();
|
||||
dispatch::from_rpc(payload, default, &dis)
|
||||
.and_then(move |payload| {
|
||||
dispatch::execute(dis, &accounts, payload, dispatch::SignWith::Nothing)
|
||||
dispatch::execute(dis, accounts, payload, dispatch::SignWith::Nothing)
|
||||
})
|
||||
.map(|v| v.into_value())
|
||||
.boxed()
|
||||
|
||||
Reference in New Issue
Block a user