Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Framework for improved RPC unit tests #1141

Merged
merged 13 commits into from
May 28, 2016
Merged
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions ethcore/src/types/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}

Expand Down
9 changes: 9 additions & 0 deletions json/src/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -30,6 +31,14 @@ impl Into<Vec<u8>> for Bytes {
}
}

impl Deref for Bytes {
type Target = Vec<u8>;

fn deref(&self) -> &Vec<u8> {
&self.0
}
}

impl Deserialize for Bytes {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: Deserializer {
Expand Down
37 changes: 34 additions & 3 deletions json/src/vm/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
}
}
7 changes: 4 additions & 3 deletions miner/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,12 @@ pub trait MinerService : Send + Sync {
/// Imports transactions to transaction queue.
fn import_transactions<T>(&self, transactions: Vec<SignedTransaction>, fetch_account: T) ->
Vec<Result<TransactionImportResult, Error>>
where T: Fn(&Address) -> AccountDetails;
where T: Fn(&Address) -> AccountDetails, Self: Sized;

/// Imports own (node owner) transaction to queue.
fn import_own_transaction<T>(&self, chain: &BlockChainClient, transaction: SignedTransaction, fetch_account: T) ->
Result<TransactionImportResult, Error>
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<H256>;
Expand All @@ -131,7 +131,8 @@ pub trait MinerService : Send + Sync {
fn submit_seal(&self, chain: &BlockChainClient, pow_hash: H256, seal: Vec<Bytes>) -> Result<(), Error>;

/// Get the sealing work package and if `Some`, apply some transform.
fn map_sealing_work<F, T>(&self, chain: &BlockChainClient, f: F) -> Option<T> where F: FnOnce(&ClosedBlock) -> T;
fn map_sealing_work<F, T>(&self, chain: &BlockChainClient, f: F) -> Option<T>
where F: FnOnce(&ClosedBlock) -> T, Self: Sized;

/// Query pending transactions for hash.
fn transaction(&self, hash: &H256) -> Option<SignedTransaction>;
Expand Down
2 changes: 2 additions & 0 deletions rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
5 changes: 5 additions & 0 deletions rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
2 changes: 1 addition & 1 deletion rpc/src/v1/impls/ethcore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
229 changes: 229 additions & 0 deletions rpc/src/v1/tests/eth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
// 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 <http://www.gnu.org/licenses/>.

//! 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 ethcore::transaction::{Transaction, Action};
use ethminer::{MinerService, ExternalMiner};
use devtools::RandomTempPath;
use util::io::IoChannel;
use util::hash::{Address, FixedHash};
use util::numbers::{Uint, 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<BlockChainClient>,
_miner: Arc<MinerService>,
accounts: Arc<TestAccountProvider>,
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);
});
}

fn account_provider() -> Arc<TestAccountProvider> {
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<TestSyncProvider> {
Arc::new(TestSyncProvider::new(Config {
network_id: U256::from(3),
num_peers: 120,
}))
}

fn miner_service() -> Arc<TestMinerService> {
Arc::new(TestMinerService::default())
}

// given a blockchain, this harness will create an EthClient wrapping it
// which tests can pass specially crafted requests to.
fn chain_harness<F, U>(chain: BlockChain, mut cb: F) -> 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();
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);

let tester = EthTester {
_miner: miner_service,
_client: client,
accounts: account_provider,
handler: handler,
};

cb(&tester)
}
Loading