diff --git a/ethcore/src/account_provider.rs b/ethcore/src/account_provider.rs index 6744c6bd2..a71251727 100644 --- a/ethcore/src/account_provider.rs +++ b/ethcore/src/account_provider.rs @@ -205,6 +205,13 @@ impl AccountProvider { self.unlock_account(account, password, Unlock::Temp) } + /// Checks if given account is unlocked + pub fn is_unlocked(&self, account: A) -> bool where Address: From { + let account = Address::from(account).into(); + let unlocked = self.unlocked.read().unwrap(); + unlocked.get(&account).is_some() + } + /// Signs the message. Account must be unlocked. pub fn sign(&self, account: A, message: M) -> Result where Address: From, Message: From { let account = Address::from(account).into(); diff --git a/parity/main.rs b/parity/main.rs index c6960993a..17d941a5d 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -198,6 +198,12 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig) }); } + // Display warning about using unlock with signer + if conf.args.flag_signer && conf.args.flag_unlock.is_some() { + warn!("Using Trusted Signer and --unlock is not recommended!"); + warn!("NOTE that Signer will not ask you to confirm transactions from unlocked account."); + } + // Secret Store let account_service = Arc::new(conf.account_service()); diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index 22520f266..c0daaa926 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -150,7 +150,7 @@ pub fn setup_rpc(server: T, deps: Arc, apis: ApiSet server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate()); if deps.signer_port.is_some() { - server.add_delegate(EthSigningQueueClient::new(&deps.signer_queue, &deps.client, &deps.miner).to_delegate()); + server.add_delegate(EthSigningQueueClient::new(&deps.signer_queue, &deps.client, &deps.miner, &deps.secret_store).to_delegate()); } else { server.add_delegate(EthSigningUnsafeClient::new(&deps.client, &deps.secret_store, &deps.miner).to_delegate()); } diff --git a/rpc/src/v1/impls/eth_signing.rs b/rpc/src/v1/impls/eth_signing.rs index c5103fd2d..700c679d6 100644 --- a/rpc/src/v1/impls/eth_signing.rs +++ b/rpc/src/v1/impls/eth_signing.rs @@ -43,15 +43,17 @@ fn fill_optional_fields(request: &mut TransactionRequest, client: &C, mine /// Implementation of functions that require signing when no trusted signer is used. pub struct EthSigningQueueClient where C: MiningBlockChainClient, M: MinerService { queue: Weak, + accounts: Weak, client: Weak, miner: Weak, } impl EthSigningQueueClient where C: MiningBlockChainClient, M: MinerService { /// Creates a new signing queue client given shared signing queue. - pub fn new(queue: &Arc, client: &Arc, miner: &Arc) -> Self { + pub fn new(queue: &Arc, client: &Arc, miner: &Arc, accounts: &Arc) -> Self { EthSigningQueueClient { queue: Arc::downgrade(queue), + accounts: Arc::downgrade(accounts), client: Arc::downgrade(client), miner: Arc::downgrade(miner), } @@ -71,9 +73,18 @@ impl EthSigning for EthSigningQueueClient fn send_transaction(&self, params: Params) -> Result { from_params::<(TransactionRequest, )>(params) .and_then(|(mut request, )| { - let queue = take_weak!(self.queue); + let accounts = take_weak!(self.accounts); let (client, miner) = (take_weak!(self.client), take_weak!(self.miner)); + if accounts.is_unlocked(request.from) { + let sender = request.from; + return match sign_and_dispatch(&*client, &*miner, request, &*accounts, sender) { + Ok(hash) => to_value(&hash), + _ => to_value(&H256::zero()), + } + } + + let queue = take_weak!(self.queue); fill_optional_fields(&mut request, &*client, &*miner); let id = queue.add_request(request); let result = id.wait_with_timeout(); diff --git a/rpc/src/v1/tests/mocked/eth_signing.rs b/rpc/src/v1/tests/mocked/eth_signing.rs index a2755ce18..5f3e75d35 100644 --- a/rpc/src/v1/tests/mocked/eth_signing.rs +++ b/rpc/src/v1/tests/mocked/eth_signing.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::str::FromStr; use std::sync::Arc; use jsonrpc_core::IoHandler; use v1::impls::EthSigningQueueClient; @@ -21,12 +22,16 @@ use v1::traits::EthSigning; use v1::helpers::{ConfirmationsQueue, SigningQueue}; use v1::tests::helpers::TestMinerService; use util::{Address, FixedHash}; +use util::numbers::{Uint, U256}; +use ethcore::account_provider::AccountProvider; use ethcore::client::TestBlockChainClient; +use ethcore::transaction::{Transaction, Action}; struct EthSigningTester { pub queue: Arc, pub client: Arc, pub miner: Arc, + pub accounts: Arc, pub io: IoHandler, } @@ -35,13 +40,15 @@ impl Default for EthSigningTester { let queue = Arc::new(ConfirmationsQueue::default()); let client = Arc::new(TestBlockChainClient::default()); let miner = Arc::new(TestMinerService::default()); + let accounts = Arc::new(AccountProvider::transient_provider()); let io = IoHandler::new(); - io.add_delegate(EthSigningQueueClient::new(&queue, &client, &miner).to_delegate()); + io.add_delegate(EthSigningQueueClient::new(&queue, &client, &miner, &accounts).to_delegate()); EthSigningTester { queue: queue, client: client, miner: miner, + accounts: accounts, io: io, } } @@ -78,5 +85,41 @@ fn should_add_transaction_to_queue() { // then assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); assert_eq!(tester.queue.requests().len(), 1); - +} + +#[test] +fn should_dispatch_transaction_if_account_is_unlocked() { + // given + let tester = eth_signing(); + let acc = tester.accounts.new_account("test").unwrap(); + tester.accounts.unlock_account_permanently(acc, "test".into()).unwrap(); + + let t = Transaction { + nonce: U256::zero(), + gas_price: U256::from(0x9184e72a000u64), + gas: U256::from(0x76c0), + action: Action::Call(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), + value: U256::from(0x9184e72au64), + data: vec![] + }; + let signature = tester.accounts.sign(acc, t.hash()).unwrap(); + let t = t.with_signature(signature); + + // when + let request = r#"{ + "jsonrpc": "2.0", + "method": "eth_sendTransaction", + "params": [{ + "from": ""#.to_owned() + format!("0x{:?}", acc).as_ref() + r#"", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", + "gas": "0x76c0", + "gasPrice": "0x9184e72a000", + "value": "0x9184e72a" + }], + "id": 1 + }"#; + let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#; + + // then + assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); }