diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 94a5e09f3..7141c7bdb 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -906,7 +906,7 @@ impl Client { pub fn state_at(&self, id: BlockId) -> Option> { // fast path for latest state. match id.clone() { - BlockId::Pending => return self.miner.pending_state().or_else(|| Some(self.state())), + BlockId::Pending => return self.miner.pending_state(self.chain.read().best_block_number()).or_else(|| Some(self.state())), BlockId::Latest => return Some(self.state()), _ => {}, } @@ -1055,19 +1055,20 @@ impl Client { self.history } - fn block_hash(chain: &BlockChain, id: BlockId) -> Option { + fn block_hash(chain: &BlockChain, miner: &Miner, id: BlockId) -> Option { match id { BlockId::Hash(hash) => Some(hash), BlockId::Number(number) => chain.block_hash(number), BlockId::Earliest => chain.block_hash(0), - BlockId::Latest | BlockId::Pending => Some(chain.best_block_hash()), + BlockId::Latest => Some(chain.best_block_hash()), + BlockId::Pending => miner.pending_block_header(chain.best_block_number()).map(|header| header.hash()) } } fn transaction_address(&self, id: TransactionId) -> Option { match id { TransactionId::Hash(ref hash) => self.chain.read().transaction_address(hash), - TransactionId::Location(id, index) => Self::block_hash(&self.chain.read(), id).map(|hash| TransactionAddress { + TransactionId::Location(id, index) => Self::block_hash(&self.chain.read(), &self.miner, id).map(|hash| TransactionAddress { block_hash: hash, index: index, }) @@ -1110,6 +1111,31 @@ impl Client { data: data, }.fake_sign(from) } + + fn do_call(&self, env_info: &EnvInfo, state: &mut State, increase_balance: bool, t: &SignedTransaction, analytics: CallAnalytics) -> Result { + let original_state = if analytics.state_diffing { Some(state.clone()) } else { None }; + + // give the sender a sufficient balance (if calling in pending block) + if increase_balance { + let sender = t.sender(); + let balance = state.balance(&sender).map_err(ExecutionError::from)?; + let needed_balance = t.value + t.gas * t.gas_price; + if balance < needed_balance { + state.add_balance(&sender, &(needed_balance - balance), state::CleanupMode::NoEmpty) + .map_err(ExecutionError::from)?; + } + } + + let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false }; + let mut ret = Executive::new(state, env_info, &*self.engine).transact_virtual(t, options)?; + + // TODO gav move this into Executive. + if let Some(original) = original_state { + ret.state_diff = Some(state.diff_from(original).map_err(ExecutionError::from)?); + } + + Ok(ret) + } } impl snapshot::DatabaseRestore for Client { @@ -1134,23 +1160,31 @@ impl snapshot::DatabaseRestore for Client { } impl BlockChainClient for Client { - fn call(&self, t: &SignedTransaction, block: BlockId, analytics: CallAnalytics) -> Result { + fn call(&self, transaction: &SignedTransaction, analytics: CallAnalytics, block: BlockId) -> Result { let mut env_info = self.env_info(block).ok_or(CallError::StatePruned)?; env_info.gas_limit = U256::max_value(); // that's just a copy of the state. let mut state = self.state_at(block).ok_or(CallError::StatePruned)?; - let original_state = if analytics.state_diffing { Some(state.clone()) } else { None }; - let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false }; - let mut ret = Executive::new(&mut state, &env_info, &*self.engine).transact_virtual(t, options)?; + self.do_call(&env_info, &mut state, block == BlockId::Pending, transaction, analytics) + } - // TODO gav move this into Executive. - if let Some(original) = original_state { - ret.state_diff = Some(state.diff_from(original).map_err(ExecutionError::from)?); + fn call_many(&self, transactions: &[(SignedTransaction, CallAnalytics)], block: BlockId) -> Result, CallError> { + let mut env_info = self.env_info(block).ok_or(CallError::StatePruned)?; + env_info.gas_limit = U256::max_value(); + + // that's just a copy of the state. + let mut state = self.state_at(block).ok_or(CallError::StatePruned)?; + let mut results = Vec::with_capacity(transactions.len()); + + for &(ref t, analytics) in transactions { + let ret = self.do_call(&env_info, &mut state, block == BlockId::Pending, t, analytics)?; + env_info.gas_used = ret.cumulative_gas_used; + results.push(ret); } - Ok(ret) + Ok(results) } fn estimate_gas(&self, t: &SignedTransaction, block: BlockId) -> Result { @@ -1303,7 +1337,16 @@ impl BlockChainClient for Client { fn block_header(&self, id: BlockId) -> Option<::encoded::Header> { let chain = self.chain.read(); - Self::block_hash(&chain, id).and_then(|hash| chain.block_header_data(&hash)) + + if let BlockId::Pending = id { + if let Some(block) = self.miner.pending_block(chain.best_block_number()) { + return Some(encoded::Header::new(block.header.rlp(Seal::Without))); + } + // fall back to latest + return self.block_header(BlockId::Latest); + } + + Self::block_hash(&chain, &self.miner, id).and_then(|hash| chain.block_header_data(&hash)) } fn block_number(&self, id: BlockId) -> Option { @@ -1311,30 +1354,48 @@ impl BlockChainClient for Client { BlockId::Number(number) => Some(number), BlockId::Hash(ref hash) => self.chain.read().block_number(hash), BlockId::Earliest => Some(0), - BlockId::Latest | BlockId::Pending => Some(self.chain.read().best_block_number()), + BlockId::Latest => Some(self.chain.read().best_block_number()), + BlockId::Pending => Some(self.chain.read().best_block_number() + 1), } } fn block_body(&self, id: BlockId) -> Option { let chain = self.chain.read(); - Self::block_hash(&chain, id).and_then(|hash| chain.block_body(&hash)) + + if let BlockId::Pending = id { + if let Some(block) = self.miner.pending_block(chain.best_block_number()) { + return Some(encoded::Body::new(BlockChain::block_to_body(&block.rlp_bytes(Seal::Without)))); + } + // fall back to latest + return self.block_body(BlockId::Latest); + } + + Self::block_hash(&chain, &self.miner, id).and_then(|hash| chain.block_body(&hash)) } fn block(&self, id: BlockId) -> Option { + let chain = self.chain.read(); + if let BlockId::Pending = id { - if let Some(block) = self.miner.pending_block() { + if let Some(block) = self.miner.pending_block(chain.best_block_number()) { return Some(encoded::Block::new(block.rlp_bytes(Seal::Without))); } + // fall back to latest + return self.block(BlockId::Latest); } - let chain = self.chain.read(); - Self::block_hash(&chain, id).and_then(|hash| { + + Self::block_hash(&chain, &self.miner, id).and_then(|hash| { chain.block(&hash) }) } fn block_status(&self, id: BlockId) -> BlockStatus { + if let BlockId::Pending = id { + return BlockStatus::Pending; + } + let chain = self.chain.read(); - match Self::block_hash(&chain, id) { + match Self::block_hash(&chain, &self.miner, id) { Some(ref hash) if chain.is_known(hash) => BlockStatus::InChain, Some(hash) => self.block_queue.status(&hash).into(), None => BlockStatus::Unknown @@ -1342,13 +1403,18 @@ impl BlockChainClient for Client { } fn block_total_difficulty(&self, id: BlockId) -> Option { - if let BlockId::Pending = id { - if let Some(block) = self.miner.pending_block() { - return Some(*block.header.difficulty() + self.block_total_difficulty(BlockId::Latest).expect("blocks in chain have details; qed")); - } - } let chain = self.chain.read(); - Self::block_hash(&chain, id).and_then(|hash| chain.block_details(&hash)).map(|d| d.total_difficulty) + if let BlockId::Pending = id { + let latest_difficulty = self.block_total_difficulty(BlockId::Latest).expect("blocks in chain have details; qed"); + let pending_difficulty = self.miner.pending_block_header(chain.best_block_number()).map(|header| *header.difficulty()); + if let Some(difficulty) = pending_difficulty { + return Some(difficulty + latest_difficulty); + } + // fall back to latest + return Some(latest_difficulty); + } + + Self::block_hash(&chain, &self.miner, id).and_then(|hash| chain.block_details(&hash)).map(|d| d.total_difficulty) } fn nonce(&self, address: &Address, id: BlockId) -> Option { @@ -1361,7 +1427,7 @@ impl BlockChainClient for Client { fn block_hash(&self, id: BlockId) -> Option { let chain = self.chain.read(); - Self::block_hash(&chain, id) + Self::block_hash(&chain, &self.miner, id) } fn code(&self, address: &Address, id: BlockId) -> Option> { @@ -1526,7 +1592,8 @@ impl BlockChainClient for Client { if self.chain.read().is_known(&unverified.hash()) { return Err(BlockImportError::Import(ImportError::AlreadyInChain)); } - if self.block_status(BlockId::Hash(unverified.parent_hash())) == BlockStatus::Unknown { + let status = self.block_status(BlockId::Hash(unverified.parent_hash())); + if status == BlockStatus::Unknown || status == BlockStatus::Pending { return Err(BlockImportError::Block(BlockError::UnknownParent(unverified.parent_hash()))); } } @@ -1540,7 +1607,8 @@ impl BlockChainClient for Client { if self.chain.read().is_known(&header.hash()) { return Err(BlockImportError::Import(ImportError::AlreadyInChain)); } - if self.block_status(BlockId::Hash(header.parent_hash())) == BlockStatus::Unknown { + let status = self.block_status(BlockId::Hash(header.parent_hash())); + if status == BlockStatus::Unknown || status == BlockStatus::Pending { return Err(BlockImportError::Block(BlockError::UnknownParent(header.parent_hash()))); } } @@ -1686,7 +1754,7 @@ impl BlockChainClient for Client { fn call_contract(&self, block_id: BlockId, address: Address, data: Bytes) -> Result { let transaction = self.contract_call_tx(block_id, address, data); - self.call(&transaction, block_id, Default::default()) + self.call(&transaction, Default::default(), block_id) .map_err(|e| format!("{:?}", e)) .map(|executed| { executed.output diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 38b0b50f4..2a205868d 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -401,10 +401,18 @@ impl MiningBlockChainClient for TestBlockChainClient { } impl BlockChainClient for TestBlockChainClient { - fn call(&self, _t: &SignedTransaction, _block: BlockId, _analytics: CallAnalytics) -> Result { + fn call(&self, _t: &SignedTransaction, _analytics: CallAnalytics, _block: BlockId) -> Result { self.execution_result.read().clone().unwrap() } + fn call_many(&self, txs: &[(SignedTransaction, CallAnalytics)], block: BlockId) -> Result, CallError> { + let mut res = Vec::with_capacity(txs.len()); + for &(ref tx, analytics) in txs { + res.push(self.call(tx, analytics, block)?); + } + Ok(res) + } + fn estimate_gas(&self, _t: &SignedTransaction, _block: BlockId) -> Result { Ok(21000.into()) } @@ -423,7 +431,7 @@ impl BlockChainClient for TestBlockChainClient { fn nonce(&self, address: &Address, id: BlockId) -> Option { match id { - BlockId::Latest => Some(self.nonces.read().get(address).cloned().unwrap_or(self.spec.params().account_start_nonce)), + BlockId::Latest | BlockId::Pending => Some(self.nonces.read().get(address).cloned().unwrap_or(self.spec.params().account_start_nonce)), _ => None, } } @@ -438,16 +446,15 @@ impl BlockChainClient for TestBlockChainClient { fn code(&self, address: &Address, id: BlockId) -> Option> { match id { - BlockId::Latest => Some(self.code.read().get(address).cloned()), + BlockId::Latest | BlockId::Pending => Some(self.code.read().get(address).cloned()), _ => None, } } fn balance(&self, address: &Address, id: BlockId) -> Option { - if let BlockId::Latest = id { - Some(self.balances.read().get(address).cloned().unwrap_or_else(U256::zero)) - } else { - None + match id { + BlockId::Latest | BlockId::Pending => Some(self.balances.read().get(address).cloned().unwrap_or_else(U256::zero)), + _ => None, } } @@ -456,10 +463,9 @@ impl BlockChainClient for TestBlockChainClient { } fn storage_at(&self, address: &Address, position: &H256, id: BlockId) -> Option { - if let BlockId::Latest = id { - Some(self.storage.read().get(&(address.clone(), position.clone())).cloned().unwrap_or_else(H256::new)) - } else { - None + match id { + BlockId::Latest | BlockId::Pending => Some(self.storage.read().get(&(address.clone(), position.clone())).cloned().unwrap_or_else(H256::new)), + _ => None, } } @@ -548,7 +554,8 @@ impl BlockChainClient for TestBlockChainClient { match id { BlockId::Number(number) if (number as usize) < self.blocks.read().len() => BlockStatus::InChain, BlockId::Hash(ref hash) if self.blocks.read().get(hash).is_some() => BlockStatus::InChain, - BlockId::Latest | BlockId::Pending | BlockId::Earliest => BlockStatus::InChain, + BlockId::Latest | BlockId::Earliest => BlockStatus::InChain, + BlockId::Pending => BlockStatus::Pending, _ => BlockStatus::Unknown, } } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index e76da60bd..8e1bd8b18 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -182,7 +182,11 @@ pub trait BlockChainClient : Sync + Send { fn logs(&self, filter: Filter) -> Vec; /// Makes a non-persistent transaction call. - fn call(&self, t: &SignedTransaction, block: BlockId, analytics: CallAnalytics) -> Result; + fn call(&self, tx: &SignedTransaction, analytics: CallAnalytics, block: BlockId) -> Result; + + /// Makes multiple non-persistent but dependent transaction calls. + /// Returns a vector of successes or a failure if any of the transaction fails. + fn call_many(&self, txs: &[(SignedTransaction, CallAnalytics)], block: BlockId) -> Result, CallError>; /// Estimates how much gas will be necessary for a call. fn estimate_gas(&self, t: &SignedTransaction, block: BlockId) -> Result; diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index b65c79204..1c962d633 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -1050,7 +1050,7 @@ mod tests { client.miner().import_own_transaction(client.as_ref(), transaction.into()).unwrap(); // Propose - let proposal = Some(client.miner().pending_block().unwrap().header.bare_hash()); + let proposal = Some(client.miner().pending_block(0).unwrap().header.bare_hash()); // Propose timeout engine.step(); diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 7256f2ff5..02463d929 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -21,8 +21,8 @@ use std::sync::Arc; use util::*; use using_queue::{UsingQueue, GetAction}; use account_provider::{AccountProvider, SignError as AccountError}; -use state::{State, CleanupMode}; -use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockId, CallAnalytics, TransactionId}; +use state::State; +use client::{MiningBlockChainClient, BlockId, TransactionId}; use client::TransactionImportResult; use executive::contract_address; use block::{ClosedBlock, IsBlock, Block}; @@ -39,7 +39,7 @@ use miner::local_transactions::{Status as LocalTransactionStatus}; use miner::service_transaction_checker::ServiceTransactionChecker; use price_info::{Client as PriceInfoClient, PriceInfo}; use price_info::fetch::Client as FetchClient; -use header::BlockNumber; +use header::{Header, BlockNumber}; /// Different possible definitions for pending transaction set. #[derive(Debug, PartialEq)] @@ -331,13 +331,28 @@ impl Miner { } /// Get `Some` `clone()` of the current pending block's state or `None` if we're not sealing. - pub fn pending_state(&self) -> Option> { - self.sealing_work.lock().queue.peek_last_ref().map(|b| b.block().fields().state.clone()) + pub fn pending_state(&self, latest_block_number: BlockNumber) -> Option> { + self.map_pending_block(|b| b.state().clone(), latest_block_number) } /// Get `Some` `clone()` of the current pending block or `None` if we're not sealing. - pub fn pending_block(&self) -> Option { - self.sealing_work.lock().queue.peek_last_ref().map(|b| b.to_base()) + pub fn pending_block(&self, latest_block_number: BlockNumber) -> Option { + self.map_pending_block(|b| b.to_base(), latest_block_number) + } + + /// Get `Some` `clone()` of the current pending block header or `None` if we're not sealing. + pub fn pending_block_header(&self, latest_block_number: BlockNumber) -> Option
{ + self.map_pending_block(|b| b.header().clone(), latest_block_number) + } + + fn map_pending_block(&self, f: F, latest_block_number: BlockNumber) -> Option where + F: FnOnce(&ClosedBlock) -> T, + { + self.from_pending_block( + latest_block_number, + || None, + |block| Some(f(block)), + ) } #[cfg_attr(feature="dev", allow(match_same_arms))] @@ -679,7 +694,7 @@ impl Miner { #[cfg_attr(feature="dev", allow(wrong_self_convention))] #[cfg_attr(feature="dev", allow(redundant_closure))] fn from_pending_block(&self, latest_block_number: BlockNumber, from_chain: F, map_block: G) -> H - where F: Fn() -> H, G: Fn(&ClosedBlock) -> H { + where F: Fn() -> H, G: FnOnce(&ClosedBlock) -> H { let sealing_work = self.sealing_work.lock(); sealing_work.queue.peek_last_ref().map_or_else( || from_chain(), @@ -717,84 +732,6 @@ impl MinerService for Miner { } } - fn call(&self, client: &MiningBlockChainClient, t: &SignedTransaction, analytics: CallAnalytics) -> Result { - let sealing_work = self.sealing_work.lock(); - match sealing_work.queue.peek_last_ref() { - Some(work) => { - let block = work.block(); - - // TODO: merge this code with client.rs's fn call somwhow. - let header = block.header(); - let last_hashes = Arc::new(client.last_hashes()); - let env_info = EnvInfo { - number: header.number(), - author: *header.author(), - timestamp: header.timestamp(), - difficulty: *header.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 = block.state().clone(); - let original_state = if analytics.state_diffing { Some(state.clone()) } else { None }; - - let sender = t.sender(); - let balance = state.balance(&sender).map_err(ExecutionError::from)?; - let needed_balance = t.value + t.gas * t.gas_price; - if balance < needed_balance { - // give the sender a sufficient balance - state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty) - .map_err(ExecutionError::from)?; - } - let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false }; - let mut ret = Executive::new(&mut state, &env_info, &*self.engine).transact(t, options)?; - - // TODO gav move this into Executive. - if let Some(original) = original_state { - ret.state_diff = Some(state.diff_from(original).map_err(ExecutionError::from)?); - } - - Ok(ret) - }, - None => client.call(t, BlockId::Latest, analytics) - } - } - - // TODO: The `chain.latest_x` actually aren't infallible, they just panic on corruption. - // TODO: return trie::Result here, or other. - fn balance(&self, chain: &MiningBlockChainClient, address: &Address) -> Option { - self.from_pending_block( - chain.chain_info().best_block_number, - || Some(chain.latest_balance(address)), - |b| b.block().fields().state.balance(address).ok(), - ) - } - - fn storage_at(&self, chain: &MiningBlockChainClient, address: &Address, position: &H256) -> Option { - self.from_pending_block( - chain.chain_info().best_block_number, - || Some(chain.latest_storage_at(address, position)), - |b| b.block().fields().state.storage_at(address, position).ok(), - ) - } - - fn nonce(&self, chain: &MiningBlockChainClient, address: &Address) -> Option { - self.from_pending_block( - chain.chain_info().best_block_number, - || Some(chain.latest_nonce(address)), - |b| b.block().fields().state.nonce(address).ok(), - ) - } - - fn code(&self, chain: &MiningBlockChainClient, address: &Address) -> Option> { - self.from_pending_block( - chain.chain_info().best_block_number, - || Some(chain.latest_code(address)), - |b| b.block().fields().state.code(address).ok().map(|c| c.map(|c| (&*c).clone())) - ) - } - fn set_author(&self, author: Address) { if self.engine.seals_internally().is_some() { let mut sealing_work = self.sealing_work.lock(); @@ -1466,14 +1403,14 @@ mod tests { miner.update_sealing(&*client); client.flush_queue(); - assert!(miner.pending_block().is_none()); + assert!(miner.pending_block(0).is_none()); assert_eq!(client.chain_info().best_block_number, 3 as BlockNumber); assert_eq!(miner.import_own_transaction(&*client, PendingTransaction::new(transaction_with_network_id(spec.network_id()).into(), None)).unwrap(), TransactionImportResult::Current); miner.update_sealing(&*client); client.flush_queue(); - assert!(miner.pending_block().is_none()); + assert!(miner.pending_block(0).is_none()); assert_eq!(client.chain_info().best_block_number, 4 as BlockNumber); } diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 1c07f4fab..b4cb065fd 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -62,12 +62,12 @@ pub use self::stratum::{Stratum, Error as StratumError, Options as StratumOption use std::collections::BTreeMap; use util::{H256, U256, Address, Bytes}; -use client::{MiningBlockChainClient, Executed, CallAnalytics}; +use client::{MiningBlockChainClient}; use block::ClosedBlock; use header::BlockNumber; use receipt::{RichReceipt, Receipt}; -use error::{Error, CallError}; -use transaction::{UnverifiedTransaction, PendingTransaction, SignedTransaction}; +use error::{Error}; +use transaction::{UnverifiedTransaction, PendingTransaction}; /// Miner client API pub trait MinerService : Send + Sync { @@ -185,21 +185,6 @@ pub trait MinerService : Send + Sync { /// Suggested gas limit. fn sensible_gas_limit(&self) -> U256 { 21000.into() } - - /// Latest account balance in pending state. - fn balance(&self, chain: &MiningBlockChainClient, address: &Address) -> Option; - - /// Call into contract code using pending state. - fn call(&self, chain: &MiningBlockChainClient, t: &SignedTransaction, analytics: CallAnalytics) -> Result; - - /// Get storage value in pending state. - fn storage_at(&self, chain: &MiningBlockChainClient, address: &Address, position: &H256) -> Option; - - /// Get account nonce in pending state. - fn nonce(&self, chain: &MiningBlockChainClient, address: &Address) -> Option; - - /// Get contract code in pending state. - fn code(&self, chain: &MiningBlockChainClient, address: &Address) -> Option>; } /// Mining status diff --git a/ethcore/types/src/block_status.rs b/ethcore/types/src/block_status.rs index 937077795..d330b9ed1 100644 --- a/ethcore/types/src/block_status.rs +++ b/ethcore/types/src/block_status.rs @@ -23,6 +23,8 @@ pub enum BlockStatus { Queued, /// Known as bad. Bad, + /// Pending block. + Pending, /// Unknown. Unknown, } diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 6ae614db1..7c656c752 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -264,6 +264,7 @@ fn check_known(client: &C, number: BlockNumber) -> Result<(), Error> where C: match client.block_status(number.into()) { BlockStatus::InChain => Ok(()), + BlockStatus::Pending => Ok(()), _ => Err(errors::unknown_block()), } } @@ -361,20 +362,12 @@ impl Eth for EthClient where fn balance(&self, address: RpcH160, num: Trailing) -> BoxFuture { let address = address.into(); - let res = match num.unwrap_or_default() { - BlockNumber::Pending => { - match self.miner.balance(&*self.client, &address) { - Some(balance) => Ok(balance.into()), - None => Err(errors::database("latest balance missing")) - } - } - id => { - try_bf!(check_known(&*self.client, id.clone())); - match self.client.balance(&address, id.into()) { - Some(balance) => Ok(balance.into()), - None => Err(errors::state_pruned()), - } - } + let id = num.unwrap_or_default(); + + try_bf!(check_known(&*self.client, id.clone())); + let res = match self.client.balance(&address, id.into()) { + Some(balance) => Ok(balance.into()), + None => Err(errors::state_pruned()), }; future::done(res).boxed() @@ -384,20 +377,12 @@ impl Eth for EthClient where let address: Address = RpcH160::into(address); let position: U256 = RpcU256::into(pos); - let res = match num.unwrap_or_default() { - BlockNumber::Pending => { - match self.miner.storage_at(&*self.client, &address, &H256::from(position)) { - Some(s) => Ok(s.into()), - None => Err(errors::database("latest storage missing")) - } - } - id => { - try_bf!(check_known(&*self.client, id.clone())); - match self.client.storage_at(&address, &H256::from(position), id.into()) { - Some(s) => Ok(s.into()), - None => Err(errors::state_pruned()), - } - } + let id = num.unwrap_or_default(); + + try_bf!(check_known(&*self.client, id.clone())); + let res = match self.client.storage_at(&address, &H256::from(position), id.into()) { + Some(s) => Ok(s.into()), + None => Err(errors::state_pruned()), }; future::done(res).boxed() @@ -410,18 +395,12 @@ impl Eth for EthClient where BlockNumber::Pending if self.options.pending_nonce_from_queue => { let nonce = self.miner.last_nonce(&address) .map(|n| n + 1.into()) - .or_else(|| self.miner.nonce(&*self.client, &address)); + .or_else(|| self.client.nonce(&address, BlockNumber::Pending.into())); match nonce { Some(nonce) => Ok(nonce.into()), None => Err(errors::database("latest nonce missing")) } } - BlockNumber::Pending => { - match self.miner.nonce(&*self.client, &address) { - Some(nonce) => Ok(nonce.into()), - None => Err(errors::database("latest nonce missing")) - } - } id => { try_bf!(check_known(&*self.client, id.clone())); match self.client.nonce(&address, id.into()) { @@ -468,20 +447,12 @@ impl Eth for EthClient where fn code_at(&self, address: RpcH160, num: Trailing) -> BoxFuture { let address: Address = RpcH160::into(address); - let res = match num.unwrap_or_default() { - BlockNumber::Pending => { - match self.miner.code(&*self.client, &address) { - Some(code) => Ok(code.map_or_else(Bytes::default, Bytes::new)), - None => Err(errors::database("latest code missing")) - } - } - id => { - try_bf!(check_known(&*self.client, id.clone())); - match self.client.code(&address, id.into()) { - Some(code) => Ok(code.map_or_else(Bytes::default, Bytes::new)), - None => Err(errors::state_pruned()), - } - } + let id = num.unwrap_or_default(); + try_bf!(check_known(&*self.client, id.clone())); + + let res = match self.client.code(&address, id.into()) { + Some(code) => Ok(code.map_or_else(Bytes::default, Bytes::new)), + None => Err(errors::state_pruned()), }; future::done(res).boxed() @@ -648,10 +619,8 @@ impl Eth for EthClient where Err(e) => return future::err(e).boxed(), }; - let result = match num.unwrap_or_default() { - BlockNumber::Pending => self.miner.call(&*self.client, &signed, Default::default()), - num => self.client.call(&signed, num.into(), Default::default()), - }; + let num = num.unwrap_or_default(); + let result = self.client.call(&signed, Default::default(), num.into()); future::done(result .map(|b| b.output.into()) diff --git a/rpc/src/v1/impls/light/parity.rs b/rpc/src/v1/impls/light/parity.rs index 481cdb136..62a7721fc 100644 --- a/rpc/src/v1/impls/light/parity.rs +++ b/rpc/src/v1/impls/light/parity.rs @@ -39,7 +39,7 @@ use v1::helpers::light_fetch::LightFetch; use v1::metadata::Metadata; use v1::traits::Parity; use v1::types::{ - Bytes, U256, H160, H256, H512, + Bytes, U256, H160, H256, H512, CallRequest, Peers, Transaction, RpcSettings, Histogram, TransactionStats, LocalTransactionStatus, BlockNumber, ConsensusCapability, VersionInfo, @@ -389,4 +389,8 @@ impl Parity for ParityClient { fn ipfs_cid(&self, content: Bytes) -> Result { ipfs::cid(content) } + + fn call(&self, _requests: Vec, _block: Trailing) -> BoxFuture, Error> { + future::err(errors::light_unimplemented(None)).boxed() + } } diff --git a/rpc/src/v1/impls/light/trace.rs b/rpc/src/v1/impls/light/trace.rs index 00d19dc24..81ed592c5 100644 --- a/rpc/src/v1/impls/light/trace.rs +++ b/rpc/src/v1/impls/light/trace.rs @@ -20,7 +20,7 @@ use jsonrpc_core::Error; use jsonrpc_macros::Trailing; use v1::traits::Traces; use v1::helpers::errors; -use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, H256}; +use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, TraceOptions, H256}; /// Traces api implementation. // TODO: all calling APIs should be possible w. proved remote TX execution. @@ -43,15 +43,19 @@ impl Traces for TracesClient { Err(errors::light_unimplemented(None)) } - fn call(&self, _request: CallRequest, _flags: Vec, _block: Trailing) -> Result { + fn call(&self, _request: CallRequest, _flags: TraceOptions, _block: Trailing) -> Result { Err(errors::light_unimplemented(None)) } - fn raw_transaction(&self, _raw_transaction: Bytes, _flags: Vec, _block: Trailing) -> Result { + fn call_many(&self, _request: Vec<(CallRequest, TraceOptions)>, _block: Trailing) -> Result, Error> { Err(errors::light_unimplemented(None)) } - fn replay_transaction(&self, _transaction_hash: H256, _flags: Vec) -> Result { + fn raw_transaction(&self, _raw_transaction: Bytes, _flags: TraceOptions, _block: Trailing) -> Result { + Err(errors::light_unimplemented(None)) + } + + fn replay_transaction(&self, _transaction_hash: H256, _flags: TraceOptions) -> Result { Err(errors::light_unimplemented(None)) } } diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index 2cffdd4e6..d855d7e17 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -28,22 +28,23 @@ use crypto::ecies; use ethkey::{Brain, Generator}; use ethstore::random_phrase; use ethsync::{SyncProvider, ManageNetwork}; +use ethcore::account_provider::AccountProvider; +use ethcore::client::{MiningBlockChainClient}; use ethcore::ids::BlockId; use ethcore::miner::MinerService; -use ethcore::client::{MiningBlockChainClient}; use ethcore::mode::Mode; -use ethcore::account_provider::AccountProvider; +use ethcore::transaction::SignedTransaction; use updater::{Service as UpdateService}; use crypto::DEFAULT_MAC; use jsonrpc_core::Error; use jsonrpc_macros::Trailing; -use v1::helpers::{self, errors, ipfs, SigningQueue, SignerService, NetworkSettings}; +use v1::helpers::{self, errors, fake_sign, ipfs, SigningQueue, SignerService, NetworkSettings}; use v1::helpers::accounts::unwrap_provider; use v1::metadata::Metadata; use v1::traits::Parity; use v1::types::{ - Bytes, U256, H160, H256, H512, + Bytes, U256, H160, H256, H512, CallRequest, Peers, Transaction, RpcSettings, Histogram, TransactionStats, LocalTransactionStatus, BlockNumber, ConsensusCapability, VersionInfo, @@ -409,4 +410,23 @@ impl Parity for ParityClient where fn ipfs_cid(&self, content: Bytes) -> Result { ipfs::cid(content) } + + fn call(&self, requests: Vec, block: Trailing) -> BoxFuture, Error> { + let requests: Result, Error> = requests + .into_iter() + .map(|request| Ok(( + fake_sign::sign_call(&self.client, &self.miner, request.into())?, + Default::default() + ))) + .collect(); + + let block = block.unwrap_or_default(); + let requests = try_bf!(requests); + + let result = self.client.call_many(&requests, block.into()) + .map(|res| res.into_iter().map(|res| res.output.into()).collect()) + .map_err(errors::call); + + future::done(result).boxed() + } } diff --git a/rpc/src/v1/impls/traces.rs b/rpc/src/v1/impls/traces.rs index 2c65f4403..31a0e889e 100644 --- a/rpc/src/v1/impls/traces.rs +++ b/rpc/src/v1/impls/traces.rs @@ -27,9 +27,9 @@ use jsonrpc_core::Error; use jsonrpc_macros::Trailing; use v1::traits::Traces; use v1::helpers::{errors, fake_sign}; -use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, H256}; +use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, TraceOptions, H256}; -fn to_call_analytics(flags: Vec) -> CallAnalytics { +fn to_call_analytics(flags: TraceOptions) -> CallAnalytics { CallAnalytics { transaction_tracing: flags.contains(&("trace".to_owned())), vm_tracing: flags.contains(&("vmTrace".to_owned())), @@ -79,29 +79,45 @@ impl Traces for TracesClient where C: MiningBlockChainClient + 'stat .map(LocalizedTrace::from)) } - fn call(&self, request: CallRequest, flags: Vec, block: Trailing) -> Result { + fn call(&self, request: CallRequest, flags: TraceOptions, block: Trailing) -> Result { let block = block.unwrap_or_default(); let request = CallRequest::into(request); let signed = fake_sign::sign_call(&self.client, &self.miner, request)?; - self.client.call(&signed, block.into(), to_call_analytics(flags)) + self.client.call(&signed, to_call_analytics(flags), block.into()) .map(TraceResults::from) .map_err(errors::call) } - fn raw_transaction(&self, raw_transaction: Bytes, flags: Vec, block: Trailing) -> Result { + fn call_many(&self, requests: Vec<(CallRequest, TraceOptions)>, block: Trailing) -> Result, Error> { + let block = block.unwrap_or_default(); + + let requests = requests.into_iter() + .map(|(request, flags)| { + let request = CallRequest::into(request); + let signed = fake_sign::sign_call(&self.client, &self.miner, request)?; + Ok((signed, to_call_analytics(flags))) + }) + .collect::, _>>()?; + + self.client.call_many(&requests, block.into()) + .map(|results| results.into_iter().map(TraceResults::from).collect()) + .map_err(errors::call) + } + + fn raw_transaction(&self, raw_transaction: Bytes, flags: TraceOptions, block: Trailing) -> Result { let block = block.unwrap_or_default(); let tx = UntrustedRlp::new(&raw_transaction.into_vec()).as_val().map_err(|e| errors::invalid_params("Transaction is not valid RLP", e))?; let signed = SignedTransaction::new(tx).map_err(errors::transaction)?; - self.client.call(&signed, block.into(), to_call_analytics(flags)) + self.client.call(&signed, to_call_analytics(flags), block.into()) .map(TraceResults::from) .map_err(errors::call) } - fn replay_transaction(&self, transaction_hash: H256, flags: Vec) -> Result { + fn replay_transaction(&self, transaction_hash: H256, flags: TraceOptions) -> Result { self.client.replay(TransactionId::Hash(transaction_hash.into()), to_call_analytics(flags)) .map(TraceResults::from) .map_err(errors::call) diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index ef9b5724b..7139d636b 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -19,9 +19,9 @@ use std::collections::{BTreeMap, HashMap}; use std::collections::hash_map::Entry; use util::{Address, H256, Bytes, U256, RwLock, Mutex}; -use ethcore::error::{Error, CallError}; -use ethcore::client::{MiningBlockChainClient, Executed, CallAnalytics}; -use ethcore::block::{ClosedBlock, IsBlock}; +use ethcore::error::Error; +use ethcore::client::MiningBlockChainClient; +use ethcore::block::ClosedBlock; use ethcore::header::BlockNumber; use ethcore::transaction::{UnverifiedTransaction, SignedTransaction, PendingTransaction}; use ethcore::receipt::{Receipt, RichReceipt}; @@ -280,41 +280,6 @@ impl MinerService for TestMinerService { unimplemented!(); } - fn balance(&self, _chain: &MiningBlockChainClient, address: &Address) -> Option { - self.latest_closed_block.lock() - .as_ref() - .map(|b| b.block().fields().state.balance(address)) - .map(|b| b.ok()) - .unwrap_or(Some(U256::default())) - } - - fn call(&self, _chain: &MiningBlockChainClient, _t: &SignedTransaction, _analytics: CallAnalytics) -> Result { - unimplemented!(); - } - - fn storage_at(&self, _chain: &MiningBlockChainClient, address: &Address, position: &H256) -> Option { - self.latest_closed_block.lock() - .as_ref() - .map(|b| b.block().fields().state.storage_at(address, position)) - .map(|s| s.ok()) - .unwrap_or(Some(H256::default())) - } - - fn nonce(&self, _chain: &MiningBlockChainClient, address: &Address) -> Option { - // we assume all transactions are in a pending block, ignoring the - // reality of gas limits. - Some(self.last_nonce(address).unwrap_or(U256::zero())) - } - - fn code(&self, _chain: &MiningBlockChainClient, address: &Address) -> Option> { - self.latest_closed_block.lock() - .as_ref() - .map(|b| b.block().fields().state.code(address)) - .map(|c| c.ok()) - .unwrap_or(None) - .map(|c| c.map(|c| (&*c).clone())) - } - fn sensible_gas_price(&self) -> U256 { 20000000000u64.into() } diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index eb5a7d4e9..f935b93e2 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -432,10 +432,7 @@ fn rpc_eth_balance_pending() { "id": 1 }"#; - // the TestMinerService doesn't communicate with the the TestBlockChainClient in any way. - // if this returns zero, we know that the "pending" call is being properly forwarded to the - // miner. - let response = r#"{"jsonrpc":"2.0","result":"0x0","id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":"0x5","id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); } diff --git a/rpc/src/v1/tests/mocked/parity.rs b/rpc/src/v1/tests/mocked/parity.rs index aeeb7902d..b5cdada6f 100644 --- a/rpc/src/v1/tests/mocked/parity.rs +++ b/rpc/src/v1/tests/mocked/parity.rs @@ -16,10 +16,10 @@ use std::sync::Arc; use ethcore_logger::RotatingLogger; -use util::Address; +use util::{Address, U256}; use ethsync::ManageNetwork; use ethcore::account_provider::AccountProvider; -use ethcore::client::{TestBlockChainClient}; +use ethcore::client::{TestBlockChainClient, Executed}; use ethcore::miner::LocalTransactionStatus; use ethstore::ethkey::{Generator, Random}; @@ -504,3 +504,40 @@ fn rpc_parity_cid() { assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } + +#[test] +fn rpc_parity_call() { + let deps = Dependencies::new(); + deps.client.set_execution_result(Ok(Executed { + exception: None, + 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: vec![], + vm_trace: None, + state_diff: None, + })); + let io = deps.default_client(); + + let request = r#"{ + "jsonrpc": "2.0", + "method": "parity_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!(io.handle_request_sync(request), Some(response.to_owned())); +} diff --git a/rpc/src/v1/tests/mocked/traces.rs b/rpc/src/v1/tests/mocked/traces.rs index f64142f8c..50890648c 100644 --- a/rpc/src/v1/tests/mocked/traces.rs +++ b/rpc/src/v1/tests/mocked/traces.rs @@ -170,6 +170,16 @@ fn rpc_trace_call() { assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); } +#[test] +fn rpc_trace_multi_call() { + let tester = io(); + + let request = r#"{"jsonrpc":"2.0","method":"trace_callMany","params":[[[{}, ["stateDiff", "vmTrace", "trace"]]]],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":[{"output":"0x010203","stateDiff":null,"trace":[],"vmTrace":null}],"id":1}"#; + + assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); +} + #[test] fn rpc_trace_call_state_pruned() { let tester = io(); diff --git a/rpc/src/v1/traits/parity.rs b/rpc/src/v1/traits/parity.rs index 92904aa40..a0b1a6e99 100644 --- a/rpc/src/v1/traits/parity.rs +++ b/rpc/src/v1/traits/parity.rs @@ -23,7 +23,7 @@ use jsonrpc_macros::Trailing; use futures::BoxFuture; use v1::types::{ - H160, H256, H512, U256, Bytes, + H160, H256, H512, U256, Bytes, CallRequest, Peers, Transaction, RpcSettings, Histogram, TransactionStats, LocalTransactionStatus, BlockNumber, ConsensusCapability, VersionInfo, @@ -203,5 +203,9 @@ build_rpc_trait! { /// Get IPFS CIDv0 given protobuf encoded bytes. #[rpc(name = "parity_cidV0")] fn ipfs_cid(&self, Bytes) -> Result; + + /// Call contract, returning the output data. + #[rpc(async, name = "parity_call")] + fn call(&self, Vec, Trailing) -> BoxFuture, Error>; } } diff --git a/rpc/src/v1/traits/traces.rs b/rpc/src/v1/traits/traces.rs index 64b37ac1d..568788af2 100644 --- a/rpc/src/v1/traits/traces.rs +++ b/rpc/src/v1/traits/traces.rs @@ -18,7 +18,7 @@ use jsonrpc_core::Error; use jsonrpc_macros::Trailing; -use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, H256}; +use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, H256, TraceOptions}; build_rpc_trait! { /// Traces specific rpc interface. @@ -41,14 +41,18 @@ build_rpc_trait! { /// Executes the given call and returns a number of possible traces for it. #[rpc(name = "trace_call")] - fn call(&self, CallRequest, Vec, Trailing) -> Result; + fn call(&self, CallRequest, TraceOptions, Trailing) -> Result; + + /// Executes all given calls and returns a number of possible traces for each of it. + #[rpc(name = "trace_callMany")] + fn call_many(&self, Vec<(CallRequest, TraceOptions)>, Trailing) -> Result, Error>; /// Executes the given raw transaction and returns a number of possible traces for it. #[rpc(name = "trace_rawTransaction")] - fn raw_transaction(&self, Bytes, Vec, Trailing) -> Result; + fn raw_transaction(&self, Bytes, TraceOptions, Trailing) -> Result; /// Executes the transaction with the given hash and returns a number of possible traces for it. #[rpc(name = "trace_replayTransaction")] - fn replay_transaction(&self, H256, Vec) -> Result; + fn replay_transaction(&self, H256, TraceOptions) -> Result; } } diff --git a/rpc/src/v1/types/mod.rs b/rpc/src/v1/types/mod.rs index 3e463f958..1407ebcf2 100644 --- a/rpc/src/v1/types/mod.rs +++ b/rpc/src/v1/types/mod.rs @@ -78,3 +78,7 @@ pub use self::transaction_request::TransactionRequest; pub use self::transaction_condition::TransactionCondition; pub use self::uint::{U128, U256}; pub use self::work::Work; + +// TODO [ToDr] Refactor to a proper type Vec of enums? +/// Expected tracing type. +pub type TraceOptions = Vec; diff --git a/secret_store/src/key_server_cluster/io/deadline.rs b/secret_store/src/key_server_cluster/io/deadline.rs index 501a69057..aea339ca9 100644 --- a/secret_store/src/key_server_cluster/io/deadline.rs +++ b/secret_store/src/key_server_cluster/io/deadline.rs @@ -19,7 +19,7 @@ use std::time::Duration; use futures::{Future, Select, BoxFuture, Poll, Async}; use tokio_core::reactor::{Handle, Timeout}; -type DeadlineBox where F: Future = BoxFuture, F::Error>; +type DeadlineBox = BoxFuture::Item>, ::Error>; /// Complete a passed future or fail if it is not completed within timeout. pub fn deadline(duration: Duration, handle: &Handle, future: F) -> Result, io::Error> @@ -82,4 +82,4 @@ mod tests { core.turn(Some(Duration::from_millis(3))); assert_eq!(deadline.wait().unwrap(), DeadlineStatus::Meet(())); } -} \ No newline at end of file +} diff --git a/sync/src/block_sync.rs b/sync/src/block_sync.rs index 8215b775b..92c8df429 100644 --- a/sync/src/block_sync.rs +++ b/sync/src/block_sync.rs @@ -267,7 +267,7 @@ impl BlockDownloader { BlockStatus::Bad => { return Err(BlockDownloaderImportError::Invalid); }, - BlockStatus::Unknown => { + BlockStatus::Unknown | BlockStatus::Pending => { headers.push(hdr.as_raw().to_vec()); hashes.push(hash); } diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 38872b24f..d6937381c 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -995,7 +995,7 @@ impl ChainSync { BlockStatus::Queued => { trace!(target: "sync", "New hash block already queued {:?}", hash); }, - BlockStatus::Unknown => { + BlockStatus::Unknown | BlockStatus::Pending => { new_hashes.push(hash.clone()); if number > max_height { trace!(target: "sync", "New unknown block hash {:?}", hash);