// Copyright 2015-2020 Parity Technologies (UK) Ltd. // This file is part of OpenEthereum. // OpenEthereum 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. // OpenEthereum 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 OpenEthereum. If not, see . //! Utilities and helpers for transaction dispatch. mod full; mod prospective_signer; #[cfg(any(test, feature = "accounts"))] mod signing; #[cfg(not(any(test, feature = "accounts")))] mod signing { use super::*; use v1::helpers::errors; /// Dummy signer implementation #[derive(Debug, Clone)] pub struct Signer; impl Signer { /// Create new instance of dummy signer (accept any AccountProvider) pub fn new(_ap: T) -> Self { Signer } } impl super::Accounts for Signer { fn sign_transaction( &self, _filled: FilledTransactionRequest, _chain_id: Option, _nonce: U256, _password: SignWith, ) -> Result> { Err(errors::account("Signing unsupported", "See #9997")) } fn sign_message( &self, _address: Address, _password: SignWith, _hash: SignMessage, ) -> Result> { Err(errors::account("Signing unsupported", "See #9997")) } fn decrypt( &self, _address: Address, _password: SignWith, _data: Bytes, ) -> Result> { Err(errors::account("Signing unsupported", "See #9997")) } fn supports_prospective_signing(&self, _address: &Address, _password: &SignWith) -> bool { false } fn default_account(&self) -> Address { Default::default() } fn is_unlocked(&self, _address: &Address) -> bool { false } } } pub use self::{full::FullDispatcher, signing::Signer}; pub use v1::helpers::nonce::Reservations; use std::{fmt::Debug, ops::Deref, sync::Arc}; use bytes::Bytes; use crypto::publickey::Signature; use ethcore::{client::BlockChainClient, miner::MinerService}; use ethereum_types::{Address, H256, H520, U256}; use ethkey::Password; use hash::keccak; use types::{ transaction::{PendingTransaction, SignedTransaction}, BlockNumber, }; use jsonrpc_core::{ futures::{future, Future, IntoFuture}, BoxFuture, Error, Result, }; use v1::{ helpers::{ConfirmationPayload, FilledTransactionRequest, TransactionRequest}, types::{ Bytes as RpcBytes, ConfirmationPayload as RpcConfirmationPayload, ConfirmationResponse, DecryptRequest as RpcDecryptRequest, EIP191SignRequest as RpcSignRequest, EthSignRequest as RpcEthSignRequest, RichRawTransaction as RpcRichRawTransaction, }, }; /// 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: IntoFuture /// Fill optional fields of a transaction request, fetching gas price but not nonce. fn fill_optional_fields( &self, request: TransactionRequest, default_sender: Address, force_nonce: bool, ) -> BoxFuture; /// Sign the given transaction request without dispatching, fetching appropriate nonce. fn sign

