diff --git a/miner/src/miner.rs b/miner/src/miner.rs index d9abb09f4..b40fdf7c8 100644 --- a/miner/src/miner.rs +++ b/miner/src/miner.rs @@ -18,14 +18,14 @@ use rayon::prelude::*; use std::sync::atomic::AtomicBool; use util::*; -use util::keys::store::{AccountService, AccountProvider}; +use util::keys::store::AccountProvider; use ethcore::views::{BlockView, HeaderView}; use ethcore::client::{BlockChainClient, BlockID}; use ethcore::block::{ClosedBlock, IsBlock}; use ethcore::error::*; use ethcore::client::{Executive, Executed, EnvInfo, TransactOptions}; use ethcore::transaction::SignedTransaction; -use ethcore::receipt::{Receipt}; +use ethcore::receipt::Receipt; use ethcore::spec::Spec; use ethcore::engine::Engine; use super::{MinerService, MinerStatus, TransactionQueue, AccountDetails, TransactionImportResult, TransactionOrigin}; @@ -44,7 +44,7 @@ pub struct Miner { extra_data: RwLock, spec: Spec, - accounts: RwLock>>, // TODO: this is horrible since AccountService already contains a single RwLock field. refactor. + accounts: Option>, } impl Default for Miner { @@ -58,7 +58,7 @@ impl Default for Miner { gas_floor_target: RwLock::new(U256::zero()), author: RwLock::new(Address::default()), extra_data: RwLock::new(Vec::new()), - accounts: RwLock::new(None), + accounts: None, spec: Spec::new_test(), } } @@ -76,13 +76,13 @@ impl Miner { gas_floor_target: RwLock::new(U256::zero()), author: RwLock::new(Address::default()), extra_data: RwLock::new(Vec::new()), - accounts: RwLock::new(None), + accounts: None, spec: spec, }) } /// Creates new instance of miner - pub fn with_accounts(force_sealing: bool, spec: Spec, accounts: Arc) -> Arc { + pub fn with_accounts(force_sealing: bool, spec: Spec, accounts: Arc) -> Arc { Arc::new(Miner { transaction_queue: Mutex::new(TransactionQueue::new()), force_sealing: force_sealing, @@ -92,7 +92,7 @@ impl Miner { gas_floor_target: RwLock::new(U256::zero()), author: RwLock::new(Address::default()), extra_data: RwLock::new(Vec::new()), - accounts: RwLock::new(Some(accounts)), + accounts: Some(accounts), spec: spec, }) } @@ -177,9 +177,8 @@ impl Miner { if !block.transactions().is_empty() { trace!(target: "miner", "prepare_sealing: block has transaction - attempting internal seal."); // block with transactions - see if we can seal immediately. - let a = self.accounts.read().unwrap(); - let s = self.engine().generate_seal(block.block(), match *a.deref() { - Some(ref x) => Some(x.deref() as &AccountProvider), + let s = self.engine().generate_seal(block.block(), match self.accounts { + Some(ref x) => Some(&**x), None => None, }); if let Some(seal) = s { diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 80a856aca..30815b8a8 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -19,151 +19,25 @@ use std::collections::HashMap; use std::sync::Arc; use std::str::FromStr; -use ethcore::client::{BlockChainClient, Client, ClientConfig}; -use ethcore::spec::Genesis; +use ethcore::ids::BlockID; +use ethcore::client::{Client, BlockChainClient, ClientConfig}; +use ethcore::spec::{Genesis, Spec}; use ethcore::block::Block; +use ethcore::views::BlockView; use ethcore::ethereum; -use ethcore::transaction::{Transaction, Action}; -use ethminer::{MinerService, ExternalMiner}; +use ethminer::{Miner, MinerService, ExternalMiner}; use devtools::RandomTempPath; +use util::Hashable; use util::io::IoChannel; -use util::hash::Address; -use util::numbers::{Uint, U256}; +use util::hash::{Address, H256}; +use util::numbers::U256; use util::keys::{AccountProvider, TestAccount, TestAccountProvider}; use jsonrpc_core::IoHandler; use ethjson::blockchain::BlockChain; use v1::traits::eth::Eth; use v1::impls::EthClient; -use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService}; - -struct EthTester { - _client: Arc, - _miner: Arc, - accounts: Arc, - handler: IoHandler, -} - -#[test] -fn harness_works() { - let chain: BlockChain = extract_chain!("BlockchainTests/bcUncleTest"); - chain_harness(chain, |_| {}); -} - -#[test] -fn eth_get_balance() { - let chain = extract_chain!("BlockchainTests/bcWalletTest", "wallet2outOf3txs"); - chain_harness(chain, |tester| { - // final account state - let req_latest = r#"{ - "jsonrpc": "2.0", - "method": "eth_getBalance", - "params": ["0xaaaf5374fce5edbc8e2a8697c15331677e6ebaaa", "latest"], - "id": 1 - }"#; - let res_latest = r#"{"jsonrpc":"2.0","result":"0x09","id":1}"#.to_owned(); - assert_eq!(tester.handler.handle_request(req_latest).unwrap(), res_latest); - - // non-existant account - let req_new_acc = r#"{ - "jsonrpc": "2.0", - "method": "eth_getBalance", - "params": ["0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"], - "id": 3 - }"#; - - let res_new_acc = r#"{"jsonrpc":"2.0","result":"0x00","id":3}"#.to_owned(); - assert_eq!(tester.handler.handle_request(req_new_acc).unwrap(), res_new_acc); - }); -} - -#[test] -fn eth_block_number() { - let chain = extract_chain!("BlockchainTests/bcRPC_API_Test"); - chain_harness(chain, |tester| { - let req_number = r#"{ - "jsonrpc": "2.0", - "method": "eth_blockNumber", - "params": [], - "id": 1 - }"#; - - let res_number = r#"{"jsonrpc":"2.0","result":"0x20","id":1}"#.to_owned(); - assert_eq!(tester.handler.handle_request(req_number).unwrap(), res_number); - }); -} - -#[cfg(test)] -#[test] -fn eth_transaction_count() { - let chain = extract_chain!("BlockchainTests/bcRPC_API_Test"); - chain_harness(chain, |tester| { - let address = tester.accounts.new_account("123").unwrap(); - let secret = tester.accounts.account_secret(&address).unwrap(); - - let req_before = r#"{ - "jsonrpc": "2.0", - "method": "eth_getTransactionCount", - "params": [""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", "latest"], - "id": 15 - }"#; - - let res_before = r#"{"jsonrpc":"2.0","result":"0x00","id":15}"#; - - assert_eq!(tester.handler.handle_request(&req_before).unwrap(), res_before); - - 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![] - }.sign(&secret); - - let req_send_trans = r#"{ - "jsonrpc": "2.0", - "method": "eth_sendTransaction", - "params": [{ - "from": ""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", - "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", - "gas": "0x76c0", - "gasPrice": "0x9184e72a000", - "value": "0x9184e72a" - }], - "id": 16 - }"#; - - let res_send_trans = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":16}"#; - - // dispatch the transaction. - assert_eq!(tester.handler.handle_request(&req_send_trans).unwrap(), res_send_trans); - - // we have submitted the transaction -- but this shouldn't be reflected in a "latest" query. - let req_after_latest = r#"{ - "jsonrpc": "2.0", - "method": "eth_getTransactionCount", - "params": [""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", "latest"], - "id": 17 - }"#; - - let res_after_latest = r#"{"jsonrpc":"2.0","result":"0x00","id":17}"#; - - assert_eq!(&tester.handler.handle_request(&req_after_latest).unwrap(), res_after_latest); - - // the pending transactions should have been updated. - let req_after_pending = r#"{ - "jsonrpc": "2.0", - "method": "eth_getTransactionCount", - "params": [""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", "pending"], - "id": 18 - }"#; - - let res_after_pending = r#"{"jsonrpc":"2.0","result":"0x01","id":18}"#; - - assert_eq!(&tester.handler.handle_request(&req_after_pending).unwrap(), res_after_pending); - }); -} +use v1::tests::helpers::{TestSyncProvider, Config}; fn account_provider() -> Arc { let mut accounts = HashMap::new(); @@ -179,51 +53,303 @@ fn sync_provider() -> Arc { })) } -fn miner_service() -> Arc { - Arc::new(TestMinerService::default()) +fn miner_service(spec: Spec, accounts: Arc) -> Arc { + Miner::with_accounts(true, spec, accounts) } -// given a blockchain, this harness will create an EthClient wrapping it -// which tests can pass specially crafted requests to. -fn chain_harness(chain: BlockChain, mut cb: F) -> U - where F: FnMut(&EthTester) -> U { +fn make_spec(chain: &BlockChain) -> Spec { let genesis = Genesis::from(chain.genesis()); let mut spec = ethereum::new_frontier_test(); let state = chain.pre_state.clone().into(); spec.set_genesis_state(state); spec.overwrite_genesis_params(genesis); assert!(spec.is_state_root_valid()); + spec +} - let dir = RandomTempPath::new(); - let client = Client::new(ClientConfig::default(), spec, dir.as_path(), IoChannel::disconnected()).unwrap(); - let sync_provider = sync_provider(); - let miner_service = miner_service(); - let account_provider = account_provider(); - let external_miner = Arc::new(ExternalMiner::default()); +struct EthTester { + _miner: Arc, + client: Arc, + accounts: Arc, + handler: IoHandler, +} - for b in &chain.blocks_rlp() { - if Block::is_good(&b) { - let _ = client.import_block(b.clone()); - client.flush_queue(); - client.import_verified_blocks(&IoChannel::disconnected()); +impl EthTester { + fn from_chain(chain: &BlockChain) -> Self { + let tester = Self::from_spec_provider(|| make_spec(chain)); + + for b in &chain.blocks_rlp() { + if Block::is_good(&b) { + let _ = tester.client.import_block(b.clone()); + tester.client.flush_queue(); + tester.client.import_verified_blocks(&IoChannel::disconnected()); + } + } + + tester.client.flush_queue(); + + assert!(tester.client.chain_info().best_block_hash == chain.best_block.clone().into()); + tester + } + + fn from_spec_provider(spec_provider: F) -> Self + where F: Fn() -> Spec { + + let dir = RandomTempPath::new(); + let client = Client::new(ClientConfig::default(), spec_provider(), dir.as_path(), IoChannel::disconnected()).unwrap(); + let sync_provider = sync_provider(); + let account_provider = account_provider(); + let miner_service = miner_service(spec_provider(), account_provider.clone()); + let external_miner = Arc::new(ExternalMiner::default()); + + let eth_client = EthClient::new( + &client, + &sync_provider, + &account_provider, + &miner_service, + &external_miner + ); + + let handler = IoHandler::new(); + let delegate = eth_client.to_delegate(); + handler.add_delegate(delegate); + + EthTester { + _miner: miner_service, + client: client, + accounts: account_provider, + handler: handler, + } + } +} + +#[test] +fn harness_works() { + let chain: BlockChain = extract_chain!("BlockchainTests/bcUncleTest"); + let _ = EthTester::from_chain(&chain); +} + +#[test] +fn eth_get_balance() { + let chain = extract_chain!("BlockchainTests/bcWalletTest", "wallet2outOf3txs"); + let tester = EthTester::from_chain(&chain); + // final account state + let req_latest = r#"{ + "jsonrpc": "2.0", + "method": "eth_getBalance", + "params": ["0xaaaf5374fce5edbc8e2a8697c15331677e6ebaaa", "latest"], + "id": 1 + }"#; + let res_latest = r#"{"jsonrpc":"2.0","result":"0x09","id":1}"#.to_owned(); + assert_eq!(tester.handler.handle_request(req_latest).unwrap(), res_latest); + + // non-existant account + let req_new_acc = r#"{ + "jsonrpc": "2.0", + "method": "eth_getBalance", + "params": ["0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"], + "id": 3 + }"#; + + let res_new_acc = r#"{"jsonrpc":"2.0","result":"0x00","id":3}"#.to_owned(); + assert_eq!(tester.handler.handle_request(req_new_acc).unwrap(), res_new_acc); +} + +#[test] +fn eth_block_number() { + let chain = extract_chain!("BlockchainTests/bcRPC_API_Test"); + let tester = EthTester::from_chain(&chain); + let req_number = r#"{ + "jsonrpc": "2.0", + "method": "eth_blockNumber", + "params": [], + "id": 1 + }"#; + + let res_number = r#"{"jsonrpc":"2.0","result":"0x20","id":1}"#.to_owned(); + assert_eq!(tester.handler.handle_request(req_number).unwrap(), res_number); +} + +// a frontier-like test with an expanded gas limit and balance on known account. +const TRANSACTION_COUNT_SPEC: &'static [u8] = br#"{ + "name": "Frontier (Test)", + "engine": { + "Ethash": { + "params": { + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", + "frontierCompatibilityModeLimit": "0xffffffffffffffff" + } + } + }, + "params": { + "accountStartNonce": "0x00", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x50000", + "networkID" : "0x1" + }, + "genesis": { + "seal": { + "ethereum": { + "nonce": "0x0000000000000042", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "difficulty": "0x400000000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa", + "gasLimit": "0x50000" + }, + "accounts": { + "0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, + "0000000000000000000000000000000000000002": { "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, + "0000000000000000000000000000000000000003": { "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, + "0000000000000000000000000000000000000004": { "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, + "faa34835af5c2ea724333018a515fbb7d5bc0b33": { "balance": "10000000000000", "nonce": "0" } + } +} +"#; + +#[test] +fn eth_transaction_count() { + use util::crypto::Secret; + + let address = Address::from_str("faa34835af5c2ea724333018a515fbb7d5bc0b33").unwrap(); + let secret = Secret::from_str("8a283037bb19c4fed7b1c569e40c7dcff366165eb869110a1b11532963eb9cb2").unwrap(); + + let tester = EthTester::from_spec_provider(|| Spec::load(TRANSACTION_COUNT_SPEC)); + tester.accounts.accounts.write().unwrap().insert(address, TestAccount { + unlocked: false, + password: "123".into(), + secret: secret + }); + + let req_before = r#"{ + "jsonrpc": "2.0", + "method": "eth_getTransactionCount", + "params": [""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", "latest"], + "id": 15 + }"#; + + let res_before = r#"{"jsonrpc":"2.0","result":"0x00","id":15}"#; + + assert_eq!(tester.handler.handle_request(&req_before).unwrap(), res_before); + + let req_send_trans = r#"{ + "jsonrpc": "2.0", + "method": "eth_sendTransaction", + "params": [{ + "from": ""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", + "gas": "0x30000", + "gasPrice": "0x01", + "value": "0x9184e72a" + }], + "id": 16 + }"#; + + // dispatch the transaction. + tester.handler.handle_request(&req_send_trans).unwrap(); + + // we have submitted the transaction -- but this shouldn't be reflected in a "latest" query. + let req_after_latest = r#"{ + "jsonrpc": "2.0", + "method": "eth_getTransactionCount", + "params": [""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", "latest"], + "id": 17 + }"#; + + let res_after_latest = r#"{"jsonrpc":"2.0","result":"0x00","id":17}"#; + + assert_eq!(&tester.handler.handle_request(&req_after_latest).unwrap(), res_after_latest); + + // the pending transactions should have been updated. + let req_after_pending = r#"{ + "jsonrpc": "2.0", + "method": "eth_getTransactionCount", + "params": [""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", "pending"], + "id": 18 + }"#; + + let res_after_pending = r#"{"jsonrpc":"2.0","result":"0x01","id":18}"#; + + assert_eq!(&tester.handler.handle_request(&req_after_pending).unwrap(), res_after_pending); +} + +fn verify_transaction_counts(name: String, chain: BlockChain) { + struct PanicHandler(String); + impl Drop for PanicHandler { + fn drop(&mut self) { + if ::std::thread::panicking() { + println!("Test failed: {}", self.0); + } } } - assert!(client.chain_info().best_block_hash == chain.best_block.into()); + let _panic = PanicHandler(name); - let eth_client = EthClient::new(&client, &sync_provider, &account_provider, - &miner_service, &external_miner); + fn by_hash(hash: H256, count: usize, id: &mut usize) -> (String, String) { + let req = r#"{ + "jsonrpc": "2.0", + "method": "eth_getBlockTransactionCountByHash", + "params": [ + ""#.to_owned() + format!("0x{:?}", hash).as_ref() + r#"" + ], + "id": "# + format!("{}", *id).as_ref() + r#" + }"#; - let handler = IoHandler::new(); - let delegate = eth_client.to_delegate(); - handler.add_delegate(delegate); + let res = r#"{"jsonrpc":"2.0","result":""#.to_owned() + + format!("0x{:02x}", count).as_ref() + + r#"","id":"# + + format!("{}", *id).as_ref() + r#"}"#; + *id += 1; + (req, res) + } - let tester = EthTester { - _miner: miner_service, - _client: client, - accounts: account_provider, - handler: handler, - }; + fn by_number(num: u64, count: usize, id: &mut usize) -> (String, String) { + let req = r#"{ + "jsonrpc": "2.0", + "method": "eth_getBlockTransactionCountByNumber", + "params": [ + "#.to_owned() + &::serde_json::to_string(&U256::from(num)).unwrap() + r#" + ], + "id": "# + format!("{}", *id).as_ref() + r#" + }"#; - cb(&tester) + let res = r#"{"jsonrpc":"2.0","result":""#.to_owned() + + format!("0x{:02x}", count).as_ref() + + r#"","id":"# + + format!("{}", *id).as_ref() + r#"}"#; + *id += 1; + (req, res) + } + + let tester = EthTester::from_chain(&chain); + + let mut id = 1; + for b in chain.blocks_rlp().iter().filter(|b| Block::is_good(b)).map(|b| BlockView::new(b)) { + let count = b.transactions_count(); + + let hash = b.sha3(); + let number = b.header_view().number(); + + let (req, res) = by_hash(hash, count, &mut id); + assert_eq!(tester.handler.handle_request(&req), Some(res)); + + // uncles can share block numbers, so skip them. + if tester.client.block_hash(BlockID::Number(number)) == Some(hash) { + let (req, res) = by_number(number, count, &mut id); + assert_eq!(tester.handler.handle_request(&req), Some(res)); + } + } } + +register_test!(eth_transaction_count_1, verify_transaction_counts, "BlockchainTests/bcWalletTest"); +register_test!(eth_transaction_count_2, verify_transaction_counts, "BlockchainTests/bcTotalDifficultyTest"); +register_test!(eth_transaction_count_3, verify_transaction_counts, "BlockchainTests/bcGasPricerTest"); \ No newline at end of file diff --git a/rpc/src/v1/tests/mod.rs b/rpc/src/v1/tests/mod.rs index 3455bfd1f..a48727475 100644 --- a/rpc/src/v1/tests/mod.rs +++ b/rpc/src/v1/tests/mod.rs @@ -13,11 +13,15 @@ pub mod helpers; // extract the chain with that name. This will panic if no chain by that name // is found. macro_rules! extract_chain { - ($file:expr, $name:expr) => {{ + (iter $file:expr) => {{ const RAW_DATA: &'static [u8] = include_bytes!(concat!("../../../../ethcore/res/ethereum/tests/", $file, ".json")); + ::ethjson::blockchain::Test::load(RAW_DATA).unwrap().into_iter() + }}; + + ($file:expr, $name:expr) => {{ let mut chain = None; - for (name, c) in ::ethjson::blockchain::Test::load(RAW_DATA).unwrap() { + for (name, c) in extract_chain!(iter $file) { if name == $name { chain = Some(c); break; @@ -27,14 +31,41 @@ macro_rules! extract_chain { }}; ($file:expr) => {{ - const RAW_DATA: &'static [u8] = - include_bytes!(concat!("../../../../ethcore/res/ethereum/tests/", $file, ".json")); - - ::ethjson::blockchain::Test::load(RAW_DATA) - .unwrap().into_iter().next().unwrap().1 + extract_chain!(iter $file).next().unwrap().1 }}; } +macro_rules! register_test { + ($name:ident, $cb:expr, $file:expr) => { + #[test] + fn $name() { + for (name, chain) in extract_chain!(iter $file) { + $cb(name, chain); + } + } + }; + + (heavy $name:ident, $cb:expr, $file:expr) => { + #[test] + #[cfg(feature = "test-heavy")] + fn $name() { + for (name, chain) in extract_chain!(iter $file) { + $cb(name, chain); + } + } + }; + + (ignore $name:ident, $cb:expr, $file:expr) => { + #[test] + #[ignore] + fn $name() { + for (name, chain) in extract_chain!(iter $file) { + $cb(name, chain); + } + } + }; +} + #[cfg(test)] mod mocked; #[cfg(test)] diff --git a/util/src/keys/store.rs b/util/src/keys/store.rs index 3402596d3..f6f41745f 100644 --- a/util/src/keys/store.rs +++ b/util/src/keys/store.rs @@ -87,7 +87,7 @@ struct AccountUnlock { } /// Basic account management trait -pub trait AccountProvider : Send + Sync { +pub trait AccountProvider: Send + Sync { /// Lists all accounts fn accounts(&self) -> Result, ::std::io::Error>; /// Unlocks account with the password provided @@ -325,7 +325,7 @@ impl SecretStore { ret } - /// Returns secret for unlocked account. + /// Returns secret for locked account. pub fn locked_account_secret(&self, account: &Address, pass: &str) -> Result { let secret_id = try!(self.account(&account).ok_or(SigningError::NoAccount)); self.get(&secret_id, pass).or_else(|e| Err(match e {