From 56b020987e550ea52f255b37cddaec52fb8c8487 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 24 May 2016 12:21:40 +0200 Subject: [PATCH 01/12] refine tests for call deserialization --- json/src/bytes.rs | 9 +++++++++ json/src/vm/call.rs | 37 ++++++++++++++++++++++++++++++++++--- rpc/src/v1/impls/ethcore.rs | 2 +- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/json/src/bytes.rs b/json/src/bytes.rs index 812b109f7..18dc73844 100644 --- a/json/src/bytes.rs +++ b/json/src/bytes.rs @@ -19,6 +19,7 @@ use rustc_serialize::hex::FromHex; use serde::{Deserialize, Deserializer, Error}; use serde::de::Visitor; +use std::ops::Deref; /// Lenient bytes json deserialization for test json files. #[derive(Default, Debug, PartialEq, Clone)] @@ -30,6 +31,14 @@ impl Into> for Bytes { } } +impl Deref for Bytes { + type Target = Vec; + + fn deref(&self) -> &Vec { + &self.0 + } +} + impl Deserialize for Bytes { fn deserialize(deserializer: &mut D) -> Result where D: Deserializer { diff --git a/json/src/vm/call.rs b/json/src/vm/call.rs index 2fe6265aa..1947fd25c 100644 --- a/json/src/vm/call.rs +++ b/json/src/vm/call.rs @@ -39,16 +39,47 @@ pub struct Call { mod tests { use serde_json; use vm::Call; + use util::numbers::U256; + use uint::Uint; + use util::hash::Address as Hash160; + use hash::Address; + use maybe::MaybeEmpty; + use std::str::FromStr; #[test] - fn call_deserialization() { + fn call_deserialization_empty_dest() { let s = r#"{ "data" : "0x1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff", "destination" : "", "gasLimit" : "0x1748766aa5", "value" : "0x00" }"#; - let _deserialized: Call = serde_json::from_str(s).unwrap(); - // TODO: validate all fields + let call: Call = serde_json::from_str(s).unwrap(); + + assert_eq!(&call.data[..], + &[0x11, 0x11, 0x22, 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, 0x66, 0x77, 0x77, + 0x88, 0x88, 0x99, 0x99, 0x00, 0x00, 0xaa, 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, + 0xee, 0xee, 0xff, 0xff]); + + assert_eq!(call.destination, MaybeEmpty::None); + assert_eq!(call.gas_limit, Uint(U256::from(0x1748766aa5u64))); + assert_eq!(call.value, Uint(U256::from(0))); + } + + #[test] + fn call_deserialization_full_dest() { + let s = r#"{ + "data" : "0x1234", + "destination" : "5a39ed1020c04d4d84539975b893a4e7c53eab6c", + "gasLimit" : "0x1748766aa5", + "value" : "0x00" + }"#; + + let call: Call = serde_json::from_str(s).unwrap(); + + assert_eq!(&call.data[..], &[0x12, 0x34]); + assert_eq!(call.destination, MaybeEmpty::Some(Address(Hash160::from_str("5a39ed1020c04d4d84539975b893a4e7c53eab6c").unwrap()))); + assert_eq!(call.gas_limit, Uint(U256::from(0x1748766aa5u64))); + assert_eq!(call.value, Uint(U256::from(0))); } } diff --git a/rpc/src/v1/impls/ethcore.rs b/rpc/src/v1/impls/ethcore.rs index de458a53f..f5d6f1fda 100644 --- a/rpc/src/v1/impls/ethcore.rs +++ b/rpc/src/v1/impls/ethcore.rs @@ -22,7 +22,7 @@ use std::sync::{Arc, Weak}; use std::ops::Deref; use std::collections::BTreeMap; use jsonrpc_core::*; -use ethminer::{MinerService}; +use ethminer::MinerService; use v1::traits::Ethcore; use v1::types::Bytes; From 152bb6f21b8abde53865ee7080ff2d55dc8d58dc Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 24 May 2016 16:56:09 +0200 Subject: [PATCH 02/12] create integration test harness for eth RPC API --- rpc/Cargo.toml | 4 + rpc/src/lib.rs | 5 ++ rpc/src/v1/tests/eth.rs | 0 rpc/src/v1/tests/helpers/sync_provider.rs | 4 +- rpc/src/v1/tests/integration/eth.rs | 100 ++++++++++++++++++++++ rpc/src/v1/tests/integration/mod.rs | 21 +++++ rpc/src/v1/tests/mod.rs | 2 +- 7 files changed, 133 insertions(+), 3 deletions(-) delete mode 100644 rpc/src/v1/tests/eth.rs create mode 100644 rpc/src/v1/tests/integration/eth.rs create mode 100644 rpc/src/v1/tests/integration/mod.rs diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 2cdbb0a2b..23fef5d06 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -29,6 +29,10 @@ json-ipc-server = { git = "https://github.com/ethcore/json-ipc-server.git" } serde_codegen = { version = "0.7.0", optional = true } syntex = "^0.32.0" +[dev-dependencies] +ethjson = { path = "../json" } +ethcore-devtools = { path = "../devtools" } + [features] default = ["serde_codegen"] nightly = ["serde_macros"] diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 7d9818615..24d58819c 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -33,6 +33,11 @@ extern crate ethminer; extern crate transient_hashmap; extern crate json_ipc_server as ipc; +#[cfg(test)] +extern crate ethjson; +#[cfg(test)] +extern crate ethcore_devtools as devtools; + use std::sync::Arc; use std::net::SocketAddr; use self::jsonrpc_core::{IoHandler, IoDelegate}; diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs deleted file mode 100644 index e69de29bb..000000000 diff --git a/rpc/src/v1/tests/helpers/sync_provider.rs b/rpc/src/v1/tests/helpers/sync_provider.rs index fc81586dd..114b5b08f 100644 --- a/rpc/src/v1/tests/helpers/sync_provider.rs +++ b/rpc/src/v1/tests/helpers/sync_provider.rs @@ -16,9 +16,9 @@ //! Test implementation of SyncProvider. -use util::{U256}; +use util::U256; use ethsync::{SyncProvider, SyncStatus, SyncState}; -use std::sync::{RwLock}; +use std::sync::RwLock; /// TestSyncProvider config. pub struct Config { diff --git a/rpc/src/v1/tests/integration/eth.rs b/rpc/src/v1/tests/integration/eth.rs new file mode 100644 index 000000000..d402f2b08 --- /dev/null +++ b/rpc/src/v1/tests/integration/eth.rs @@ -0,0 +1,100 @@ +// Copyright 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 . + +//! rpc integration tests. +use std::collections::HashMap; +use std::sync::Arc; + +use ethjson::blockchain::test::Test; +use ethcore::client::{BlockChainClient, Client, ClientConfig}; +use ethcore::spec::Genesis; +use ethcore::block::Block; +use ethcore::ethereum; +use ethminer::ExternalMiner; +use devtools::RandomTempPath; +use util::io::IoChannel; +use util::hash::{Address, FixedHash}; +use util::numbers::U256; +use util::keys::{TestAccount, TestAccountProvider}; +use jsonrpc_core::IoHandler; + +use v1::traits::eth::Eth; +use v1::impls::EthClient; +use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService}; + +use super::RPC_CHAIN; + +#[test] +fn harness_works() { + eth_harness(|_| {}); +} + +fn account_provider() -> Arc { + let mut accounts = HashMap::new(); + accounts.insert(Address::from(1), TestAccount::new("test")); + let ap = TestAccountProvider::new(accounts); + Arc::new(ap) +} + +fn sync_provider() -> Arc { + Arc::new(TestSyncProvider::new(Config { + network_id: U256::from(3), + num_peers: 120, + })) +} + +fn miner_service() -> Arc { + Arc::new(TestMinerService::default()) +} + +// this harness will create a handler which tests can send specially-crafted +// JSONRPC requests to. +fn eth_harness(mut cb: F) -> U + where F: FnMut(&IoHandler) -> U { + let chains = Test::load(RPC_CHAIN).unwrap(); + let chain = chains.into_iter().next().unwrap().1; + 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()); + + 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()); + + 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()); + } + } + + assert!(client.chain_info().best_block_hash == chain.best_block.into()); + + 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); + cb(&handler) +} diff --git a/rpc/src/v1/tests/integration/mod.rs b/rpc/src/v1/tests/integration/mod.rs new file mode 100644 index 000000000..8a67d78dd --- /dev/null +++ b/rpc/src/v1/tests/integration/mod.rs @@ -0,0 +1,21 @@ +// Copyright 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 . + +//! Integration tests for the JSONRPC APIs + +mod eth; + +const RPC_CHAIN: &'static [u8] = include_bytes!("../../../../../ethcore/res/ethereum/tests/BlockchainTests/bcRPC_API_Test.json"); \ No newline at end of file diff --git a/rpc/src/v1/tests/mod.rs b/rpc/src/v1/tests/mod.rs index f5e7d1404..78a6a674f 100644 --- a/rpc/src/v1/tests/mod.rs +++ b/rpc/src/v1/tests/mod.rs @@ -5,4 +5,4 @@ pub mod helpers; #[cfg(test)] mod mocked; #[cfg(test)] -mod eth; \ No newline at end of file +mod integration; From d370a86b4325210820d269ed45377ac6580fc43a Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 24 May 2016 19:20:07 +0200 Subject: [PATCH 03/12] More flexible chain extraction, get_balance test --- rpc/src/v1/tests/integration/eth.rs | 42 ++++++++++++++++++++++------- rpc/src/v1/tests/integration/mod.rs | 35 ++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/rpc/src/v1/tests/integration/eth.rs b/rpc/src/v1/tests/integration/eth.rs index d402f2b08..a2640203f 100644 --- a/rpc/src/v1/tests/integration/eth.rs +++ b/rpc/src/v1/tests/integration/eth.rs @@ -18,7 +18,6 @@ use std::collections::HashMap; use std::sync::Arc; -use ethjson::blockchain::test::Test; use ethcore::client::{BlockChainClient, Client, ClientConfig}; use ethcore::spec::Genesis; use ethcore::block::Block; @@ -30,16 +29,43 @@ use util::hash::{Address, FixedHash}; use util::numbers::U256; use util::keys::{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}; -use super::RPC_CHAIN; - #[test] fn harness_works() { - eth_harness(|_| {}); + 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, |handler| { + // 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}"#; + assert_eq!(&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}"#; + assert_eq!(&handler.handle_request(req_new_acc).unwrap(), res_new_acc); + }); } fn account_provider() -> Arc { @@ -60,12 +86,10 @@ fn miner_service() -> Arc { Arc::new(TestMinerService::default()) } -// this harness will create a handler which tests can send specially-crafted -// JSONRPC requests to. -fn eth_harness(mut cb: F) -> U +// 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(&IoHandler) -> U { - let chains = Test::load(RPC_CHAIN).unwrap(); - let chain = chains.into_iter().next().unwrap().1; let genesis = Genesis::from(chain.genesis()); let mut spec = ethereum::new_frontier_test(); let state = chain.pre_state.clone().into(); diff --git a/rpc/src/v1/tests/integration/mod.rs b/rpc/src/v1/tests/integration/mod.rs index 8a67d78dd..4a845c074 100644 --- a/rpc/src/v1/tests/integration/mod.rs +++ b/rpc/src/v1/tests/integration/mod.rs @@ -16,6 +16,37 @@ //! Integration tests for the JSONRPC APIs -mod eth; +// extract a chain from the given JSON file, +// stored in ethcore/res/ethereum/tests/. +// +// usage: +// `extract_chain!("Folder/File")` will load Folder/File.json and extract +// the first block chain stored within. +// +// `extract_chain!("Folder/File", "with_name")` will load Folder/File.json and +// 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) => {{ + const RAW_DATA: &'static [u8] = + include_bytes!(concat!("../../../../../ethcore/res/ethereum/tests/", $file, ".json")); + let mut chain = None; + for (name, c) in ::ethjson::blockchain::Test::load(RAW_DATA).unwrap() { + if name == $name { + chain = Some(c); + break; + } + } + chain.unwrap() + }}; -const RPC_CHAIN: &'static [u8] = include_bytes!("../../../../../ethcore/res/ethereum/tests/BlockchainTests/bcRPC_API_Test.json"); \ No newline at end of file + ($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 + }}; +} + +mod eth; \ No newline at end of file From cf18c4bb0ac89f507deec985d1de69b3b9a8ad7f Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 25 May 2016 12:30:55 +0200 Subject: [PATCH 04/12] make MinerService object-safe --- miner/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/miner/src/lib.rs b/miner/src/lib.rs index e9ac7aba2..a1780efff 100644 --- a/miner/src/lib.rs +++ b/miner/src/lib.rs @@ -107,12 +107,12 @@ pub trait MinerService : Send + Sync { /// Imports transactions to transaction queue. fn import_transactions(&self, transactions: Vec, fetch_account: T) -> Vec> - where T: Fn(&Address) -> AccountDetails; + where T: Fn(&Address) -> AccountDetails, Self: Sized; /// Imports own (node owner) transaction to queue. fn import_own_transaction(&self, chain: &BlockChainClient, transaction: SignedTransaction, fetch_account: T) -> Result - where T: Fn(&Address) -> AccountDetails; + where T: Fn(&Address) -> AccountDetails, Self: Sized; /// Returns hashes of transactions currently in pending fn pending_transactions_hashes(&self) -> Vec; @@ -131,7 +131,8 @@ pub trait MinerService : Send + Sync { fn submit_seal(&self, chain: &BlockChainClient, pow_hash: H256, seal: Vec) -> Result<(), Error>; /// 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; + fn map_sealing_work(&self, chain: &BlockChainClient, f: F) -> Option + where F: FnOnce(&ClosedBlock) -> T, Self: Sized; /// Query pending transactions for hash. fn transaction(&self, hash: &H256) -> Option; From 688790f13f5025d87c532b21109ae6fbdb07906b Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 25 May 2016 12:44:27 +0200 Subject: [PATCH 05/12] re-export AccountProvider trait --- util/src/keys/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/util/src/keys/mod.rs b/util/src/keys/mod.rs index 5d718affc..38fa51eca 100644 --- a/util/src/keys/mod.rs +++ b/util/src/keys/mod.rs @@ -21,4 +21,5 @@ pub mod store; mod geth_import; mod test_account_provider; +pub use self::store::AccountProvider; pub use self::test_account_provider::{TestAccount, TestAccountProvider}; From f67486e31f883926ac2af21867b2e8cb5388ea43 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 25 May 2016 13:22:17 +0200 Subject: [PATCH 06/12] have miner service update the pending nonces on transaction import --- rpc/src/v1/tests/helpers/miner_service.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 5d57d5d61..36296613c 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -115,12 +115,18 @@ impl MinerService for TestMinerService { } /// Imports transactions to transaction queue. - fn import_transactions(&self, transactions: Vec, _fetch_account: T) -> + fn import_transactions(&self, transactions: Vec, fetch_account: T) -> Vec> where T: Fn(&Address) -> AccountDetails { // lets assume that all txs are valid self.imported_transactions.lock().unwrap().extend_from_slice(&transactions); + for transaction in &transactions { + if let Ok(ref sender) = transaction.sender() { + let nonce = self.last_nonce(sender).unwrap_or(fetch_account(sender).nonce); + self.last_nonces.write().unwrap().insert(sender.clone(), nonce + U256::from(1)); + } + } transactions .iter() .map(|_| Ok(TransactionImportResult::Current)) @@ -128,9 +134,16 @@ impl MinerService for TestMinerService { } /// Imports transactions to transaction queue. - fn import_own_transaction(&self, _chain: &BlockChainClient, transaction: SignedTransaction, _fetch_account: T) -> + fn import_own_transaction(&self, chain: &BlockChainClient, transaction: SignedTransaction, _fetch_account: T) -> Result where T: Fn(&Address) -> AccountDetails { + + // keep the pending nonces up to date + if let Ok(ref sender) = transaction.sender() { + let nonce = self.last_nonce(sender).unwrap_or(chain.latest_nonce(sender)); + self.last_nonces.write().unwrap().insert(sender.clone(), nonce + U256::from(1)); + } + // lets assume that all txs are valid self.imported_transactions.lock().unwrap().push(transaction); @@ -200,7 +213,9 @@ impl MinerService for TestMinerService { } fn nonce(&self, _chain: &BlockChainClient, address: &Address) -> U256 { - self.latest_closed_block.lock().unwrap().as_ref().map_or_else(U256::zero, |b| b.block().fields().state.nonce(address).clone()) + // we assume all transactions are in a pending block, ignoring the + // reality of gas limits. + self.last_nonce(address).unwrap_or(U256::zero()) } fn code(&self, _chain: &BlockChainClient, address: &Address) -> Option { From 1de7ea090c3aa27fd3084910c3ce9d79fee57172 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 25 May 2016 13:23:24 +0200 Subject: [PATCH 07/12] add informative comment on transaction::Action --- ethcore/src/types/transaction.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ethcore/src/types/transaction.rs b/ethcore/src/types/transaction.rs index 842decd88..a6b4c1781 100644 --- a/ethcore/src/types/transaction.rs +++ b/ethcore/src/types/transaction.rs @@ -36,6 +36,7 @@ pub enum Action { /// Create creates new contract. Create, /// Calls contract at given address. + /// In the case of a transfer, this is the receiver's address.' Call(Address), } From 4c55e4968ef0fba267e9df2cc433a80133b31b83 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 25 May 2016 13:27:03 +0200 Subject: [PATCH 08/12] add eth_blockNumber, eth_TransactionCount integration tests. also adds an EthTester struct for more test flexibility. --- rpc/src/v1/tests/integration/eth.rs | 123 +++++++++++++++++++++++++--- 1 file changed, 113 insertions(+), 10 deletions(-) diff --git a/rpc/src/v1/tests/integration/eth.rs b/rpc/src/v1/tests/integration/eth.rs index a2640203f..3c897dbb3 100644 --- a/rpc/src/v1/tests/integration/eth.rs +++ b/rpc/src/v1/tests/integration/eth.rs @@ -17,17 +17,19 @@ //! rpc integration tests. use std::collections::HashMap; use std::sync::Arc; +use std::str::FromStr; use ethcore::client::{BlockChainClient, Client, ClientConfig}; use ethcore::spec::Genesis; use ethcore::block::Block; use ethcore::ethereum; -use ethminer::ExternalMiner; +use ethcore::transaction::{Transaction, Action}; +use ethminer::{MinerService, ExternalMiner}; use devtools::RandomTempPath; use util::io::IoChannel; use util::hash::{Address, FixedHash}; -use util::numbers::U256; -use util::keys::{TestAccount, TestAccountProvider}; +use util::numbers::{Uint, U256}; +use util::keys::{AccountProvider, TestAccount, TestAccountProvider}; use jsonrpc_core::IoHandler; use ethjson::blockchain::BlockChain; @@ -35,6 +37,13 @@ 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"); @@ -44,7 +53,7 @@ fn harness_works() { #[test] fn eth_get_balance() { let chain = extract_chain!("BlockchainTests/bcWalletTest", "wallet2outOf3txs"); - chain_harness(chain, |handler| { + chain_harness(chain, |tester| { // final account state let req_latest = r#"{ "jsonrpc": "2.0", @@ -52,8 +61,8 @@ fn eth_get_balance() { "params": ["0xaaaf5374fce5edbc8e2a8697c15331677e6ebaaa", "latest"], "id": 1 }"#; - let res_latest = r#"{"jsonrpc":"2.0","result":"0x09","id":1}"#; - assert_eq!(&handler.handle_request(req_latest).unwrap(), res_latest); + 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#"{ @@ -63,8 +72,94 @@ fn eth_get_balance() { "id": 3 }"#; - let res_new_acc = r#"{"jsonrpc":"2.0","result":"0x00","id":3}"#; - assert_eq!(&handler.handle_request(req_new_acc).unwrap(), res_new_acc); + 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 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![] + }.fake_sign(address.clone()); + + 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 + }"#; + + // 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); }); } @@ -89,7 +184,7 @@ fn miner_service() -> Arc { // 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(&IoHandler) -> U { + where F: FnMut(&EthTester) -> U { let genesis = Genesis::from(chain.genesis()); let mut spec = ethereum::new_frontier_test(); let state = chain.pre_state.clone().into(); @@ -120,5 +215,13 @@ fn chain_harness(chain: BlockChain, mut cb: F) -> U let handler = IoHandler::new(); let delegate = eth_client.to_delegate(); handler.add_delegate(delegate); - cb(&handler) + + let tester = EthTester { + _miner: miner_service, + _client: client, + accounts: account_provider, + handler: handler, + }; + + cb(&tester) } From 7ee23240f08a4ecf5d5f053c45498c76eedae787 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 25 May 2016 15:14:02 +0200 Subject: [PATCH 09/12] fix travis test build --- Cargo.lock | 2 ++ rpc/Cargo.toml | 6 ++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4dbcd1825..1a5b61a3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -326,7 +326,9 @@ dependencies = [ "clippy 0.0.69 (registry+https://github.com/rust-lang/crates.io-index)", "ethash 1.2.0", "ethcore 1.2.0", + "ethcore-devtools 1.2.0", "ethcore-util 1.2.0", + "ethjson 0.1.0", "ethminer 1.2.0", "ethsync 1.2.0", "json-ipc-server 0.1.0 (git+https://github.com/ethcore/json-ipc-server.git)", diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 23fef5d06..61b51cf88 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -19,6 +19,8 @@ ethcore = { path = "../ethcore" } ethash = { path = "../ethash" } ethsync = { path = "../sync" } ethminer = { path = "../miner" } +ethjson = { path = "../json" } +ethcore-devtools = { path = "../devtools" } rustc-serialize = "0.3" transient-hashmap = "0.1" serde_macros = { version = "0.7.0", optional = true } @@ -29,10 +31,6 @@ json-ipc-server = { git = "https://github.com/ethcore/json-ipc-server.git" } serde_codegen = { version = "0.7.0", optional = true } syntex = "^0.32.0" -[dev-dependencies] -ethjson = { path = "../json" } -ethcore-devtools = { path = "../devtools" } - [features] default = ["serde_codegen"] nightly = ["serde_macros"] From e7791c220a3ea76e3d8dd6fb017b8bd9ee856dad Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 26 May 2016 13:26:07 +0200 Subject: [PATCH 10/12] rebase fixes and address style concern --- rpc/src/v1/tests/helpers/miner_service.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 36296613c..600f88508 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -121,11 +121,9 @@ impl MinerService for TestMinerService { // lets assume that all txs are valid self.imported_transactions.lock().unwrap().extend_from_slice(&transactions); - for transaction in &transactions { - if let Ok(ref sender) = transaction.sender() { - let nonce = self.last_nonce(sender).unwrap_or(fetch_account(sender).nonce); - self.last_nonces.write().unwrap().insert(sender.clone(), nonce + U256::from(1)); - } + for sender in transactions.iter().filter_map(|t| t.sender().ok()) { + let nonce = self.last_nonce(&sender).unwrap_or(fetch_account(&sender).nonce); + self.last_nonces.write().unwrap().insert(sender, nonce + U256::from(1)); } transactions .iter() From 9d4cd7b73e657c99e1704033e69f9ab414d9482e Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 26 May 2016 13:30:19 +0200 Subject: [PATCH 11/12] assert the transaction is being signed correctly --- rpc/src/v1/tests/integration/eth.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rpc/src/v1/tests/integration/eth.rs b/rpc/src/v1/tests/integration/eth.rs index 3c897dbb3..386b72cf7 100644 --- a/rpc/src/v1/tests/integration/eth.rs +++ b/rpc/src/v1/tests/integration/eth.rs @@ -99,6 +99,7 @@ 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", @@ -119,7 +120,7 @@ fn eth_transaction_count() { action: Action::Call(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), value: U256::from(0x9184e72au64), data: vec![] - }.fake_sign(address.clone()); + }.sign(&secret); let req_send_trans = r#"{ "jsonrpc": "2.0", @@ -134,8 +135,10 @@ fn eth_transaction_count() { "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. - tester.handler.handle_request(&req_send_trans).unwrap(); + 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#"{ From c021ecd13bd9a0005086be311cd68e7c95ae5528 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 27 May 2016 18:40:48 +0200 Subject: [PATCH 12/12] move "integration" tests out into main module --- rpc/src/v1/tests/{integration => }/eth.rs | 1 - rpc/src/v1/tests/helpers/mod.rs | 2 +- rpc/src/v1/tests/integration/mod.rs | 52 ----------------------- rpc/src/v1/tests/mocked/mod.rs | 3 +- rpc/src/v1/tests/mod.rs | 35 ++++++++++++++- 5 files changed, 37 insertions(+), 56 deletions(-) rename rpc/src/v1/tests/{integration => }/eth.rs (99%) delete mode 100644 rpc/src/v1/tests/integration/mod.rs diff --git a/rpc/src/v1/tests/integration/eth.rs b/rpc/src/v1/tests/eth.rs similarity index 99% rename from rpc/src/v1/tests/integration/eth.rs rename to rpc/src/v1/tests/eth.rs index 386b72cf7..eac5bafcb 100644 --- a/rpc/src/v1/tests/integration/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -110,7 +110,6 @@ fn eth_transaction_count() { 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 { diff --git a/rpc/src/v1/tests/helpers/mod.rs b/rpc/src/v1/tests/helpers/mod.rs index 9b321af98..1b8f9e256 100644 --- a/rpc/src/v1/tests/helpers/mod.rs +++ b/rpc/src/v1/tests/helpers/mod.rs @@ -20,4 +20,4 @@ mod sync_provider; mod miner_service; pub use self::sync_provider::{Config, TestSyncProvider}; -pub use self::miner_service::{TestMinerService}; +pub use self::miner_service::TestMinerService; diff --git a/rpc/src/v1/tests/integration/mod.rs b/rpc/src/v1/tests/integration/mod.rs deleted file mode 100644 index 4a845c074..000000000 --- a/rpc/src/v1/tests/integration/mod.rs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 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 . - -//! Integration tests for the JSONRPC APIs - -// extract a chain from the given JSON file, -// stored in ethcore/res/ethereum/tests/. -// -// usage: -// `extract_chain!("Folder/File")` will load Folder/File.json and extract -// the first block chain stored within. -// -// `extract_chain!("Folder/File", "with_name")` will load Folder/File.json and -// 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) => {{ - const RAW_DATA: &'static [u8] = - include_bytes!(concat!("../../../../../ethcore/res/ethereum/tests/", $file, ".json")); - let mut chain = None; - for (name, c) in ::ethjson::blockchain::Test::load(RAW_DATA).unwrap() { - if name == $name { - chain = Some(c); - break; - } - } - chain.unwrap() - }}; - - ($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 - }}; -} - -mod eth; \ No newline at end of file diff --git a/rpc/src/v1/tests/mocked/mod.rs b/rpc/src/v1/tests/mocked/mod.rs index 98caf6e08..dc09d998d 100644 --- a/rpc/src/v1/tests/mocked/mod.rs +++ b/rpc/src/v1/tests/mocked/mod.rs @@ -14,7 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//! RPC serialization tests. +//! RPC mocked tests. Most of these test that the RPC server is serializing and forwarding +//! method calls properly. mod eth; mod net; diff --git a/rpc/src/v1/tests/mod.rs b/rpc/src/v1/tests/mod.rs index 78a6a674f..3455bfd1f 100644 --- a/rpc/src/v1/tests/mod.rs +++ b/rpc/src/v1/tests/mod.rs @@ -2,7 +2,40 @@ pub mod helpers; +// extract a chain from the given JSON file, +// stored in ethcore/res/ethereum/tests/. +// +// usage: +// `extract_chain!("Folder/File")` will load Folder/File.json and extract +// the first block chain stored within. +// +// `extract_chain!("Folder/File", "with_name")` will load Folder/File.json and +// 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) => {{ + const RAW_DATA: &'static [u8] = + include_bytes!(concat!("../../../../ethcore/res/ethereum/tests/", $file, ".json")); + let mut chain = None; + for (name, c) in ::ethjson::blockchain::Test::load(RAW_DATA).unwrap() { + if name == $name { + chain = Some(c); + break; + } + } + chain.unwrap() + }}; + + ($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 + }}; +} + #[cfg(test)] mod mocked; #[cfg(test)] -mod integration; +mod eth;