diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 614a74798..1d34bf158 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -37,6 +37,8 @@ use log_entry::LocalizedLogEntry; use block_queue::{BlockQueue, BlockQueueInfo}; use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute}; use client::{BlockId, TransactionId, ClientConfig, BlockChainClient}; +use env_info::EnvInfo; +use executive::{Executive, Executed}; pub use blockchain::CacheSize as BlockChainCacheSize; /// General block status @@ -385,6 +387,29 @@ impl Client where V: Verifier { } impl BlockChainClient for Client where V: Verifier { + fn call(&self, t: &SignedTransaction) -> Result { + let header = self.block_header(BlockId::Latest).unwrap(); + let view = HeaderView::new(&header); + let last_hashes = self.build_last_hashes(view.hash()); + let env_info = EnvInfo { + number: view.number(), + author: view.author(), + timestamp: view.timestamp(), + difficulty: view.difficulty(), + last_hashes: last_hashes, + gas_used: U256::zero(), + gas_limit: U256::max_value(), + }; + // that's just a copy of the state. + let mut state = self.state(); + let sender = try!(t.sender()); + let balance = state.balance(&sender); + // give the sender max balance + state.sub_balance(&sender, &balance); + state.add_balance(&sender, &U256::max_value()); + Executive::new(&mut state, &env_info, self.engine.deref().deref()).transact(t, false) + } + // TODO [todr] Should be moved to miner crate eventually. fn try_seal(&self, block: ClosedBlock, seal: Vec) -> Result { block.try_seal(self.engine.deref().deref(), seal) diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 198e918f7..9663ab62b 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -25,6 +25,7 @@ pub use self::client::*; pub use self::config::{ClientConfig, BlockQueueConfig, BlockChainConfig}; pub use self::ids::{BlockId, TransactionId}; pub use self::test_client::{TestBlockChainClient, EachBlockWith}; +pub use executive::Executed; use std::collections::HashSet; use util::bytes::Bytes; @@ -37,7 +38,7 @@ use header::BlockNumber; use transaction::{LocalizedTransaction, SignedTransaction}; use log_entry::LocalizedLogEntry; use filter::Filter; -use error::{ImportResult}; +use error::{ImportResult, Error}; /// Blockchain database client. Owns and manages a blockchain and a block queue. pub trait BlockChainClient : Sync + Send { @@ -118,5 +119,7 @@ pub trait BlockChainClient : Sync + Send { /// Attempts to seal given block. Returns `SealedBlock` on success and the same block in case of error. fn try_seal(&self, block: ClosedBlock, seal: Vec) -> Result; + /// Makes a non-persistent transaction call. + fn call(&self, t: &SignedTransaction) -> Result; } diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 9150a5f55..ad73234b3 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -29,6 +29,8 @@ use error::{ImportResult}; use block_queue::BlockQueueInfo; use block::{SealedBlock, ClosedBlock}; +use executive::Executed; +use error::Error; /// Test client. pub struct TestBlockChainClient { @@ -48,6 +50,8 @@ pub struct TestBlockChainClient { pub storage: RwLock>, /// Code. pub code: RwLock>, + /// Execution result. + pub execution_result: RwLock>, } #[derive(Clone)] @@ -82,12 +86,18 @@ impl TestBlockChainClient { balances: RwLock::new(HashMap::new()), storage: RwLock::new(HashMap::new()), code: RwLock::new(HashMap::new()), + execution_result: RwLock::new(None), }; client.add_blocks(1, EachBlockWith::Nothing); // add genesis block client.genesis_hash = client.last_hash.read().unwrap().clone(); client } + /// Set the execution result. + pub fn set_execution_result(&self, result: Executed) { + *self.execution_result.write().unwrap() = Some(result); + } + /// Set the balance of account `address` to `balance`. pub fn set_balance(&self, address: Address, balance: U256) { self.balances.write().unwrap().insert(address, balance); @@ -182,6 +192,10 @@ impl TestBlockChainClient { } impl BlockChainClient for TestBlockChainClient { + fn call(&self, _t: &SignedTransaction) -> Result { + Ok(self.execution_result.read().unwrap().clone().unwrap()) + } + fn block_total_difficulty(&self, _id: BlockId) -> Option { Some(U256::zero()) } diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index d02f58815..1fbef7345 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -37,7 +37,7 @@ pub fn contract_address(address: &Address, nonce: &U256) -> Address { } /// Transaction execution receipt. -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub struct Executed { /// Gas paid up front for execution of transaction. pub gas: U256, diff --git a/ethcore/src/views.rs b/ethcore/src/views.rs index 745cbff2c..11e26eb5f 100644 --- a/ethcore/src/views.rs +++ b/ethcore/src/views.rs @@ -256,6 +256,9 @@ impl<'a> HeaderView<'a> { } } + /// Returns header hash. + pub fn hash(&self) -> H256 { self.sha3() } + /// Returns raw rlp. pub fn rlp(&self) -> &Rlp<'a> { &self.rlp } diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 9b2588670..a99a912f8 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -398,6 +398,50 @@ impl Eth for EthClient } }) } + + fn call(&self, params: Params) -> Result { + from_params::<(TransactionRequest, BlockNumber)>(params) + .and_then(|(transaction_request, _block_number)| { + let accounts = take_weak!(self.accounts); + match accounts.account_secret(&transaction_request.from) { + Ok(secret) => { + let client = take_weak!(self.client); + + let transaction: EthTransaction = transaction_request.into(); + let signed_transaction = transaction.sign(&secret); + + let output = client.call(&signed_transaction) + .map(|e| Bytes::new(e.output)) + .unwrap_or(Bytes::default()); + + to_value(&output) + }, + Err(_) => { to_value(&Bytes::default()) } + } + }) + } + + fn estimate_gas(&self, params: Params) -> Result { + from_params::<(TransactionRequest, BlockNumber)>(params) + .and_then(|(transaction_request, _block_number)| { + let accounts = take_weak!(self.accounts); + match accounts.account_secret(&transaction_request.from) { + Ok(secret) => { + let client = take_weak!(self.client); + + let transaction: EthTransaction = transaction_request.into(); + let signed_transaction = transaction.sign(&secret); + + let gas_used = client.call(&signed_transaction) + .map(|e| e.gas_used + e.refunded) + .unwrap_or(U256::zero()); + + to_value(&gas_used) + }, + Err(_) => { to_value(&U256::zero()) } + } + }) + } } /// Eth filter rpc implementation. diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index a5f318350..72235b390 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -18,8 +18,8 @@ use std::collections::HashMap; use std::sync::{Arc, RwLock}; use jsonrpc_core::IoHandler; use util::hash::{Address, H256}; -use util::numbers::U256; -use ethcore::client::{TestBlockChainClient, EachBlockWith}; +use util::numbers::{Uint, U256}; +use ethcore::client::{TestBlockChainClient, EachBlockWith, Executed}; use v1::{Eth, EthClient}; use v1::tests::helpers::{TestAccount, TestAccountProvider, TestSyncProvider, Config, TestMinerService, TestExternalMiner}; @@ -299,9 +299,69 @@ fn rpc_eth_code() { } #[test] -#[ignore] fn rpc_eth_call() { - unimplemented!() + let tester = EthTester::default(); + tester.client.set_execution_result(Executed { + gas: U256::zero(), + gas_used: U256::from(0xff30), + refunded: U256::from(0x5), + cumulative_gas_used: U256::zero(), + logs: vec![], + contracts_created: vec![], + output: vec![0x12, 0x34, 0xff], + trace: None, + }); + + let request = r#"{ + "jsonrpc": "2.0", + "method": "eth_call", + "params": [{ + "from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", + "gas": "0x76c0", + "gasPrice": "0x9184e72a000", + "value": "0x9184e72a", + "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" + }, + "latest"], + "id": 1 + }"#; + let response = r#"{"jsonrpc":"2.0","result":"0x1234ff","id":1}"#; + + assert_eq!(tester.io.handle_request(request), Some(response.to_owned())); +} + +#[test] +fn rpc_eth_estimate_gas() { + let tester = EthTester::default(); + tester.client.set_execution_result(Executed { + gas: U256::zero(), + gas_used: U256::from(0xff30), + refunded: U256::from(0x5), + cumulative_gas_used: U256::zero(), + logs: vec![], + contracts_created: vec![], + output: vec![0x12, 0x34, 0xff], + trace: None, + }); + + let request = r#"{ + "jsonrpc": "2.0", + "method": "eth_estimateGas", + "params": [{ + "from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", + "gas": "0x76c0", + "gasPrice": "0x9184e72a000", + "value": "0x9184e72a", + "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" + }, + "latest"], + "id": 1 + }"#; + let response = r#"{"jsonrpc":"2.0","result":"0xff35","id":1}"#; + + assert_eq!(tester.io.handle_request(request), Some(response.to_owned())); } #[test] @@ -322,12 +382,6 @@ fn rpc_eth_sign() { unimplemented!() } -#[test] -#[ignore] -fn rpc_eth_estimate_gas() { - unimplemented!() -} - #[test] fn rpc_eth_compilers() { let request = r#"{"jsonrpc": "2.0", "method": "eth_getCompilers", "params": [], "id": 1}"#; diff --git a/rpc/src/v1/tests/helpers/account_provider.rs b/rpc/src/v1/tests/helpers/account_provider.rs index 614aba346..6ef6e2b59 100644 --- a/rpc/src/v1/tests/helpers/account_provider.rs +++ b/rpc/src/v1/tests/helpers/account_provider.rs @@ -19,7 +19,7 @@ use std::sync::RwLock; use std::collections::HashMap; use std::io; -use util::hash::{Address, H256}; +use util::hash::{Address, H256, FixedHash}; use util::crypto::{Secret, Signature}; use util::keys::store::{AccountProvider, SigningError, EncryptedHashMapError}; @@ -83,7 +83,7 @@ impl AccountProvider for TestAccountProvider { } fn account_secret(&self, _account: &Address) -> Result { - unimplemented!() + Ok(Secret::random()) } fn sign(&self, _account: &Address, _message: &H256) -> Result { diff --git a/rpc/src/v1/types/transaction_request.rs b/rpc/src/v1/types/transaction_request.rs index d40402ab5..ed4dc19a2 100644 --- a/rpc/src/v1/types/transaction_request.rs +++ b/rpc/src/v1/types/transaction_request.rs @@ -46,6 +46,8 @@ impl Into for TransactionRequest { #[cfg(test)] mod tests { + use std::str::FromStr; + use rustc_serialize::hex::FromHex; use serde_json; use util::numbers::{Uint, U256}; use util::hash::Address; @@ -121,6 +123,29 @@ mod tests { }); } + #[test] + fn transaction_request_deserialize2() { + let s = r#"{ + "from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", + "gas": "0x76c0", + "gasPrice": "0x9184e72a000", + "value": "0x9184e72a", + "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" + }"#; + let deserialized: TransactionRequest = serde_json::from_str(s).unwrap(); + + assert_eq!(deserialized, TransactionRequest { + from: Address::from_str("b60e8dd61c5d32be8058bb8eb970870f07233155").unwrap(), + to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), + gas_price: Some(U256::from_str("9184e72a000").unwrap()), + gas: Some(U256::from_str("76c0").unwrap()), + value: Some(U256::from_str("9184e72a").unwrap()), + data: Some(Bytes::new("d46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675".from_hex().unwrap())), + nonce: None + }); + } + #[test] fn transaction_request_deserialize_empty() { let s = r#"{"from":"0x0000000000000000000000000000000000000001"}"#; diff --git a/util/bigint/src/uint.rs b/util/bigint/src/uint.rs index d185750c2..7bc6007ed 100644 --- a/util/bigint/src/uint.rs +++ b/util/bigint/src/uint.rs @@ -490,6 +490,8 @@ pub trait Uint: Sized + Default + FromStr + From + fmt::Debug + fmt::Displa fn zero() -> Self; /// Returns new instance equalling one. fn one() -> Self; + /// Returns the largest value that can be represented by this integer type. + fn max_value() -> Self; /// Error type for converting from a decimal string. type FromDecStrErr; @@ -647,6 +649,15 @@ macro_rules! construct_uint { From::from(1u64) } + #[inline] + fn max_value() -> Self { + let mut result = [0; $n_words]; + for i in 0..$n_words { + result[i] = u64::max_value(); + } + $name(result) + } + /// Fast exponentation by squaring /// https://en.wikipedia.org/wiki/Exponentiation_by_squaring fn pow(self, expon: Self) -> Self {