diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 4e8c34b33..a860dd752 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -449,6 +449,10 @@ impl BlockChainClient for Client where V: Verifier { self.state().code(address) } + fn balance(&self, address: &Address) -> U256 { + self.state().balance(address) + } + fn transaction(&self, id: TransactionId) -> Option { match id { TransactionId::Hash(ref hash) => self.chain.transaction_address(hash), diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index afdfb200a..af2c6ac14 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -66,6 +66,9 @@ pub trait BlockChainClient : Sync + Send { /// Get address code. fn code(&self, address: &Address) -> Option; + /// Get address balance. + fn balance(&self, address: &Address) -> U256; + /// Get transaction with given hash. fn transaction(&self, id: TransactionId) -> Option; diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 207f1090f..cf1352bc8 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -40,6 +40,8 @@ pub struct TestBlockChainClient { pub last_hash: RwLock, /// Difficulty. pub difficulty: RwLock, + /// Balances. + pub balances: RwLock>, } #[derive(Clone)] @@ -65,12 +67,17 @@ impl TestBlockChainClient { genesis_hash: H256::new(), last_hash: RwLock::new(H256::new()), difficulty: RwLock::new(From::from(0)), + balances: RwLock::new(HashMap::new()), }; client.add_blocks(1, EachBlockWith::Nothing); // add genesis block client.genesis_hash = client.last_hash.read().unwrap().clone(); client } + pub fn set_balance(&mut self, address: Address, balance: U256) { + self.balances.write().unwrap().insert(address, balance); + } + /// Add blocks to test client. pub fn add_blocks(&mut self, count: usize, with: EachBlockWith) { let len = self.numbers.read().unwrap().len(); @@ -165,6 +172,10 @@ impl BlockChainClient for TestBlockChainClient { unimplemented!(); } + fn balance(&self, address: &Address) -> U256 { + self.balances.read().unwrap().get(address).cloned().unwrap_or_else(U256::zero) + } + fn transaction(&self, _id: TransactionId) -> Option { unimplemented!(); } diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 38e363624..f5159f55f 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -155,6 +155,14 @@ impl Eth for EthClient where C: BlockChainClient + 'static, S: } } + fn accounts(&self, _: Params) -> Result { + let store = take_weak!(self.accounts); + match store.accounts() { + Ok(account_list) => to_value(&account_list), + Err(_) => Err(Error::internal_error()) + } + } + fn block_number(&self, params: Params) -> Result { match params { Params::None => to_value(&U256::from(take_weak!(self.client).chain_info().best_block_number)), @@ -162,6 +170,11 @@ impl Eth for EthClient where C: BlockChainClient + 'static, S: } } + fn balance(&self, params: Params) -> Result { + from_params::<(Address, BlockNumber)>(params) + .and_then(|(address, _block_number)| to_value(&take_weak!(self.client).balance(&address))) + } + fn block_transaction_count_by_hash(&self, params: Params) -> Result { from_params::<(H256,)>(params) .and_then(|(hash,)| match take_weak!(self.client).block(BlockId::Hash(hash)) { diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index ce200244c..0cd3f0040 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -39,12 +39,7 @@ impl Personal for PersonalClient where A: AccountProvider + 'static { fn accounts(&self, _: Params) -> Result { let store = take_weak!(self.accounts); match store.accounts() { - Ok(account_list) => { - Ok(Value::Array(account_list.iter() - .map(|&account| Value::String(format!("{:?}", account))) - .collect::>()) - ) - } + Ok(account_list) => to_value(&account_list), Err(_) => Err(Error::internal_error()) } } diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs new file mode 100644 index 000000000..8c61c2ed9 --- /dev/null +++ b/rpc/src/v1/tests/eth.rs @@ -0,0 +1,90 @@ +// 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 . + +use std::collections::HashMap; +use std::sync::Arc; +use jsonrpc_core::IoHandler; +use util::hash::Address; +use util::numbers::U256; +use ethcore::client::{TestBlockChainClient, EachBlockWith}; +use v1::{Eth, EthClient}; +use v1::tests::helpers::{TestAccount, TestAccountProvider, TestSyncProvider, Config}; + +fn blockchain_client() -> Arc { + let mut client = TestBlockChainClient::new(); + client.add_blocks(10, EachBlockWith::Nothing); + client.set_balance(Address::from(1), U256::from(5)); + Arc::new(client) +} + +fn accounts_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 { + protocol_version: 65, + num_peers: 120, + })) +} + +struct EthTester { + client: Arc, + sync: Arc, + accounts_provider: Arc, + pub io: IoHandler, +} + +impl Default for EthTester { + fn default() -> Self { + let client = blockchain_client(); + let sync = sync_provider(); + let ap = accounts_provider(); + let eth = EthClient::new(&client, &sync, &ap).to_delegate(); + let io = IoHandler::new(); + io.add_delegate(eth); + EthTester { + client: client, + sync: sync, + accounts_provider: ap, + io: io + } + } +} + +#[test] +fn rpc_eth_accounts() { + let request = r#"{"jsonrpc": "2.0", "method": "eth_accounts", "params": [], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":["0x0000000000000000000000000000000000000001"],"id":1}"#; + + assert_eq!(EthTester::default().io.handle_request(request), Some(response.to_owned())); +} + +#[test] +fn rpc_eth_balance() { + let request = r#"{ + "jsonrpc": "2.0", + "method": "eth_getBalance", + "params": ["0x0000000000000000000000000000000000000001", "latest"], + "id": 1 + }"#; + let response = r#"{"jsonrpc":"2.0","result":"0x05","id":1}"#; + + assert_eq!(EthTester::default().io.handle_request(request), Some(response.to_owned())); +} diff --git a/rpc/src/v1/tests/helpers/account_provider.rs b/rpc/src/v1/tests/helpers/account_provider.rs new file mode 100644 index 000000000..66f085f74 --- /dev/null +++ b/rpc/src/v1/tests/helpers/account_provider.rs @@ -0,0 +1,84 @@ +// 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 . + +use std::sync::RwLock; +use std::collections::HashMap; +use std::io; +use util::hash::{Address, H256}; +use util::crypto::{Secret, Signature}; +use util::keys::store::{AccountProvider, SigningError, EncryptedHashMapError}; + +/// Account mock. +#[derive(Clone)] +pub struct TestAccount { + /// True if account is unlocked. + pub unlocked: bool, + /// Account's password. + pub password: String, +} + +impl TestAccount { + pub fn new(password: &str) -> Self { + TestAccount { + unlocked: false, + password: password.to_owned(), + } + } +} + +/// Test account provider. +pub struct TestAccountProvider { + accounts: RwLock>, +} + +impl TestAccountProvider { + /// Basic constructor. + pub fn new(accounts: HashMap) -> Self { + TestAccountProvider { + accounts: RwLock::new(accounts), + } + } +} + +impl AccountProvider for TestAccountProvider { + fn accounts(&self) -> Result, io::Error> { + Ok(self.accounts.read().unwrap().keys().cloned().collect()) + } + + fn unlock_account(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> { + match self.accounts.write().unwrap().get_mut(account) { + Some(ref mut acc) if acc.password == pass => { + acc.unlocked = true; + Ok(()) + }, + Some(_) => Err(EncryptedHashMapError::InvalidPassword), + None => Err(EncryptedHashMapError::UnknownIdentifier), + } + } + + fn new_account(&self, _pass: &str) -> Result { + unimplemented!() + } + fn account_secret(&self, _account: &Address) -> Result { + unimplemented!() + } + + fn sign(&self, _account: &Address, _message: &H256) -> Result { + unimplemented!() + } + +} + diff --git a/rpc/src/v1/tests/helpers/mod.rs b/rpc/src/v1/tests/helpers/mod.rs index 501bfb2d3..3bd74bab7 100644 --- a/rpc/src/v1/tests/helpers/mod.rs +++ b/rpc/src/v1/tests/helpers/mod.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +mod account_provider; mod sync_provider; +pub use self::account_provider::{TestAccount, TestAccountProvider}; pub use self::sync_provider::{Config, TestSyncProvider}; diff --git a/rpc/src/v1/tests/mod.rs b/rpc/src/v1/tests/mod.rs index 3a38ced15..3374bad36 100644 --- a/rpc/src/v1/tests/mod.rs +++ b/rpc/src/v1/tests/mod.rs @@ -16,6 +16,7 @@ //!TODO: load custom blockchain state and test +mod eth; mod net; mod web3; mod helpers; diff --git a/rpc/src/v1/traits/eth.rs b/rpc/src/v1/traits/eth.rs index 8c24dd38c..7d33cb63f 100644 --- a/rpc/src/v1/traits/eth.rs +++ b/rpc/src/v1/traits/eth.rs @@ -130,7 +130,7 @@ pub trait Eth: Sized + Send + Sync + 'static { delegate.add_method("eth_gasPrice", Eth::gas_price); delegate.add_method("eth_accounts", Eth::accounts); delegate.add_method("eth_blockNumber", Eth::block_number); - delegate.add_method("eth_balance", Eth::balance); + delegate.add_method("eth_getBalance", Eth::balance); delegate.add_method("eth_getStorageAt", Eth::storage_at); delegate.add_method("eth_getTransactionCount", Eth::transaction_count); delegate.add_method("eth_getBlockTransactionCountByHash", Eth::block_transaction_count_by_hash);