diff --git a/ethcore/src/json_tests/state.rs b/ethcore/src/json_tests/state.rs index 4c001007a..1ed7dcadb 100644 --- a/ethcore/src/json_tests/state.rs +++ b/ethcore/src/json_tests/state.rs @@ -19,6 +19,7 @@ use tests::helpers::*; use pod_state::*; use state_diff::*; use ethereum; +use ethjson; fn do_json_test(json_data: &[u8]) -> Vec { json_chain_test(json_data, ChainEra::Frontier) @@ -26,18 +27,14 @@ fn do_json_test(json_data: &[u8]) -> Vec { pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec { init_log(); - - let json = Json::from_str(::std::str::from_utf8(json_data).unwrap()).expect("Json is invalid"); + let tests = ethjson::state::Test::load(json_data).unwrap(); let mut failed = Vec::new(); - let engine = match era { ChainEra::Frontier => ethereum::new_mainnet_like(), ChainEra::Homestead => ethereum::new_homestead_test(), }.to_engine().unwrap(); - flushln!(""); - - for (name, test) in json.as_object().unwrap() { + for (name, test) in tests.into_iter() { let mut fail = false; { let mut fail_unless = |cond: bool| if !cond && !fail { @@ -49,16 +46,13 @@ pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec { flush!(" - {}...", name); - let t = SignedTransaction::from_json(&test["transaction"]); - let env = EnvInfo::from_json(&test["env"]); - let _out = Bytes::from_json(&test["out"]); - let post_state_root = xjson!(&test["postStateRoot"]); - let pre = PodState::from_json(&test["pre"]); - let post = PodState::from_json(&test["post"]); - let logs: Vec<_> = test["logs"].as_array().unwrap().iter().map(&LogEntry::from_json).collect(); + let transaction = test.transaction.into(); + let post_state_root = test.post_state_root.into(); + let env = test.env.into(); + let pre: PodState = test.pre_state.into(); + let post: PodState = test.post_state.into(); + let logs: Vec = test.logs.into_iter().map(Into::into).collect(); - //println!("Transaction: {:?}", t); - //println!("Env: {:?}", env); let calc_post = sec_trie_root(post.get().iter().map(|(k, v)| (k.to_vec(), v.rlp())).collect()); if fail_unless(post_state_root == calc_post) { @@ -69,7 +63,7 @@ pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec { let mut state = state_result.reference_mut(); state.populate_from(pre); state.commit(); - let res = state.apply(&env, engine.deref(), &t, false); + let res = state.apply(&env, engine.deref(), &transaction, false); if fail_unless(state.root() == &post_state_root) { println!("!!! {}: State mismatch (got: {}, expect: {}):", name, state.root(), post_state_root); @@ -88,12 +82,12 @@ pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec { } } } + if !fail { flushln!("ok"); } - // TODO: Add extra APIs for output - //if fail_unless(out == r.) } + println!("!!! {:?} tests from failed.", failed.len()); failed } diff --git a/ethcore/src/log_entry.rs b/ethcore/src/log_entry.rs index 63e07730e..e0443cc0d 100644 --- a/ethcore/src/log_entry.rs +++ b/ethcore/src/log_entry.rs @@ -19,6 +19,7 @@ use util::*; use basic_types::LogBloom; use header::BlockNumber; +use ethjson; /// A record of execution for a `LOG` operation. #[derive(Default, Debug, Clone, PartialEq, Eq)] @@ -65,6 +66,16 @@ impl LogEntry { } } +impl From for LogEntry { + fn from(l: ethjson::state::Log) -> Self { + LogEntry { + address: l.address.into(), + topics: l.topics.into_iter().map(Into::into).collect(), + data: l.data.into(), + } + } +} + impl FromJson for LogEntry { /// Convert given JSON object to a LogEntry. fn from_json(json: &Json) -> LogEntry { diff --git a/ethcore/src/transaction.rs b/ethcore/src/transaction.rs index 02507da21..db110f8c8 100644 --- a/ethcore/src/transaction.rs +++ b/ethcore/src/transaction.rs @@ -20,6 +20,7 @@ use util::*; use error::*; use evm::Schedule; use header::BlockNumber; +use ethjson; #[derive(Debug, Clone, PartialEq, Eq)] /// Transaction action type. @@ -79,6 +80,23 @@ impl Transaction { } } +impl From for SignedTransaction { + fn from(t: ethjson::state::Transaction) -> Self { + let to: Option<_> = t.to.into(); + Transaction { + nonce: t.nonce.into(), + gas_price: t.gas_price.into(), + gas: t.gas_limit.into(), + action: match to { + Some(to) => Action::Call(to.into()), + None => Action::Create + }, + value: t.value.into(), + data: t.data.into(), + }.sign(&t.secret.into()) + } +} + impl FromJson for SignedTransaction { #[cfg_attr(feature="dev", allow(single_char_pattern))] fn from_json(json: &Json) -> SignedTransaction { diff --git a/json/src/lib.rs.in b/json/src/lib.rs.in index aff685cb7..61f556e7c 100644 --- a/json/src/lib.rs.in +++ b/json/src/lib.rs.in @@ -26,3 +26,4 @@ pub mod blockchain; pub mod spec; pub mod vm; pub mod maybe; +pub mod state; diff --git a/json/src/state/log.rs b/json/src/state/log.rs new file mode 100644 index 000000000..5fb3e469b --- /dev/null +++ b/json/src/state/log.rs @@ -0,0 +1,52 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! State test log deserialization. +use hash::{Address, H256, Bloom}; +use bytes::Bytes; + +/// State test log deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct Log { + /// Address. + pub address: Address, + /// Topics. + pub topics: Vec, + /// Data. + pub data: Bytes, + /// Bloom. + pub bloom: Bloom, +} + +#[cfg(test)] +mod tests { + use serde_json; + use state::Log; + + #[test] + fn log_deserialization() { + let s = r#"{ + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "bloom" : "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008800000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000", + "data" : "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "topics" : [ + "0000000000000000000000000000000000000000000000000000000000000000" + ] + }"#; + let _deserialized: Log = serde_json::from_str(s).unwrap(); + // TODO: validate all fields + } +} diff --git a/json/src/state/mod.rs b/json/src/state/mod.rs new file mode 100644 index 000000000..e0bced6ef --- /dev/null +++ b/json/src/state/mod.rs @@ -0,0 +1,29 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! State test deserialization. + +pub mod state; +pub mod transaction; +pub mod test; +pub mod log; + +pub use self::state::State; +pub use self::transaction::Transaction; +pub use self::test::Test; +pub use self::log::Log; +pub use vm::Env as Env; +pub use blockchain::State as AccountState; diff --git a/json/src/state/state.rs b/json/src/state/state.rs new file mode 100644 index 000000000..3ddca43c7 --- /dev/null +++ b/json/src/state/state.rs @@ -0,0 +1,156 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! State test deserialization. + +use bytes::Bytes; +use hash::H256; +use state::{Env, AccountState, Transaction, Log}; + +/// State test deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct State { + /// Environment. + pub env: Env, + /// Output. + #[serde(rename="out")] + pub output: Bytes, + /// Pre state. + #[serde(rename="pre")] + pub pre_state: AccountState, + /// Post state. + #[serde(rename="post")] + pub post_state: AccountState, + /// Post state root. + #[serde(rename="postStateRoot")] + pub post_state_root: H256, + /// Transaction. + pub transaction: Transaction, + /// Logs. + pub logs: Vec +} + +#[cfg(test)] +mod tests { + use serde_json; + use state::State; + + #[test] + fn state_deserialization() { + let s = r#"{ + "env" : { + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "0x0100", + "currentGasLimit" : "0x01c9c380", + "currentNumber" : "0x00", + "currentTimestamp" : "0x01", + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6" + }, + "logs" : [ + ], + "out" : "0x", + "post" : { + "1000000000000000000000000000000000000000" : { + "balance" : "0x0de0b6b3a763ffff", + "code" : "0x6040600060406000600173100000000000000000000000000000000000000162055730f1600055", + "nonce" : "0x00", + "storage" : { + "0x00" : "0x01" + } + }, + "1000000000000000000000000000000000000001" : { + "balance" : "0x0de0b6b3a763ffff", + "code" : "0x604060006040600060027310000000000000000000000000000000000000026203d090f1600155", + "nonce" : "0x00", + "storage" : { + "0x01" : "0x01" + } + }, + "1000000000000000000000000000000000000002" : { + "balance" : "0x02", + "code" : "0x600160025533600455346007553060e6553260e8553660ec553860ee553a60f055", + "nonce" : "0x00", + "storage" : { + "0x02" : "0x01", + "0x04" : "0x1000000000000000000000000000000000000001", + "0x07" : "0x02", + "0xe6" : "0x1000000000000000000000000000000000000002", + "0xe8" : "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "0xec" : "0x40", + "0xee" : "0x21", + "0xf0" : "0x01" + } + }, + "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" : { + "balance" : "0x039455", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + } + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x0de0b6b3a7606bab", + "code" : "0x", + "nonce" : "0x01", + "storage" : { + } + } + }, + "postStateRoot" : "8f8ed2aed2973e159fa5486f47c6ebf15c5058f8e2350286b84b569bc6ce2d25", + "pre" : { + "1000000000000000000000000000000000000000" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x6040600060406000600173100000000000000000000000000000000000000162055730f1600055", + "nonce" : "0x00", + "storage" : { + } + }, + "1000000000000000000000000000000000000001" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x604060006040600060027310000000000000000000000000000000000000026203d090f1600155", + "nonce" : "0x00", + "storage" : { + } + }, + "1000000000000000000000000000000000000002" : { + "balance" : "0x00", + "code" : "0x600160025533600455346007553060e6553260e8553660ec553860ee553a60f055", + "nonce" : "0x00", + "storage" : { + } + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + } + } + }, + "transaction" : { + "data" : "", + "gasLimit" : "0x2dc6c0", + "gasPrice" : "0x01", + "nonce" : "0x00", + "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "to" : "1000000000000000000000000000000000000000", + "value" : "0x00" + } + }"#; + let _deserialized: State = serde_json::from_str(s).unwrap(); + // TODO: validate all fields + } +} diff --git a/json/src/state/test.rs b/json/src/state/test.rs new file mode 100644 index 000000000..0d28232ef --- /dev/null +++ b/json/src/state/test.rs @@ -0,0 +1,43 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! State test deserializer. + +use std::collections::BTreeMap; +use std::io::Read; +use serde_json; +use serde_json::Error; +use state::State; + +/// State test deserializer. +#[derive(Debug, PartialEq, Deserialize)] +pub struct Test(BTreeMap); + +impl IntoIterator for Test { + type Item = as IntoIterator>::Item; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl Test { + /// Loads test from json. + pub fn load(reader: R) -> Result where R: Read { + serde_json::from_reader(reader) + } +} diff --git a/json/src/state/transaction.rs b/json/src/state/transaction.rs new file mode 100644 index 000000000..15626c224 --- /dev/null +++ b/json/src/state/transaction.rs @@ -0,0 +1,65 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! State test transaction deserialization. + +use uint::Uint; +use bytes::Bytes; +use hash::{Address, H256}; +use maybe::MaybeEmpty; + +/// State test transaction deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct Transaction { + /// Transaction data. + pub data: Bytes, + /// Gas limit. + #[serde(rename="gasLimit")] + pub gas_limit: Uint, + /// Gas price. + #[serde(rename="gasPrice")] + pub gas_price: Uint, + /// Nonce. + pub nonce: Uint, + /// Secret key. + #[serde(rename="secretKey")] + pub secret: H256, + /// To. + pub to: MaybeEmpty
, + /// Value. + pub value: Uint +} + +#[cfg(test)] +mod tests { + use serde_json; + use state::Transaction; + + #[test] + fn transaction_deserialization() { + let s = r#"{ + "data" : "", + "gasLimit" : "0x2dc6c0", + "gasPrice" : "0x01", + "nonce" : "0x00", + "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "to" : "1000000000000000000000000000000000000000", + "value" : "0x00" + }"#; + let _deserialized: Transaction = serde_json::from_str(s).unwrap(); + // TODO: validate all fields + } +} diff --git a/miner/src/lib.rs b/miner/src/lib.rs index db13ed776..c7a02bdc4 100644 --- a/miner/src/lib.rs +++ b/miner/src/lib.rs @@ -104,6 +104,9 @@ pub trait MinerService : Send + Sync { /// Get the sealing work package and if `Some`, apply some transform. fn map_sealing_work(&self, chain: &BlockChainClient, f: F) -> Option where F: FnOnce(&ClosedBlock) -> T; + + /// Query pending transactions for hash + fn transaction(&self, hash: &H256) -> Option; } /// Mining status diff --git a/miner/src/miner.rs b/miner/src/miner.rs index b424626bd..bf38d6a42 100644 --- a/miner/src/miner.rs +++ b/miner/src/miner.rs @@ -217,6 +217,11 @@ impl MinerService for Miner { transaction_queue.pending_hashes() } + fn transaction(&self, hash: &H256) -> Option { + let queue = self.transaction_queue.lock().unwrap(); + queue.find(hash) + } + fn update_sealing(&self, chain: &BlockChainClient) { if self.sealing_enabled.load(atomic::Ordering::Relaxed) { let current_no = chain.chain_info().best_block_number; diff --git a/miner/src/transaction_queue.rs b/miner/src/transaction_queue.rs index 9abe80f1c..8f2855836 100644 --- a/miner/src/transaction_queue.rs +++ b/miner/src/transaction_queue.rs @@ -504,6 +504,11 @@ impl TransactionQueue { .collect() } + /// Finds transaction in the queue by hash (if any) + pub fn find(&self, hash: &H256) -> Option { + match self.by_hash.get(hash) { Some(transaction_ref) => Some(transaction_ref.transaction.clone()), None => None } + } + /// Removes all elements (in any state) from the queue pub fn clear(&mut self) { self.current.clear(); diff --git a/parity/main.rs b/parity/main.rs index 731bba9a1..e9833fd38 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -39,6 +39,8 @@ extern crate rpassword; #[cfg(feature = "rpc")] extern crate ethcore_rpc as rpc; +use std::io::{BufRead, BufReader}; +use std::fs::File; use std::net::{SocketAddr, IpAddr}; use std::env; use std::process::exit; @@ -89,6 +91,11 @@ Protocol Options: [default: $HOME/.web3/keys]. --identity NAME Specify your node's name. +Account Options: + --unlock ACCOUNT Unlock ACCOUNT for the duration of the execution. + --password FILE Provide a file containing a password for unlocking + an account. + Networking Options: --port PORT Override the port on which the node should listen [default: 30303]. @@ -176,6 +183,8 @@ struct Args { flag_chain: String, flag_db_path: String, flag_identity: String, + flag_unlock: Vec, + flag_password: Vec, flag_cache: Option, flag_keys_path: String, flag_bootnodes: Option, @@ -490,6 +499,28 @@ impl Configuration { } } + fn account_service(&self) -> AccountService { + // Secret Store + let passwords = self.args.flag_password.iter().flat_map(|filename| { + BufReader::new(&File::open(filename).unwrap_or_else(|_| die!("{} Unable to read password file. Ensure it exists and permissions are correct.", filename))) + .lines() + .map(|l| l.unwrap()) + .collect::>() + .into_iter() + }).collect::>(); + + let account_service = AccountService::new(); + for d in &self.args.flag_unlock { + let a = Address::from_str(clean_0x(&d)).unwrap_or_else(|_| { + die!("{}: Invalid address for --unlock. Must be 40 hex characters, without the 0x at the beginning.", d) + }); + if passwords.iter().find(|p| account_service.unlock_account_no_expire(&a, p).is_ok()).is_none() { + die!("No password given to unlock account {}. Pass the password using `--password`.", a); + } + } + account_service + } + #[cfg_attr(feature="dev", allow(useless_format))] fn execute_client(&self) { // Setup panic handler @@ -504,6 +535,9 @@ impl Configuration { let net_settings = self.net_settings(&spec); let sync_config = self.sync_config(&spec); + // Secret Store + let account_service = Arc::new(self.account_service()); + // Build client let mut service = ClientService::start(self.client_config(), spec, net_settings, &Path::new(&self.path())).unwrap(); panic_handler.forward_from(&service); @@ -519,9 +553,6 @@ impl Configuration { // Sync let sync = EthSync::register(service.network(), sync_config, client.clone(), miner.clone()); - // Secret Store - let account_service = Arc::new(AccountService::new()); - // Setup rpc if self.args.flag_jsonrpc || self.args.flag_rpc { let url = format!("{}:{}", diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 2a27764bc..f2e4da64e 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -348,7 +348,13 @@ impl Eth for EthClient fn transaction_by_hash(&self, params: Params) -> Result { from_params::<(H256,)>(params) - .and_then(|(hash,)| self.transaction(TransactionId::Hash(hash))) + .and_then(|(hash,)| { + let miner = take_weak!(self.miner); + match miner.transaction(&hash) { + Some(pending_tx) => to_value(&Transaction::from(pending_tx)), + None => self.transaction(TransactionId::Hash(hash)) + } + }) } fn transaction_by_block_hash_and_index(&self, params: Params) -> Result { @@ -471,11 +477,11 @@ impl Eth for EthClient Ok(_) => to_value(&hash), Err(e) => { warn!("Error sending transaction: {:?}", e); - to_value(&U256::zero()) + to_value(&H256::zero()) } } }, - Err(_) => { to_value(&U256::zero()) } + Err(_) => { to_value(&H256::zero()) } } }) } @@ -498,11 +504,11 @@ impl Eth for EthClient Ok(_) => to_value(&hash), Err(e) => { warn!("Error sending transaction: {:?}", e); - to_value(&U256::zero()) + to_value(&H256::zero()) } } }, - Err(_) => { to_value(&U256::zero()) } + Err(_) => { to_value(&H256::zero()) } } }) } diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index a4b0e4448..40748d990 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -53,7 +53,7 @@ struct EthTester { pub client: Arc, pub sync: Arc, _accounts_provider: Arc, - _miner: Arc, + miner: Arc, hashrates: Arc>>, pub io: IoHandler, } @@ -73,7 +73,7 @@ impl Default for EthTester { client: client, sync: sync, _accounts_provider: ap, - _miner: miner, + miner: miner, io: io, hashrates: hashrates, } @@ -258,6 +258,27 @@ fn rpc_eth_transaction_count_by_number_pending() { assert_eq!(EthTester::default().io.handle_request(request), Some(response.to_owned())); } +#[test] +fn rpc_eth_pending_transaction_by_hash() { + use util::*; + use ethcore::transaction::*; + + let tester = EthTester::default(); + { + let tx: SignedTransaction = decode(&FromHex::from_hex("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap()); + tester.miner.pending_transactions.lock().unwrap().insert(H256::zero(), tx); + } + + let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"from":"0x0f65fe9276bc9a24ae7083ae28e2660ef72df99e","gas":"0x5208","gasPrice":"0x01","hash":"0x41df922fd0d4766fcc02e161f8295ec28522f329ae487f14d811e4b64c8d6e31","input":"0x","nonce":"0x00","to":"0x095e7baea6a6c7c4c2dfeb977efac326af552d87","transactionIndex":null,"value":"0x0a"},"id":1}"#; + let request = r#"{ + "jsonrpc": "2.0", + "method": "eth_getTransactionByHash", + "params": ["0x0000000000000000000000000000000000000000000000000000000000000000"], + "id": 1 + }"#; + assert_eq!(tester.io.handle_request(request), Some(response.to_owned())); +} + #[test] fn rpc_eth_uncle_count_by_block_hash() { diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 9c0d4ac8d..517f2deb5 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -30,6 +30,8 @@ pub struct TestMinerService { pub imported_transactions: RwLock>, /// Latest closed block. pub latest_closed_block: Mutex>, + /// Pre-existed pending transactions + pub pending_transactions: Mutex>, } impl Default for TestMinerService { @@ -37,6 +39,7 @@ impl Default for TestMinerService { TestMinerService { imported_transactions: RwLock::new(Vec::new()), latest_closed_block: Mutex::new(None), + pending_transactions: Mutex::new(HashMap::new()), } } } @@ -70,6 +73,10 @@ impl MinerService for TestMinerService { fn map_sealing_work(&self, _chain: &BlockChainClient, _f: F) -> Option where F: FnOnce(&ClosedBlock) -> T { unimplemented!(); } + fn transaction(&self, hash: &H256) -> Option { + self.pending_transactions.lock().unwrap().get(hash).and_then(|tx_ref| Some(tx_ref.clone())) + } + /// Submit `seal` as a valid solution for the header of `pow_hash`. /// Will check the seal, but not actually insert the block into the chain. fn submit_seal(&self, _chain: &BlockChainClient, _pow_hash: H256, _seal: Vec) -> Result<(), Error> { unimplemented!(); } diff --git a/rpc/src/v1/types/transaction.rs b/rpc/src/v1/types/transaction.rs index d809d19b4..8a46d5e15 100644 --- a/rpc/src/v1/types/transaction.rs +++ b/rpc/src/v1/types/transaction.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . use util::numbers::*; -use ethcore::transaction::{LocalizedTransaction, Action}; +use ethcore::transaction::{LocalizedTransaction, Action, SignedTransaction}; use v1::types::{Bytes, OptionalValue}; #[derive(Debug, Default, Serialize)] @@ -58,6 +58,27 @@ impl From for Transaction { } } +impl From for Transaction { + fn from(t: SignedTransaction) -> Transaction { + Transaction { + hash: t.hash(), + nonce: t.nonce, + block_hash: OptionalValue::Null, + block_number: OptionalValue::Null, + transaction_index: OptionalValue::Null, + from: t.sender().unwrap(), + to: match t.action { + Action::Create => OptionalValue::Null, + Action::Call(ref address) => OptionalValue::Value(address.clone()) + }, + value: t.value, + gas_price: t.gas_price, + gas: t.gas, + input: Bytes::new(t.data.clone()) + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/rpc/src/v1/types/transaction_request.rs b/rpc/src/v1/types/transaction_request.rs index 2623fd0ea..f00fa9ef0 100644 --- a/rpc/src/v1/types/transaction_request.rs +++ b/rpc/src/v1/types/transaction_request.rs @@ -102,4 +102,27 @@ mod tests { nonce: None, }); } + + #[test] + fn transaction_request_deserialize_test() { + let s = r#"{ + "from":"0xb5f7502a2807cb23615c7456055e1d65b2508625", + "to":"0x895d32f2db7d01ebb50053f9e48aacf26584fe40", + "data":"0x8595bab1", + "gas":"0x2fd618", + "gasPrice":"0x0ba43b7400" + }"#; + + let deserialized: TransactionRequest = serde_json::from_str(s).unwrap(); + + assert_eq!(deserialized, TransactionRequest { + from: Address::from_str("b5f7502a2807cb23615c7456055e1d65b2508625").unwrap(), + to: Some(Address::from_str("895d32f2db7d01ebb50053f9e48aacf26584fe40").unwrap()), + gas_price: Some(U256::from_str("0ba43b7400").unwrap()), + gas: Some(U256::from_str("2fd618").unwrap()), + value: None, + data: Some(Bytes::new(vec![0x85, 0x95, 0xba, 0xb1])), + nonce: None, + }); + } } diff --git a/util/src/keys/store.rs b/util/src/keys/store.rs index 78540bdb0..f98efd4be 100644 --- a/util/src/keys/store.rs +++ b/util/src/keys/store.rs @@ -75,7 +75,8 @@ pub struct SecretStore { struct AccountUnlock { secret: H256, - expires: DateTime, + /// expiration datetime (None - never) + expires: Option>, } /// Basic account management trait @@ -148,6 +149,11 @@ impl AccountService { pub fn tick(&self) { self.secret_store.write().unwrap().collect_garbage(); } + + /// Unlocks account for use (no expiration of unlock) + pub fn unlock_account_no_expire(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> { + self.secret_store.write().unwrap().unlock_account_with_expiration(account, pass, None) + } } @@ -226,14 +232,23 @@ impl SecretStore { /// Unlocks account for use pub fn unlock_account(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> { + self.unlock_account_with_expiration(account, pass, Some(UTC::now() + Duration::minutes(20))) + } + + /// Unlocks account for use (no expiration of unlock) + pub fn unlock_account_no_expire(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> { + self.unlock_account_with_expiration(account, pass, None) + } + + fn unlock_account_with_expiration(&self, account: &Address, pass: &str, expiration: Option>) -> Result<(), EncryptedHashMapError> { let secret_id = try!(self.account(&account).ok_or(EncryptedHashMapError::UnknownIdentifier)); let secret = try!(self.get(&secret_id, pass)); { let mut write_lock = self.unlocks.write().unwrap(); let mut unlock = write_lock.entry(*account) - .or_insert_with(|| AccountUnlock { secret: secret, expires: UTC::now() }); + .or_insert_with(|| AccountUnlock { secret: secret, expires: Some(UTC::now()) }); unlock.secret = secret; - unlock.expires = UTC::now() + Duration::minutes(20); + unlock.expires = expiration; } Ok(()) } @@ -277,7 +292,7 @@ impl SecretStore { self.directory.collect_garbage(); let utc = UTC::now(); let expired_addresses = garbage_lock.iter() - .filter(|&(_, unlock)| unlock.expires < utc) + .filter(|&(_, unlock)| match unlock.expires { Some(ref expire_val) => expire_val < &utc, _ => false }) .map(|(address, _)| address.clone()).collect::>(); for expired in expired_addresses { garbage_lock.remove(&expired); } @@ -629,7 +644,7 @@ mod tests { let ss_rw = svc.secret_store.write().unwrap(); let mut ua_rw = ss_rw.unlocks.write().unwrap(); let entry = ua_rw.entry(address); - if let Entry::Occupied(mut occupied) = entry { occupied.get_mut().expires = UTC::now() - Duration::minutes(1); } + if let Entry::Occupied(mut occupied) = entry { occupied.get_mut().expires = Some(UTC::now() - Duration::minutes(1)) } } svc.tick();