// 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 . use bytes::ToPretty; use ethereum_types::{Address, H520, U256}; use std::{str::FromStr, sync::Arc}; use accounts::AccountProvider; use ethcore::client::TestBlockChainClient; use parity_runtime::Runtime; use parking_lot::Mutex; use types::transaction::{Action, SignedTransaction, Transaction, TypedTransaction}; use jsonrpc_core::IoHandler; use serde_json; use v1::{ helpers::{ dispatch::{self, eth_data_hash, FullDispatcher}, external_signer::{SignerService, SigningQueue}, nonce, ConfirmationPayload, FilledTransactionRequest, }, metadata::Metadata, tests::helpers::TestMinerService, types::Bytes as RpcBytes, Origin, Signer, SignerClient, }; struct SignerTester { _runtime: Runtime, signer: Arc, accounts: Arc, io: IoHandler, miner: Arc, } fn blockchain_client() -> Arc { let client = TestBlockChainClient::new(); Arc::new(client) } fn accounts_provider() -> Arc { Arc::new(AccountProvider::transient_provider()) } fn miner_service() -> Arc { Arc::new(TestMinerService::default()) } fn signer_tester() -> SignerTester { let runtime = Runtime::with_thread_count(1); let signer = Arc::new(SignerService::new_test(false)); let accounts = accounts_provider(); let account_signer = Arc::new(dispatch::Signer::new(accounts.clone())); let client = blockchain_client(); let miner = miner_service(); let reservations = Arc::new(Mutex::new(nonce::Reservations::new(runtime.executor()))); let dispatcher = FullDispatcher::new(client, miner.clone(), reservations, 50); let mut io = IoHandler::default(); io.extend_with( SignerClient::new(account_signer, dispatcher, &signer, runtime.executor()).to_delegate(), ); SignerTester { _runtime: runtime, signer: signer, accounts: accounts, io: io, miner: miner, } } #[test] fn should_return_list_of_items_to_confirm() { // given let tester = signer_tester(); let _send_future = tester .signer .add_request( ConfirmationPayload::SendTransaction(FilledTransactionRequest { transaction_type: Default::default(), from: Address::from_low_u64_be(1), used_default_from: false, to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), gas_price: Some(U256::from(10_000)), max_fee_per_gas: None, gas: U256::from(10_000_000), value: U256::from(1), data: vec![], nonce: None, condition: None, access_list: None, max_priority_fee_per_gas: None, }), Origin::Unknown, ) .unwrap(); let _sign_future = tester .signer .add_request( ConfirmationPayload::EthSignMessage(Address::from_low_u64_be(1), vec![5].into()), Origin::Unknown, ) .unwrap(); // when let request = r#"{"jsonrpc":"2.0","method":"signer_requestsToConfirm","params":[],"id":1}"#; let response = concat!( r#"{"jsonrpc":"2.0","result":["#, r#"{"id":"0x1","origin":"unknown","payload":{"sendTransaction":{"condition":null,"data":"0x","from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x1"}}},"#, r#"{"id":"0x2","origin":"unknown","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","data":"0x05"}}}"#, r#"],"id":1}"# ); // then assert_eq!( tester.io.handle_request_sync(&request), Some(response.to_owned()) ); } #[test] fn should_reject_transaction_from_queue_without_dispatching() { // given let tester = signer_tester(); let _confirmation_future = tester .signer .add_request( ConfirmationPayload::SendTransaction(FilledTransactionRequest { transaction_type: Default::default(), from: Address::from_low_u64_be(1), used_default_from: false, to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), gas_price: Some(U256::from(10_000)), max_fee_per_gas: None, gas: U256::from(10_000_000), value: U256::from(1), data: vec![], nonce: None, condition: None, access_list: None, max_priority_fee_per_gas: None, }), Origin::Unknown, ) .unwrap(); assert_eq!(tester.signer.requests().len(), 1); // when let request = r#"{"jsonrpc":"2.0","method":"signer_rejectRequest","params":["0x1"],"id":1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; // then assert_eq!( tester.io.handle_request_sync(&request), Some(response.to_owned()) ); assert_eq!(tester.signer.requests().len(), 0); assert_eq!(tester.miner.imported_transactions.lock().len(), 0); } #[test] fn should_not_remove_transaction_if_password_is_invalid() { // given let tester = signer_tester(); let _confirmation_future = tester .signer .add_request( ConfirmationPayload::SendTransaction(FilledTransactionRequest { transaction_type: Default::default(), from: Address::from_low_u64_be(1), used_default_from: false, to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), gas_price: Some(U256::from(10_000)), max_fee_per_gas: None, gas: U256::from(10_000_000), value: U256::from(1), data: vec![], nonce: None, condition: None, access_list: None, max_priority_fee_per_gas: None, }), Origin::Unknown, ) .unwrap(); assert_eq!(tester.signer.requests().len(), 1); // when let request = r#"{"jsonrpc":"2.0","method":"signer_confirmRequest","params":["0x1",{},"xxx"],"id":1}"#; let response = r#"{"jsonrpc":"2.0","error":{"code":-32021,"message":"Account password is invalid or account does not exist.","data":"SStore(InvalidAccount)"},"id":1}"#; // then assert_eq!( tester.io.handle_request_sync(&request), Some(response.to_owned()) ); assert_eq!(tester.signer.requests().len(), 1); } #[test] fn should_not_remove_sign_if_password_is_invalid() { // given let tester = signer_tester(); let _confirmation_future = tester .signer .add_request( ConfirmationPayload::EthSignMessage(Address::from_low_u64_be(0), vec![5].into()), Origin::Unknown, ) .unwrap(); assert_eq!(tester.signer.requests().len(), 1); // when let request = r#"{"jsonrpc":"2.0","method":"signer_confirmRequest","params":["0x1",{},"xxx"],"id":1}"#; let response = r#"{"jsonrpc":"2.0","error":{"code":-32021,"message":"Account password is invalid or account does not exist.","data":"SStore(InvalidAccount)"},"id":1}"#; // then assert_eq!( tester.io.handle_request_sync(&request), Some(response.to_owned()) ); assert_eq!(tester.signer.requests().len(), 1); } #[test] fn should_confirm_transaction_and_dispatch() { //// given let tester = signer_tester(); let address = tester.accounts.new_account(&"test".into()).unwrap(); let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(); let _confirmation_future = tester .signer .add_request( ConfirmationPayload::SendTransaction(FilledTransactionRequest { transaction_type: Default::default(), from: address, used_default_from: false, to: Some(recipient), gas_price: Some(U256::from(10_000)), max_fee_per_gas: None, gas: U256::from(10_000_000), value: U256::from(1), data: vec![], nonce: None, condition: None, access_list: None, max_priority_fee_per_gas: None, }), Origin::Unknown, ) .unwrap(); let t = TypedTransaction::Legacy(Transaction { nonce: U256::zero(), gas_price: U256::from(0x1000), gas: U256::from(0x50505), action: Action::Call(recipient), value: U256::from(0x1), data: vec![], }); tester .accounts .unlock_account_temporarily(address, "test".into()) .unwrap(); let signature = tester .accounts .sign(address, None, t.signature_hash(None)) .unwrap(); let t = t.with_signature(signature, None); assert_eq!(tester.signer.requests().len(), 1); // when let request = r#"{ "jsonrpc":"2.0", "method":"signer_confirmRequest", "params":["0x1", {"gasPrice":"0x1000","gas":"0x50505"}, "test"], "id":1 }"#; let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:x}", t.hash()).as_ref() + r#"","id":1}"#; // then assert_eq!( tester.io.handle_request_sync(&request), Some(response.to_owned()) ); assert_eq!(tester.signer.requests().len(), 0); assert_eq!(tester.miner.imported_transactions.lock().len(), 1); } #[test] fn should_alter_the_sender_and_nonce() { //// given let tester = signer_tester(); let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(); let _confirmation_future = tester .signer .add_request( ConfirmationPayload::SendTransaction(FilledTransactionRequest { transaction_type: Default::default(), from: Address::from_low_u64_be(0), used_default_from: false, to: Some(recipient), gas_price: Some(U256::from(10_000)), max_fee_per_gas: None, gas: U256::from(10_000_000), value: U256::from(1), data: vec![], nonce: Some(10.into()), condition: None, access_list: None, max_priority_fee_per_gas: None, }), Origin::Unknown, ) .unwrap(); let t = TypedTransaction::Legacy(Transaction { nonce: U256::zero(), gas_price: U256::from(0x1000), gas: U256::from(0x50505), action: Action::Call(recipient), value: U256::from(0x1), data: vec![], }); let address = tester.accounts.new_account(&"test".into()).unwrap(); let signature = tester .accounts .sign(address, Some("test".into()), t.signature_hash(None)) .unwrap(); let t = t.with_signature(signature, None); assert_eq!(tester.signer.requests().len(), 1); // when let request = r#"{ "jsonrpc":"2.0", "method":"signer_confirmRequest", "params":["0x1", {"sender":""# .to_owned() + &format!("0x{:x}", address) + r#"","gasPrice":"0x1000","gas":"0x50505"}, "test"], "id":1 }"#; let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + &format!("0x{:x}", t.hash()) + r#"","id":1}"#; // then assert_eq!( tester.io.handle_request_sync(&request), Some(response.to_owned()) ); assert_eq!(tester.signer.requests().len(), 0); assert_eq!(tester.miner.imported_transactions.lock().len(), 1); } #[test] fn should_confirm_transaction_with_token() { // given let tester = signer_tester(); let address = tester.accounts.new_account(&"test".into()).unwrap(); let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(); let _confirmation_future = tester .signer .add_request( ConfirmationPayload::SendTransaction(FilledTransactionRequest { transaction_type: Default::default(), from: address, used_default_from: false, to: Some(recipient), gas_price: Some(U256::from(10_000)), max_fee_per_gas: None, gas: U256::from(10_000_000), value: U256::from(1), data: vec![], nonce: None, condition: None, access_list: None, max_priority_fee_per_gas: None, }), Origin::Unknown, ) .unwrap(); let t = TypedTransaction::Legacy(Transaction { nonce: U256::zero(), gas_price: U256::from(0x1000), gas: U256::from(10_000_000), action: Action::Call(recipient), value: U256::from(0x1), data: vec![], }); let (signature, token) = tester .accounts .sign_with_token(address, "test".into(), t.signature_hash(None)) .unwrap(); let t = t.with_signature(signature, None); assert_eq!(tester.signer.requests().len(), 1); // when let request = r#"{ "jsonrpc":"2.0", "method":"signer_confirmRequestWithToken", "params":["0x1", {"gasPrice":"0x1000"}, ""# .to_owned() + token.as_str() + r#""], "id":1 }"#; let response = r#"{"jsonrpc":"2.0","result":{"result":""#.to_owned() + format!("0x{:x}", t.hash()).as_ref() + r#"","token":""#; // then let result = tester.io.handle_request_sync(&request).unwrap(); assert!( result.starts_with(&response), "Should return correct result. Expected: {:?}, Got: {:?}", response, result ); assert_eq!(tester.signer.requests().len(), 0); assert_eq!(tester.miner.imported_transactions.lock().len(), 1); } #[test] fn should_confirm_transaction_with_rlp() { // given let tester = signer_tester(); let address = tester.accounts.new_account(&"test".into()).unwrap(); let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(); let _confirmation_future = tester .signer .add_request( ConfirmationPayload::SendTransaction(FilledTransactionRequest { transaction_type: Default::default(), from: address, used_default_from: false, to: Some(recipient), gas_price: Some(U256::from(10_000)), max_fee_per_gas: None, gas: U256::from(10_000_000), value: U256::from(1), data: vec![], nonce: None, condition: None, access_list: None, max_priority_fee_per_gas: None, }), Origin::Unknown, ) .unwrap(); let t = TypedTransaction::Legacy(Transaction { nonce: U256::zero(), gas_price: U256::from(0x1000), gas: U256::from(10_000_000), action: Action::Call(recipient), value: U256::from(0x1), data: vec![], }); let signature = tester .accounts .sign(address, Some("test".into()), t.signature_hash(None)) .unwrap(); let t = t.with_signature(signature, None); let rlp = t.encode(); assert_eq!(tester.signer.requests().len(), 1); // when let request = r#"{ "jsonrpc":"2.0", "method":"signer_confirmRequestRaw", "params":["0x1", "0x"# .to_owned() + &rlp.to_hex() + r#""], "id":1 }"#; let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:x}", t.hash()).as_ref() + r#"","id":1}"#; // then assert_eq!( tester.io.handle_request_sync(&request), Some(response.to_owned()) ); assert_eq!(tester.signer.requests().len(), 0); assert_eq!(tester.miner.imported_transactions.lock().len(), 1); } #[test] fn should_return_error_when_sender_does_not_match() { // given let tester = signer_tester(); let address = tester.accounts.new_account(&"test".into()).unwrap(); let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(); let _confirmation_future = tester .signer .add_request( ConfirmationPayload::SendTransaction(FilledTransactionRequest { transaction_type: Default::default(), from: Address::default(), used_default_from: false, to: Some(recipient), gas_price: Some(U256::from(10_000)), max_fee_per_gas: None, gas: U256::from(10_000_000), value: U256::from(1), data: vec![], nonce: None, condition: None, access_list: None, max_priority_fee_per_gas: None, }), Origin::Unknown, ) .unwrap(); let t = TypedTransaction::Legacy(Transaction { nonce: U256::zero(), gas_price: U256::from(0x1000), gas: U256::from(10_000_000), action: Action::Call(recipient), value: U256::from(0x1), data: vec![], }); tester .accounts .unlock_account_temporarily(address, "test".into()) .unwrap(); let signature = tester .accounts .sign(address, None, t.signature_hash(None)) .unwrap(); let t = t.with_signature(signature, None); let rlp = t.encode(); assert_eq!(tester.signer.requests().len(), 1); // when let request = r#"{ "jsonrpc":"2.0", "method":"signer_confirmRequestRaw", "params":["0x1", "0x"# .to_owned() + &rlp.to_hex() + r#""], "id":1 }"#; let response = r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Couldn't parse parameters: Sent transaction does not match the request.","data":"[\"from\"]"},"id":1}"#; // then assert_eq!( tester.io.handle_request_sync(&request), Some(response.to_owned()) ); assert_eq!(tester.signer.requests().len(), 1); } #[test] fn should_confirm_sign_transaction_with_rlp() { // given let tester = signer_tester(); let address = tester.accounts.new_account(&"test".into()).unwrap(); let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(); let _confirmation_future = tester .signer .add_request( ConfirmationPayload::SignTransaction(FilledTransactionRequest { transaction_type: Default::default(), from: address, used_default_from: false, to: Some(recipient), gas_price: Some(U256::from(10_000)), max_fee_per_gas: None, gas: U256::from(10_000_000), value: U256::from(1), data: vec![], nonce: None, condition: None, access_list: None, max_priority_fee_per_gas: None, }), Origin::Unknown, ) .unwrap(); assert_eq!(tester.signer.requests().len(), 1); let t = TypedTransaction::Legacy(Transaction { nonce: U256::zero(), gas_price: U256::from(0x1000), gas: U256::from(10_000_000), action: Action::Call(recipient), value: U256::from(0x1), data: vec![], }); let signature = tester .accounts .sign(address, Some("test".into()), t.signature_hash(None)) .unwrap(); let t = SignedTransaction::new(t.with_signature(signature.clone(), None)).unwrap(); let rlp = t.encode(); // when let request = r#"{ "jsonrpc":"2.0", "method":"signer_confirmRequestRaw", "params":["0x1", "0x"# .to_owned() + &rlp.to_hex() + r#""], "id":1 }"#; let response = r#"{"jsonrpc":"2.0","result":{"#.to_owned() + r#""raw":"0x"# + &rlp.to_hex() + r#"","# + r#""tx":{"# + r#""blockHash":null,"blockNumber":null,"# + &format!( "\"chainId\":{},", t.chain_id().map_or("null".to_owned(), |n| format!("{}", n)) ) + r#""condition":null,"creates":null,"# + &format!("\"from\":\"0x{:x}\",", &address) + r#""gas":"0x989680","gasPrice":"0x1000","# + &format!("\"hash\":\"0x{:x}\",", t.hash()) + r#""input":"0x","# + r#""nonce":"0x0","# + &format!("\"publicKey\":\"0x{:x}\",", t.public_key().unwrap()) + &format!("\"r\":\"0x{:x}\",", U256::from(signature.r())) + &format!("\"raw\":\"0x{}\",", rlp.to_hex()) + &format!("\"s\":\"0x{:x}\",", U256::from(signature.s())) + &format!("\"standardV\":\"0x{:x}\",", U256::from(t.standard_v())) + r#""to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","transactionIndex":null,"# + r#""type":"0x0","# + &format!("\"v\":\"0x{:x}\",", U256::from(t.v())) + r#""value":"0x1""# + r#"}},"id":1}"#; // then assert_eq!( tester.io.handle_request_sync(&request), Some(response.to_owned()) ); assert_eq!(tester.signer.requests().len(), 0); assert_eq!(tester.miner.imported_transactions.lock().len(), 0); } #[test] fn should_confirm_data_sign_with_signature() { // given let tester = signer_tester(); let address = tester.accounts.new_account(&"test".into()).unwrap(); let _confirmation_future = tester .signer .add_request( ConfirmationPayload::EthSignMessage(address, vec![1, 2, 3, 4].into()), Origin::Unknown, ) .unwrap(); assert_eq!(tester.signer.requests().len(), 1); let data_hash = eth_data_hash(vec![1, 2, 3, 4].into()); let signature = H520( tester .accounts .sign(address, Some("test".into()), data_hash) .unwrap() .into_electrum(), ); let signature = format!("{:?}", signature); // when let request = r#"{ "jsonrpc":"2.0", "method":"signer_confirmRequestRaw", "params":["0x1", ""# .to_owned() + &signature + r#""], "id":1 }"#; let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + &signature + r#"","id":1}"#; // then assert_eq!( tester.io.handle_request_sync(&request), Some(response.to_owned()) ); assert_eq!(tester.signer.requests().len(), 0); assert_eq!(tester.miner.imported_transactions.lock().len(), 0); } #[test] fn should_confirm_decrypt_with_phrase() { // given let tester = signer_tester(); let address = tester.accounts.new_account(&"test".into()).unwrap(); let _confirmation_future = tester .signer .add_request( ConfirmationPayload::Decrypt(address, vec![1, 2, 3, 4].into()), Origin::Unknown, ) .unwrap(); assert_eq!(tester.signer.requests().len(), 1); let decrypted = serde_json::to_string(&RpcBytes::new(b"phrase".to_vec())).unwrap(); // when let request = r#"{ "jsonrpc":"2.0", "method":"signer_confirmRequestRaw", "params":["0x1", "# .to_owned() + &decrypted + r#"], "id":1 }"#; let response = r#"{"jsonrpc":"2.0","result":"#.to_owned() + &decrypted + r#","id":1}"#; // then assert_eq!( tester.io.handle_request_sync(&request), Some(response.to_owned()) ); assert_eq!(tester.signer.requests().len(), 0); assert_eq!(tester.miner.imported_transactions.lock().len(), 0); } #[test] fn should_generate_new_token() { // given let tester = signer_tester(); // when let request = r#"{ "jsonrpc":"2.0", "method":"signer_generateAuthorizationToken", "params":[], "id":1 }"#; let response = r#"{"jsonrpc":"2.0","result":"new_token","id":1}"#; // then assert_eq!( tester.io.handle_request_sync(&request), Some(response.to_owned()) ); }