openethereum/rpc/src/v1/helpers/dispatch.rs

778 lines
24 KiB
Rust
Raw Normal View History

// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
2017-02-08 20:44:40 +01:00
//! Utilities and helpers for transaction dispatch.
2016-11-30 16:11:41 +01:00
use std::fmt::Debug;
2016-11-30 17:05:31 +01:00
use std::ops::Deref;
use std::sync::Arc;
2017-02-17 16:18:31 +01:00
use light::cache::Cache as LightDataCache;
2017-02-09 20:22:31 +01:00
use light::client::LightChainClient;
use light::on_demand::{request, OnDemand};
use light::TransactionQueue as LightTransactionQueue;
use rlp;
2017-08-31 11:35:41 +02:00
use hash::keccak;
use ethereum_types::{H256, H520, Address, U256};
use bytes::Bytes;
use parking_lot::{Mutex, RwLock};
2017-02-17 21:38:43 +01:00
use stats::Corpus;
use ethkey::Signature;
2017-02-09 20:41:42 +01:00
use ethsync::LightSync;
use ethcore::ids::BlockId;
use ethcore::miner::MinerService;
use ethcore::client::MiningBlockChainClient;
use ethcore::account_provider::AccountProvider;
Secretstore RPCs + integration (#5439) * ECDKG protocol prototype * added test for enc/dec math * get rid of decryption_session * added licenses * fix after merge * get rid of unused serde dependency * doc * decryption session [without commutative enc] * failed_dec_session * fixed tests * added commen * added more decryption session tests * helper to localize an issue * more computations to localize error * decryption_session::SessionParams * added tests for EC math to localize problem * secretstore network transport * encryption_session_works_over_network * network errors processing * connecting to KeyServer * licenses * get rid of debug println-s * fixed secretstore args * encryption results are stored in KS database * decryption protocol works over network * enc/dec Session traits * fixing warnings * fix after merge * on-chain ACL checker proto * fixed compilation * fixed compilation * finally fixed <odd>-of-N-scheme * temporary commented test * 1-of-N works in math * scheme 1-of-N works * updated AclStorage with real contract ABI * remove unnecessary unsafety * fixed grumbles * wakeup on access denied * encrypt secretstore messages * 'shadow' decryption * fix grumbles * lost files * secretstore cli-options * decryption seccion when ACL check failed on master * disallow regenerating key for existing document * removed obsolete TODO * fix after merge * switched to tokio_io * fix after merge * fix after merge * fix after merge * fix after merge * fix after merge * fixed test * fix after merge * encryption session errors are now fatal * session timeouts * autorestart decryption session * remove sessions on completion * exclude disconnected nodes from decryption session * test for enc/dec session over network with 1 node * remove debug printlns * fixed 1-of-1 scheme * drop for KeyServerHttpListener * Use standard encryption and decryption (as in RPC) * added some tests * moved DEFAULT_MAC to ethcrypto * rpc_secretstore_encrypt_and_decrypt * serialization with "0x" prefix (RPC compatibility) * secretstore RPC API * fix after merge * fixed typo * secretstore_shadowDecrypt RPC * enable secretstore RPCs by default * fixed test * SecStore RPCs available without SecStore feature * fixed grumbles * lost files * added password argument to Parity RPCs * update docs * lost file
2017-05-05 15:57:29 +02:00
use crypto::DEFAULT_MAC;
use transaction::{Action, SignedTransaction, PendingTransaction, Transaction};
2017-11-14 11:38:17 +01:00
use jsonrpc_core::{BoxFuture, Result, Error};
use jsonrpc_core::futures::{future, Future, Poll, Async};
use jsonrpc_core::futures::future::Either;
use v1::helpers::{errors, nonce, TransactionRequest, FilledTransactionRequest, ConfirmationPayload};
use v1::types::{
H256 as RpcH256, H520 as RpcH520, Bytes as RpcBytes,
2016-11-18 11:03:29 +01:00
RichRawTransaction as RpcRichRawTransaction,
ConfirmationPayload as RpcConfirmationPayload,
ConfirmationResponse,
SignRequest as RpcSignRequest,
DecryptRequest as RpcDecryptRequest,
};
pub use self::nonce::Reservations;
/// 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.
2017-05-02 11:39:48 +02:00
fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address, force_nonce: bool)
2017-11-14 11:38:17 +01:00
-> BoxFuture<FilledTransactionRequest>;
/// Sign the given transaction request without dispatching, fetching appropriate nonce.
2017-02-09 21:12:28 +01:00
fn sign(&self, accounts: Arc<AccountProvider>, filled: FilledTransactionRequest, password: SignWith)
2017-11-14 11:38:17 +01:00
-> BoxFuture<WithToken<SignedTransaction>>;
/// Converts a `SignedTransaction` into `RichRawTransaction`
fn enrich(&self, SignedTransaction) -> RpcRichRawTransaction;
/// "Dispatch" a local transaction.
fn dispatch_transaction(&self, signed_transaction: PendingTransaction)
2017-11-14 11:38:17 +01:00
-> Result<H256>;
}
/// A dispatcher which uses references to a client and miner in order to sign
/// requests locally.
#[derive(Debug)]
pub struct FullDispatcher<C, M> {
client: Arc<C>,
miner: Arc<M>,
2017-11-12 12:36:41 +01:00
nonces: Arc<Mutex<nonce::Reservations>>,
gas_price_percentile: usize,
}
impl<C, M> FullDispatcher<C, M> {
/// Create a `FullDispatcher` from Arc references to a client and miner.
2017-11-10 17:11:04 +01:00
pub fn new(
client: Arc<C>,
miner: Arc<M>,
2017-11-12 12:36:41 +01:00
nonces: Arc<Mutex<nonce::Reservations>>,
gas_price_percentile: usize,
2017-11-10 17:11:04 +01:00
) -> Self {
FullDispatcher {
client,
miner,
nonces,
gas_price_percentile,
}
}
}
impl<C, M> Clone for FullDispatcher<C, M> {
fn clone(&self) -> Self {
FullDispatcher {
client: self.client.clone(),
miner: self.miner.clone(),
nonces: self.nonces.clone(),
gas_price_percentile: self.gas_price_percentile,
}
}
}
2017-05-02 11:39:48 +02:00
impl<C: MiningBlockChainClient, M: MinerService> FullDispatcher<C, M> {
fn state_nonce(&self, from: &Address) -> U256 {
self.miner.last_nonce(from).map(|nonce| nonce + U256::one())
.unwrap_or_else(|| self.client.latest_nonce(from))
}
/// Imports transaction to the miner's queue.
2017-11-14 11:38:17 +01:00
pub fn dispatch_transaction(client: &C, miner: &M, signed_transaction: PendingTransaction) -> Result<H256> {
let hash = signed_transaction.transaction.hash();
miner.import_own_transaction(client, signed_transaction)
.map_err(errors::transaction)
.map(|_| hash)
2017-05-02 11:39:48 +02:00
}
}
impl<C: MiningBlockChainClient, M: MinerService> Dispatcher for FullDispatcher<C, M> {
2017-05-02 11:39:48 +02:00
fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address, force_nonce: bool)
2017-11-14 11:38:17 +01:00
-> BoxFuture<FilledTransactionRequest>
{
let request = request;
2017-05-02 11:39:48 +02:00
let from = request.from.unwrap_or(default_sender);
let nonce = if force_nonce {
request.nonce.or_else(|| Some(self.state_nonce(&from)))
} else {
request.nonce
2017-05-02 11:39:48 +02:00
};
Box::new(future::ok(FilledTransactionRequest {
from,
used_default_from: request.from.is_none(),
to: request.to,
nonce,
gas_price: request.gas_price.unwrap_or_else(|| {
default_gas_price(&*self.client, &*self.miner, self.gas_price_percentile)
}),
gas: request.gas.unwrap_or_else(|| self.miner.sensible_gas_limit()),
value: request.value.unwrap_or_else(|| 0.into()),
data: request.data.unwrap_or_else(Vec::new),
condition: request.condition,
}))
}
2017-02-09 21:12:28 +01:00
fn sign(&self, accounts: Arc<AccountProvider>, filled: FilledTransactionRequest, password: SignWith)
2017-11-14 11:38:17 +01:00
-> BoxFuture<WithToken<SignedTransaction>>
{
let chain_id = self.client.signing_chain_id();
if let Some(nonce) = filled.nonce {
return Box::new(future::done(sign_transaction(&*accounts, filled, chain_id, nonce, password)));
}
let state = self.state_nonce(&filled.from);
2017-11-12 12:36:41 +01:00
let reserved = self.nonces.lock().reserve(filled.from, state);
2017-11-09 19:49:34 +01:00
Box::new(ProspectiveSigner::new(accounts, filled, chain_id, reserved, password))
}
fn enrich(&self, signed_transaction: SignedTransaction) -> RpcRichRawTransaction {
let block_number = self.client.best_block_header().number();
RpcRichRawTransaction::from_signed(signed_transaction, block_number, self.client.eip86_transition())
}
2017-11-14 11:38:17 +01:00
fn dispatch_transaction(&self, signed_transaction: PendingTransaction) -> Result<H256> {
Self::dispatch_transaction(&*self.client, &*self.miner, signed_transaction)
2017-02-09 20:22:31 +01:00
}
}
2017-02-26 15:05:33 +01:00
/// Get a recent gas price corpus.
// TODO: this could be `impl Trait`.
pub fn fetch_gas_price_corpus(
sync: Arc<LightSync>,
client: Arc<LightChainClient>,
on_demand: Arc<OnDemand>,
cache: Arc<Mutex<LightDataCache>>,
2017-11-14 11:38:17 +01:00
) -> BoxFuture<Corpus<U256>> {
2017-02-26 15:05:33 +01:00
const GAS_PRICE_SAMPLE_SIZE: usize = 100;
if let Some(cached) = { cache.lock().gas_price_corpus() } {
return Box::new(future::ok(cached))
2017-02-26 15:05:33 +01:00
}
let cache = cache.clone();
let eventual_corpus = sync.with_context(|ctx| {
// get some recent headers with gas used,
// and request each of the blocks from the network.
let block_requests = client.ancestry_iter(BlockId::Latest)
2017-02-26 15:05:33 +01:00
.filter(|hdr| hdr.gas_used() != U256::default())
.take(GAS_PRICE_SAMPLE_SIZE)
.map(|hdr| request::Body(hdr.into()))
.collect::<Vec<_>>();
// when the blocks come in, collect gas prices into a vector
on_demand.request(ctx, block_requests)
.expect("no back-references; therefore all back-references are valid; qed")
.map(|bodies| {
bodies.into_iter().fold(Vec::new(), |mut v, block| {
for t in block.transaction_views().iter() {
v.push(t.gas_price())
}
v
})
2017-02-26 15:05:33 +01:00
})
.map(move |prices| {
// produce a corpus from the vector and cache it.
// It's later used to get a percentile for default gas price.
let corpus: ::stats::Corpus<_> = prices.into();
2017-02-26 15:05:33 +01:00
cache.lock().set_gas_price_corpus(corpus.clone());
corpus
})
});
match eventual_corpus {
Some(corp) => Box::new(corp.map_err(|_| errors::no_light_peers())),
None => Box::new(future::err(errors::network_disabled())),
2017-02-26 15:05:33 +01:00
}
}
/// Returns a eth_sign-compatible hash of data to sign.
/// The data is prepended with special message to prevent
/// chosen-plaintext attacks.
pub fn eth_data_hash(mut data: Bytes) -> H256 {
let mut message_data =
format!("\x19Ethereum Signed Message:\n{}", data.len())
.into_bytes();
message_data.append(&mut data);
2017-08-31 11:35:41 +02:00
keccak(message_data)
}
2017-02-09 20:22:31 +01:00
/// Dispatcher for light clients -- fetches default gas price, next nonce, etc. from network.
#[derive(Clone)]
pub struct LightDispatcher {
2017-02-17 21:38:43 +01:00
/// Sync service.
pub sync: Arc<LightSync>,
/// Header chain client.
pub client: Arc<LightChainClient>,
/// On-demand request service.
pub on_demand: Arc<OnDemand>,
/// Data cache.
pub cache: Arc<Mutex<LightDataCache>>,
/// Transaction queue.
pub transaction_queue: Arc<RwLock<LightTransactionQueue>>,
/// Nonce reservations
2017-11-12 12:36:41 +01:00
pub nonces: Arc<Mutex<nonce::Reservations>>,
/// Gas Price percentile value used as default gas price.
pub gas_price_percentile: usize,
2017-02-09 20:22:31 +01:00
}
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>,
2017-02-17 16:18:31 +01:00
cache: Arc<Mutex<LightDataCache>>,
2017-02-09 20:22:31 +01:00
transaction_queue: Arc<RwLock<LightTransactionQueue>>,
2017-11-12 12:36:41 +01:00
nonces: Arc<Mutex<nonce::Reservations>>,
gas_price_percentile: usize,
2017-02-09 20:22:31 +01:00
) -> Self {
LightDispatcher {
sync,
client,
on_demand,
cache,
transaction_queue,
nonces,
gas_price_percentile,
}
2017-02-09 20:22:31 +01:00
}
2017-02-17 21:38:43 +01:00
/// Get a recent gas price corpus.
// TODO: this could be `impl Trait`.
2017-11-14 11:38:17 +01:00
pub fn gas_price_corpus(&self) -> BoxFuture<Corpus<U256>> {
2017-02-26 15:05:33 +01:00
fetch_gas_price_corpus(
self.sync.clone(),
self.client.clone(),
self.on_demand.clone(),
self.cache.clone(),
)
2017-02-17 21:38:43 +01:00
}
/// Get an account's next nonce.
2017-11-14 11:38:17 +01:00
pub fn next_nonce(&self, addr: Address) -> BoxFuture<U256> {
2017-02-17 21:38:43 +01:00
// fast path where we don't go to network; nonce provided or can be gotten from queue.
let maybe_nonce = self.transaction_queue.read().next_nonce(&addr);
if let Some(nonce) = maybe_nonce {
return Box::new(future::ok(nonce))
2017-02-17 21:38:43 +01:00
}
let best_header = self.client.best_block_header();
let account_start_nonce = self.client.engine().account_start_nonce(best_header.number());
let nonce_future = self.sync.with_context(|ctx| self.on_demand.request(ctx, request::Account {
header: best_header.into(),
2017-02-17 21:38:43 +01:00
address: addr,
}).expect("no back-references; therefore all back-references valid; qed"));
2017-02-17 21:38:43 +01:00
match nonce_future {
Some(x) => Box::new(
x.map(move |acc| acc.map_or(account_start_nonce, |acc| acc.nonce))
.map_err(|_| errors::no_light_peers())
),
None => Box::new(future::err(errors::network_disabled()))
2017-02-17 21:38:43 +01:00
}
}
}
2017-02-09 21:12:28 +01:00
impl Dispatcher for LightDispatcher {
2017-05-02 11:39:48 +02:00
fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address, force_nonce: bool)
2017-11-14 11:38:17 +01:00
-> BoxFuture<FilledTransactionRequest>
2017-02-09 21:12:28 +01:00
{
const DEFAULT_GAS_PRICE: U256 = U256([0, 0, 0, 21_000_000]);
2017-02-13 16:49:01 +01:00
let gas_limit = self.client.best_block_header().gas_limit();
let request_gas_price = request.gas_price.clone();
2017-05-02 11:39:48 +02:00
let request_nonce = request.nonce.clone();
let from = request.from.unwrap_or(default_sender);
2017-02-09 21:12:28 +01:00
let with_gas_price = move |gas_price| {
let request = request;
FilledTransactionRequest {
2017-05-02 11:39:48 +02:00
from: from.clone(),
used_default_from: request.from.is_none(),
to: request.to,
nonce: request.nonce,
gas_price: gas_price,
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,
}
};
2017-02-17 21:38:43 +01:00
// fast path for known gas price.
let gas_price_percentile = self.gas_price_percentile;
2017-05-02 11:39:48 +02:00
let gas_price = match request_gas_price {
Some(gas_price) => Either::A(future::ok(with_gas_price(gas_price))),
None => Either::B(fetch_gas_price_corpus(
2017-02-26 15:05:33 +01:00
self.sync.clone(),
self.client.clone(),
self.on_demand.clone(),
self.cache.clone()
).and_then(move |corp| match corp.percentile(gas_price_percentile) {
Some(percentile) => Ok(*percentile),
None => Ok(DEFAULT_GAS_PRICE), // fall back to default on error.
}).map(with_gas_price))
2017-05-02 11:39:48 +02:00
};
match (request_nonce, force_nonce) {
(_, false) | (Some(_), true) => Box::new(gas_price),
2017-05-02 11:39:48 +02:00
(None, true) => {
let next_nonce = self.next_nonce(from);
Box::new(gas_price.and_then(move |mut filled| next_nonce
2017-05-02 11:39:48 +02:00
.map_err(|_| errors::no_light_peers())
.map(move |nonce| {
filled.nonce = Some(nonce);
filled
})
))
2017-05-02 11:39:48 +02:00
},
}
2017-02-09 21:12:28 +01:00
}
fn sign(&self, accounts: Arc<AccountProvider>, filled: FilledTransactionRequest, password: SignWith)
2017-11-14 11:38:17 +01:00
-> BoxFuture<WithToken<SignedTransaction>>
2017-02-09 21:12:28 +01:00
{
let chain_id = self.client.signing_chain_id();
2017-02-09 21:12:28 +01:00
2017-02-17 21:38:43 +01:00
// fast path for pre-filled nonce.
if let Some(nonce) = filled.nonce {
return Box::new(future::done(sign_transaction(&*accounts, filled, chain_id, nonce, password)))
2017-02-09 21:12:28 +01:00
}
let nonces = self.nonces.clone();
Box::new(self.next_nonce(filled.from)
2017-02-09 21:12:28 +01:00
.map_err(|_| errors::no_light_peers())
.and_then(move |nonce| {
2017-11-12 12:36:41 +01:00
let reserved = nonces.lock().reserve(filled.from, nonce);
ProspectiveSigner::new(accounts, filled, chain_id, reserved, password)
}))
2017-02-09 21:12:28 +01:00
}
fn enrich(&self, signed_transaction: SignedTransaction) -> RpcRichRawTransaction {
let block_number = self.client.best_block_header().number();
RpcRichRawTransaction::from_signed(signed_transaction, block_number, self.client.eip86_transition())
}
2017-11-14 11:38:17 +01:00
fn dispatch_transaction(&self, signed_transaction: PendingTransaction) -> Result<H256> {
2017-02-09 21:12:28 +01:00
let hash = signed_transaction.transaction.hash();
self.transaction_queue.write().import(signed_transaction)
.map_err(errors::transaction)
2017-02-09 21:12:28 +01:00
.map(|_| hash)
}
}
fn sign_transaction(
accounts: &AccountProvider,
filled: FilledTransactionRequest,
chain_id: Option<u64>,
nonce: U256,
password: SignWith,
2017-11-14 11:38:17 +01:00
) -> Result<WithToken<SignedTransaction>> {
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(&filled.from) {
return hardware_signature(accounts, filled.from, t, chain_id).map(WithToken::No)
}
let hash = t.hash(chain_id);
let signature = signature(accounts, filled.from, hash, password)?;
Ok(signature.map(|sig| {
SignedTransaction::new(t.with_signature(sig, chain_id))
.expect("Transaction was signed by AccountsProvider; it never produces invalid signatures; qed")
}))
}
#[derive(Debug, Clone, Copy)]
enum ProspectiveSignerState {
TryProspectiveSign,
WaitForNonce,
Finish,
}
struct ProspectiveSigner {
accounts: Arc<AccountProvider>,
filled: FilledTransactionRequest,
chain_id: Option<u64>,
reserved: nonce::Reserved,
password: SignWith,
state: ProspectiveSignerState,
2017-11-14 11:38:17 +01:00
prospective: Option<Result<WithToken<SignedTransaction>>>,
ready: Option<nonce::Ready>,
}
impl ProspectiveSigner {
pub fn new(
accounts: Arc<AccountProvider>,
filled: FilledTransactionRequest,
chain_id: Option<u64>,
reserved: nonce::Reserved,
password: SignWith,
) -> Self {
// If the account is permanently unlocked we can try to sign
// using prospective nonce. This should speed up sending
// multiple subsequent transactions in multi-threaded RPC environment.
let is_unlocked_permanently = accounts.is_unlocked_permanently(&filled.from);
let has_password = password.is_password();
ProspectiveSigner {
accounts,
filled,
chain_id,
reserved,
password,
state: if is_unlocked_permanently || has_password {
ProspectiveSignerState::TryProspectiveSign
} else {
ProspectiveSignerState::WaitForNonce
},
prospective: None,
ready: None,
}
}
2017-11-14 11:38:17 +01:00
fn sign(&self, nonce: &U256) -> Result<WithToken<SignedTransaction>> {
sign_transaction(
&*self.accounts,
self.filled.clone(),
self.chain_id,
*nonce,
self.password.clone()
)
}
fn poll_reserved(&mut self) -> Poll<nonce::Ready, Error> {
self.reserved.poll().map_err(|_| errors::internal("Nonce reservation failure", ""))
}
}
impl Future for ProspectiveSigner {
type Item = WithToken<SignedTransaction>;
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
use self::ProspectiveSignerState::*;
loop {
match self.state {
TryProspectiveSign => {
// Try to poll reserved, it might be ready.
match self.poll_reserved()? {
Async::NotReady => {
self.state = WaitForNonce;
self.prospective = Some(self.sign(self.reserved.prospective_value()));
},
Async::Ready(nonce) => {
self.state = Finish;
self.prospective = Some(self.sign(nonce.value()));
self.ready = Some(nonce);
},
}
},
WaitForNonce => {
let nonce = try_ready!(self.poll_reserved());
let result = match (self.prospective.take(), nonce.matches_prospective()) {
(Some(prospective), true) => prospective,
_ => self.sign(nonce.value()),
};
self.state = Finish;
self.prospective = Some(result);
self.ready = Some(nonce);
},
Finish => {
if let (Some(result), Some(nonce)) = (self.prospective.take(), self.ready.take()) {
// Mark nonce as used on successful signing
return result.map(move |tx| {
nonce.mark_used();
Async::Ready(tx)
})
} else {
panic!("Poll after ready.");
}
}
}
}
}
}
2017-02-08 20:44:40 +01:00
/// Single-use account token.
pub type AccountToken = String;
2016-11-30 16:11:41 +01:00
2017-02-08 20:44:40 +01:00
/// Values used to unlock accounts for signing.
2016-11-30 16:11:41 +01:00
#[derive(Debug, Clone, PartialEq)]
pub enum SignWith {
2017-02-08 20:44:40 +01:00
/// Nothing -- implies the account is already unlocked.
2016-11-30 16:11:41 +01:00
Nothing,
2017-02-08 20:44:40 +01:00
/// Unlock with password.
2016-11-30 16:11:41 +01:00
Password(String),
2017-02-08 20:44:40 +01:00
/// Unlock with single-use token.
2016-11-30 16:11:41 +01:00
Token(AccountToken),
}
impl SignWith {
fn is_password(&self) -> bool {
if let SignWith::Password(_) = *self {
true
} else {
false
}
}
}
2017-02-08 20:44:40 +01:00
/// A value, potentially accompanied by a signing token.
2016-11-30 16:11:41 +01:00
#[derive(Debug)]
pub enum WithToken<T: Debug> {
2017-02-08 20:44:40 +01:00
/// No token.
2016-11-30 16:11:41 +01:00
No(T),
2017-02-08 20:44:40 +01:00
/// With token.
2016-11-30 16:11:41 +01:00
Yes(T, AccountToken),
}
2016-11-30 17:05:31 +01:00
impl<T: Debug> Deref for WithToken<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match *self {
WithToken::No(ref v) => v,
WithToken::Yes(ref v, _) => v,
}
}
}
2016-11-30 16:11:41 +01:00
impl<T: Debug> WithToken<T> {
2017-02-08 20:44:40 +01:00
/// Map the value with the given closure, preserving the token.
2016-11-30 16:11:41 +01:00
pub fn map<S, F>(self, f: F) -> WithToken<S> where
S: Debug,
F: FnOnce(T) -> S,
{
match self {
WithToken::No(v) => WithToken::No(f(v)),
WithToken::Yes(v, token) => WithToken::Yes(f(v), token),
}
}
2017-02-08 20:44:40 +01:00
/// Convert into inner value, ignoring possible token.
2016-11-30 16:11:41 +01:00
pub fn into_value(self) -> T {
match self {
WithToken::No(v) => v,
2016-11-30 17:05:31 +01:00
WithToken::Yes(v, _) => v,
2016-11-30 16:11:41 +01:00
}
}
/// 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))
}
}
2016-11-30 16:11:41 +01:00
}
impl<T: Debug> From<(T, AccountToken)> for WithToken<T> {
fn from(tuple: (T, AccountToken)) -> Self {
WithToken::Yes(tuple.0, tuple.1)
}
}
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),
}
}
}
2017-02-08 20:44:40 +01:00
/// Execute a confirmation payload.
pub fn execute<D: Dispatcher + 'static>(
dispatcher: D,
2017-02-09 21:12:28 +01:00
accounts: Arc<AccountProvider>,
payload: ConfirmationPayload,
pass: SignWith
2017-11-14 11:38:17 +01:00
) -> BoxFuture<WithToken<ConfirmationResponse>> {
match payload {
ConfirmationPayload::SendTransaction(request) => {
let condition = request.condition.clone().map(Into::into);
Box::new(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)))
}))
},
ConfirmationPayload::SignTransaction(request) => {
Box::new(dispatcher.sign(accounts, request, pass)
.map(move |result| result
.map(move |tx| dispatcher.enrich(tx))
2016-11-30 16:11:41 +01:00
.map(ConfirmationResponse::SignTransaction)
))
},
ConfirmationPayload::EthSignMessage(address, data) => {
if accounts.is_hardware_address(&address) {
return Box::new(future::err(errors::unsupported("Signing via hardware wallets is not supported.", None)));
}
let hash = eth_data_hash(data);
let res = signature(&accounts, address, hash, pass)
2016-11-30 16:11:41 +01:00
.map(|result| result
2017-05-11 12:18:20 +02:00
.map(|rsv| H520(rsv.into_electrum()))
2016-11-30 16:11:41 +01:00
.map(RpcH520::from)
.map(ConfirmationResponse::Signature)
);
Box::new(future::done(res))
},
ConfirmationPayload::Decrypt(address, data) => {
if accounts.is_hardware_address(&address) {
return Box::new(future::err(errors::unsupported("Decrypting via hardware wallets is not supported.", None)));
}
2017-02-09 21:12:28 +01:00
let res = decrypt(&accounts, address, data, pass)
2016-11-30 16:11:41 +01:00
.map(|result| result
.map(RpcBytes)
.map(ConfirmationResponse::Decrypt)
);
Box::new(future::done(res))
},
}
}
2017-11-14 11:38:17 +01:00
fn signature(accounts: &AccountProvider, address: Address, hash: H256, password: SignWith) -> Result<WithToken<Signature>> {
2016-11-30 16:11:41 +01:00
match password.clone() {
SignWith::Nothing => accounts.sign(address, None, hash).map(WithToken::No),
SignWith::Password(pass) => accounts.sign(address, Some(pass), hash).map(WithToken::No),
SignWith::Token(token) => accounts.sign_with_token(address, token, hash).map(Into::into),
}.map_err(|e| match password {
SignWith::Nothing => errors::signing(e),
_ => errors::password(e),
})
}
2017-02-10 14:31:17 +01:00
// obtain a hardware signature from the given account.
fn hardware_signature(accounts: &AccountProvider, address: Address, t: Transaction, chain_id: Option<u64>)
2017-11-14 11:38:17 +01:00
-> Result<SignedTransaction>
2017-02-10 14:31:17 +01:00
{
debug_assert!(accounts.is_hardware_address(&address));
2017-02-10 14:31:17 +01:00
let mut stream = rlp::RlpStream::new();
t.rlp_append_unsigned_transaction(&mut stream, chain_id);
Trezor Support (#6403) * Copy modal from keepkey branch and generalize The keepkey PinMatrix modal needs to be the same for Trezor, but we should probably try to keep it general since it can be used for both. * Add trezor communication code This is a result of much trial-and-error and a couple of dead-ends in how to communicate and wire everything up. Code here is still a bit WIP with lots of debug prints and stuff. The test works though, it is possible to sign a transaction. * Extend the basic lib to allow Trezor This is kind of ugly and needs some cleanup and generalization. I’ve just copy-pasted some things to bring in the trezor wallets. I’ve also had to add a lock to the USB API so that only one thing talks to the USB at once. * Add RPC plumbing needed We need to be able to get “locked” devices from the frontend to figure out if we’re going to display the PinMatrix or not. Then we need to be able to send a pin to a device. * Add logic to query backend for Trezor and display PinMatrix There’s a bug somewhere here because signing a transaction fails if you take too long to press the confirm button on the device. * Change back to paritytech branch As my fork has been merged in. * Converting spaces to tabs, as it should be * Incorporate correct handling of EIP-155 Turns out the Trezor was adjusting the v part of the signature, and we’re already doing that so it was done twice. * Some circular logic here that was incorrect BE-encoded U256 is almost the same as RLP encoded without the size-byte, except for <u8 sized values. What’s really done is BE-encoded U256 and then left-trimmed to the smallest size. Kind of obvious in hindsight. * Resolve issue where not clicking fast enough fails The device will not repeat a ButtonRequest when you read from it, so you need to have a blocking `read` for whatever amount of time that you want to give the user to click. You could also have a shorter timeout but keep retrying for some amount of time, but it would amount to the same thing. * Scan after pin entry to make accepting it faster * Remove ability to cancel pin request * Some slight cleanup * Probe for the correct HID Version to determine padding * Move the PinMatrix from Accounts to Application * Removing unused dependencies * Mistake in copying over stuff from keepkey branch * Simplify FormattedMessage * Move generated code to external crate * Remove ethcore-util dependency * Fix broken import in test This test is useless without a connected Trezor, not sure how to make it useful without one. * Merge branch 'master' into fh-4500-trezor-support # Conflicts: # rpc/src/v1/helpers/dispatch.rs * Ignore test that can't be run without trezor device * Fixing grumbles * Avoiding owning data in RPC method * Checking for overflow in v part of signature * s/network_id/chain_id * Propagating an error from the HID Api * Condensing code a little bit * Fixing UI. * Debugging trezor. * Minor styling tweak * Make message type into an actual type This makes the message type that the RPC message accepts into an actual type as opposed to just a string, based on feedback. Although I’m not 100% sure this has actually improved the situation. Overall I think the hardware wallet interface needs some refactoring love. * Split the trezor RPC endpoint It’s split into two more generic endpoints that should be suitable for any hardware wallets with the same behavior to sit behind. * Reflect RPC method split in javascript * Fix bug with pin entry * Fix deadlock for Ledger * Avoid having a USB lock in just listing locked wallets * Fix javascript issue (see #6509) * Replace Mutex with RwLock * Update Ledger test * Fix typo causing faulty signatures (sometimes) * *Actually* fix tests * Update git submodule Needed to make tests pass * Swap line orders to prevent possible deadlock * Make setPinMatrixRequest an @action
2017-09-14 19:28:43 +02:00
let signature = accounts.sign_with_hardware(address, &t, chain_id, &stream.as_raw())
2017-02-10 14:31:17 +01:00
.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, chain_id))
2017-02-10 14:31:17 +01:00
.map_err(|e| {
debug!(target: "miner", "Hardware wallet has produced invalid signature: {}", e);
errors::account("Invalid signature generated", e)
})
}
2017-11-14 11:38:17 +01:00
fn decrypt(accounts: &AccountProvider, address: Address, msg: Bytes, password: SignWith) -> Result<WithToken<Bytes>> {
2016-11-30 16:11:41 +01:00
match password.clone() {
SignWith::Nothing => accounts.decrypt(address, None, &DEFAULT_MAC, &msg).map(WithToken::No),
SignWith::Password(pass) => accounts.decrypt(address, Some(pass), &DEFAULT_MAC, &msg).map(WithToken::No),
SignWith::Token(token) => accounts.decrypt_with_token(address, token, &DEFAULT_MAC, &msg).map(Into::into),
}.map_err(|e| match password {
SignWith::Nothing => errors::signing(e),
_ => errors::password(e),
2016-11-30 16:11:41 +01:00
})
}
2017-02-10 02:44:02 +01:00
/// Extract the default gas price from a client and miner.
pub fn default_gas_price<C, M>(client: &C, miner: &M, percentile: usize) -> U256 where
C: MiningBlockChainClient,
M: MinerService,
{
client.gas_price_corpus(100).percentile(percentile).cloned().unwrap_or_else(|| miner.sensible_gas_price())
}
/// Convert RPC confirmation payload to signer confirmation payload.
/// May need to resolve in the future to fetch things like gas price.
2017-11-14 11:38:17 +01:00
pub fn from_rpc<D>(payload: RpcConfirmationPayload, default_account: Address, dispatcher: &D) -> BoxFuture<ConfirmationPayload>
where D: Dispatcher
{
match payload {
RpcConfirmationPayload::SendTransaction(request) => {
Box::new(dispatcher.fill_optional_fields(request.into(), default_account, false)
.map(ConfirmationPayload::SendTransaction))
},
RpcConfirmationPayload::SignTransaction(request) => {
Box::new(dispatcher.fill_optional_fields(request.into(), default_account, false)
.map(ConfirmationPayload::SignTransaction))
},
RpcConfirmationPayload::Decrypt(RpcDecryptRequest { address, msg }) => {
Box::new(future::ok(ConfirmationPayload::Decrypt(address.into(), msg.into())))
},
RpcConfirmationPayload::EthSignMessage(RpcSignRequest { address, data }) => {
Box::new(future::ok(ConfirmationPayload::EthSignMessage(address.into(), data.into())))
},
}
}