( &self, filled: FilledTransactionRequest, signer: &Arc, password: SignWith, post_sign: P, ) -> BoxFuture where P: PostSign + 'static, ::Future: Send; /// Converts a `SignedTransaction` into `RichRawTransaction` fn enrich(&self, SignedTransaction) -> RpcRichRawTransaction; /// "Dispatch" a local transaction. fn dispatch_transaction(&self, signed_transaction: PendingTransaction) -> Result; } /// Payload to sign pub enum SignMessage { /// Eth-sign kind data (requires prefixing) Data(Bytes), /// Prefixed data hash Hash(H256), } /// Abstract transaction signer. /// /// NOTE This signer is semi-correct, it's a temporary measure to avoid moving too much code. /// If accounts are ultimately removed all password-dealing endpoints will be wiped out. pub trait Accounts: Send + Sync { /// Sign given filled transaction request for the specified chain_id. fn sign_transaction( &self, filled: FilledTransactionRequest, chain_id: Option, nonce: U256, password: SignWith, ) -> Result>; /// Sign given message. fn sign_message( &self, address: Address, password: SignWith, hash: SignMessage, ) -> Result>; /// Decrypt given message. fn decrypt( &self, address: Address, password: SignWith, data: Bytes, ) -> Result>; /// Returns `true` if the accounts can sign multiple times. fn supports_prospective_signing(&self, address: &Address, password: &SignWith) -> bool; /// Returns default account. fn default_account(&self) -> Address; /// Returns true if account is unlocked (i.e. can sign without a password) fn is_unlocked(&self, address: &Address) -> bool; } /// action to execute after signing /// e.g importing a transaction into the chain pub trait PostSign: Send { /// item that this PostSign returns type Item: Send; /// incase you need to perform async PostSign actions type Out: IntoFuture + Send; /// perform an action with the signed transaction fn execute(self, signer: WithToken) -> Self::Out; } impl PostSign for () { type Item = WithToken; type Out = Result; fn execute(self, signed: WithToken) -> Self::Out { Ok(signed) } } impl PostSign for F where F: FnOnce(WithToken) -> Result, { type Item = T; type Out = Result; fn execute(self, signed: WithToken) -> Self::Out { (self)(signed) } } /// Single-use account token. pub type AccountToken = Password; /// Values used to unlock accounts for signing. #[derive(Clone, PartialEq)] pub enum SignWith { /// Nothing -- implies the account is already unlocked. Nothing, /// Unlock with password. Password(Password), /// Unlock with single-use token. Token(AccountToken), } impl SignWith { #[cfg(any(test, feature = "accounts"))] fn is_password(&self) -> bool { if let SignWith::Password(_) = *self { true } else { false } } } /// A value, potentially accompanied by a signing token. pub enum WithToken { /// No token. No(T), /// With token. Yes(T, AccountToken), } impl Deref for WithToken { type Target = T; fn deref(&self) -> &Self::Target { match *self { WithToken::No(ref v) => v, WithToken::Yes(ref v, _) => v, } } } impl WithToken { /// Map the value with the given closure, preserving the token. pub fn map(self, f: F) -> WithToken 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), } } /// Convert into inner value, ignoring possible token. pub fn into_value(self) -> T { match self { WithToken::No(v) => v, WithToken::Yes(v, _) => v, } } /// Convert the `WithToken` into a tuple. pub fn into_tuple(self) -> (T, Option) { match self { WithToken::No(v) => (v, None), WithToken::Yes(v, token) => (v, Some(token)), } } } impl From<(T, AccountToken)> for WithToken { fn from(tuple: (T, AccountToken)) -> Self { WithToken::Yes(tuple.0, tuple.1) } } impl From<(T, Option)> for WithToken { fn from(tuple: (T, Option)) -> Self { match tuple.1 { Some(token) => WithToken::Yes(tuple.0, token), None => WithToken::No(tuple.0), } } } /// Execute a confirmation payload. pub fn execute( dispatcher: D, signer: &Arc, payload: ConfirmationPayload, pass: SignWith, ) -> BoxFuture> { match payload { ConfirmationPayload::SendTransaction(request) => { let condition = request.condition.clone().map(Into::into); let cloned_dispatcher = dispatcher.clone(); let post_sign = move |with_token_signed: WithToken| { let (signed, token) = with_token_signed.into_tuple(); let signed_transaction = PendingTransaction::new(signed, condition); cloned_dispatcher .dispatch_transaction(signed_transaction) .map(|hash| (hash, token)) }; Box::new( dispatcher .sign(request, &signer, pass, post_sign) .map(|(hash, token)| { WithToken::from((ConfirmationResponse::SendTransaction(hash), token)) }), ) } ConfirmationPayload::SignTransaction(request) => Box::new( dispatcher .sign(request, &signer, pass, ()) .map(move |result| { result .map(move |tx| dispatcher.enrich(tx)) .map(ConfirmationResponse::SignTransaction) }), ), ConfirmationPayload::EthSignMessage(address, data) => { let res = signer .sign_message(address, pass, SignMessage::Data(data)) .map(|result| { result .map(|s| H520(s.into_electrum())) .map(ConfirmationResponse::Signature) }); Box::new(future::done(res)) } ConfirmationPayload::SignMessage(address, data) => { let res = signer .sign_message(address, pass, SignMessage::Hash(data)) .map(|result| { result .map(|rsv| H520(rsv.into_electrum())) .map(ConfirmationResponse::Signature) }); Box::new(future::done(res)) } ConfirmationPayload::Decrypt(address, data) => { let res = signer .decrypt(address, pass, data) .map(|result| result.map(RpcBytes).map(ConfirmationResponse::Decrypt)); Box::new(future::done(res)) } } } /// Returns a eth_sign-compatible hash of data to sign. /// The data is prepended with special message to prevent /// malicious DApps from using the function to sign forged transactions. 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); keccak(message_data) } /// Extract the default gas price from a client and miner. pub fn default_gas_price(client: &C, miner: &M, percentile: usize) -> U256 where C: BlockChainClient, M: MinerService, { client .gas_price_corpus(100) .percentile(percentile) .cloned() .unwrap_or_else(|| miner.sensible_gas_price()) } /// Extract the default priority gas price from a client and miner. pub fn default_max_priority_fee_per_gas( client: &C, miner: &M, percentile: usize, eip1559_transition: BlockNumber, ) -> U256 where C: BlockChainClient, M: MinerService, { client .priority_gas_price_corpus(100, eip1559_transition) .percentile(percentile) .cloned() .unwrap_or_else(|| miner.sensible_max_priority_fee()) } /// 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( payload: RpcConfirmationPayload, default_account: Address, dispatcher: &D, ) -> BoxFuture 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, msg.into())), ), RpcConfirmationPayload::EthSignMessage(RpcEthSignRequest { address, data }) => Box::new( future::ok(ConfirmationPayload::EthSignMessage(address, data.into())), ), RpcConfirmationPayload::EIP191SignMessage(RpcSignRequest { address, data }) => { Box::new(future::ok(ConfirmationPayload::SignMessage(address, data))) } } }