From 9d7d6f7108172ca699244b00c2d4123c24db7ded Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sat, 3 Mar 2018 18:42:13 +0100 Subject: [PATCH] `Client` refactoring (#7038) * Improves `BestBlock` comment * Improves `TraceDB` comment * Improves `journaldb::Algorithm` comment. Probably the whole enum should be renamed to `Strategy` or something alike. * Comments some of the `Client`'s fields * Deglobs client imports * Fixes comments * Extracts `import_lock` to `Importer` struct * Extracts `verifier` to `Importer` struct * Extracts `block_queue` to `Importer` struct * Extracts `miner` to `Importer` struct * Extracts `ancient_verifier` to `Importer` struct * Extracts `rng` to `Importer` struct * Extracts `import_old_block` to `Importer` struct * Adds `Nonce` trait * Adds `Balance` trait * Adds `ChainInfo` trait * Fixes imports for tests using `chain_info` method * Adds `BlockInfo` trait * Adds more `ChainInfo` imports * Adds `BlockInfo` imports * Adds `ReopenBlock` trait * Adds `PrepareOpenBlock` trait * Fixes import in tests * Adds `CallContract` trait * Fixes imports in tests using `call_contract` method * Adds `TransactionInfo` trait * Adds `RegistryInfo` trait * Fixes imports in tests using `registry_address` method * Adds `ScheduleInfo` trait * Adds `ImportSealedBlock` trait * Fixes imports in test using `import_sealed_block` method * Adds `BroadcastProposalBlock` trait * Migrates `Miner` to static dispatch * Fixes tests * Moves `calculate_enacted_retracted` to `Importer` * Moves import-related methods to `Importer` * Removes redundant `import_old_block` wrapper * Extracts `import_block*` into separate trait * Fixes tests * Handles `Pending` in `LightFetch` * Handles `Pending` in filters * Handles `Pending` in `ParityClient` * Handles `Pending` in `EthClient` * Removes `BlockId::Pending`, partly refactors dependent code * Adds `StateInfo` trait * Exports `StateOrBlock` and `BlockChain` types from `client` module * Refactors `balance` RPC using generic API * Refactors `storage_at` RPC using generic API * Makes `MinerService::pending_state`'s return type dynamic * Adds `StateOrBlock` and `BlockChain` types * Adds impl of `client::BlockChain` for `Client` * Exports `StateInfo` trait from `client` module * Missing `self` use To be fixed up to "Adds impl of `client::BlockChain` for `Client`" * Adds `number_to_id` and refactors dependent RPC methods * Refactors `code_at` using generic API * Adds `StateClient` trait * Refactors RPC to use `StateClient` trait * Reverts `client::BlockChain` trait stuff, refactors methods to accept `StateOrBlock` * Refactors TestClient * Adds helper function `block_number_to_id` * Uses `block_number_to_id` instead of local function * Handles `Pending` in `list_accounts` and `list_storage_keys` * Attempt to use associated types for state instead of trait objects * Simplifies `state_at_beginning` * Extracts `call` and `call_many` into separate trait * Refactors `build_last_hashes` to accept reference * Exports `Call` type from the module * Refactors `call` and `call_many` to accept state and header * Exports `state_at` in `StateClient` * Exports `pending_block_header` from `MinerService` * Refactors RPC `call` method using new API * Adds missing parentheses * Refactors `parity::call` to use new call API * Update .gitlab-ci.yml fix gitlab lint * Fixes error handling * Refactors `traces::call` and `call_many` to use new call API * Refactors `call_contract` * Refactors `block_header` * Refactors internal RPC method `block` * Moves `estimate_gas` to `Call` trait, refactors parameters * Refactors `estimate_gas` in RPC * Refactors `uncle` * Refactors RPC `transaction` * Covers missing branches * Makes it all compile, fixes compiler grumbles * Adds casts in `blockchain` module * Fixes `PendingBlock` tests, work on `MinerService` * Adds test stubs for StateClient and EngineInfo * Makes `state_db` public * Adds missing impls for `TestBlockChainClient` * Adds trait documentation * Adds missing docs to the `state_db` module * Fixes trivial compilation errors * Moves `code_hash` method to a `BlockInfo` trait * Refactors `Verifier` to be generic over client * Refactors `TransactionFilter` to be generic over client * Refactors `Miner` and `Client` to reflect changes in verifier and txfilter API * Moves `ServiceTransactionChecker` back to `ethcore` * Fixes trait bounds in `Miner` API * Fixes `Client` * Fixes lifetime bound in `FullFamilyParams` * Adds comments to `FullFamilyParams` * Fixes imports in `ethcore` * Fixes BlockNumber handling in `code_at` and `replay_block_transactions` * fix compile issues * First step to redundant trait merge * Fixes compilation error in RPC tests * Adds mock `State` as a stub for `TestClient` * Handles `StateOrBlock::State` in `TestBlockChainClient::balance` * Fixes `transaction_count` RPC * Fixes `transaction_count` * Moves `service_transaction.json` to the `contracts` subfolder * Fixes compilation errors in tests * Refactors client to use `AccountData` * Refactors client to use `BlockChain` * Refactors miner to use aggregate traits * Adds `SealedBlockImporter` trait * Refactors miner to use `SealedBlockImporter` trait * Removes unused imports * Simplifies `RegistryInfo::registry_address` * Fixes indentation * Removes commented out trait bound --- Cargo.lock | 1 + ethcore/light/src/client/header_chain.rs | 7 +- ethcore/light/src/client/mod.rs | 10 +- ethcore/light/src/on_demand/request.rs | 2 +- ethcore/light/src/provider.rs | 8 +- .../res/contracts}/service_transaction.json | 0 ethcore/src/blockchain/best_block.rs | 7 +- ethcore/src/client/client.rs | 1551 +++++++++-------- ethcore/src/client/mod.rs | 9 +- ethcore/src/client/test_client.rs | 362 ++-- ethcore/src/client/traits.rs | 283 ++- ethcore/src/engines/tendermint/mod.rs | 1 + ethcore/src/engines/validator_set/contract.rs | 2 +- ethcore/src/engines/validator_set/multi.rs | 2 +- .../engines/validator_set/safe_contract.rs | 2 +- ethcore/src/json_tests/chain.rs | 2 +- ethcore/src/machine.rs | 4 +- ethcore/src/miner/miner.rs | 109 +- ethcore/src/miner/mod.rs | 48 +- .../src/miner}/service_transaction_checker.rs | 17 +- ethcore/src/snapshot/service.rs | 2 +- ethcore/src/snapshot/tests/helpers.rs | 2 +- .../src/snapshot/tests/proof_of_authority.rs | 2 +- ethcore/src/snapshot/tests/service.rs | 2 +- ethcore/src/snapshot/watcher.rs | 2 +- ethcore/src/state/mod.rs | 22 + ethcore/src/state_db.rs | 18 +- ethcore/src/tests/client.rs | 2 +- ethcore/src/tests/helpers.rs | 2 +- ethcore/src/trace/db.rs | 7 +- ethcore/src/tx_filter.rs | 4 +- ethcore/src/verification/canon_verifier.rs | 5 +- ethcore/src/verification/mod.rs | 4 +- ethcore/src/verification/noop_verifier.rs | 5 +- ethcore/src/verification/verification.rs | 40 +- ethcore/src/verification/verifier.rs | 9 +- ethcore/types/src/ids.rs | 2 - miner/src/lib.rs | 5 - parity/blockchain.rs | 8 +- parity/dapps.rs | 2 +- parity/informant.rs | 2 +- rpc/Cargo.toml | 1 + rpc/src/lib.rs | 1 + rpc/src/v1/helpers/light_fetch.rs | 16 +- rpc/src/v1/impls/eth.rs | 366 +++- rpc/src/v1/impls/light/eth.rs | 33 +- rpc/src/v1/impls/light/parity.rs | 12 +- rpc/src/v1/impls/parity.rs | 89 +- rpc/src/v1/impls/traces.rs | 65 +- rpc/src/v1/tests/eth.rs | 6 +- rpc/src/v1/tests/helpers/miner_service.rs | 61 +- rpc/src/v1/tests/mocked/eth_pubsub.rs | 2 +- rpc/src/v1/types/block_number.rs | 32 +- rpc/src/v1/types/filter.rs | 10 +- rpc/src/v1/types/mod.rs | 2 +- rpc/src/v1/types/trace_filter.rs | 13 +- secret_store/src/acl_storage.rs | 2 +- secret_store/src/key_server_set.rs | 6 +- secret_store/src/listener/service_contract.rs | 2 +- secret_store/src/trusted_client.rs | 2 +- sync/src/chain.rs | 2 +- sync/src/light_sync/tests/mod.rs | 2 +- sync/src/tests/chain.rs | 2 +- sync/src/tests/consensus.rs | 2 +- util/journaldb/src/lib.rs | 2 +- 65 files changed, 2067 insertions(+), 1238 deletions(-) rename {miner/res => ethcore/res/contracts}/service_transaction.json (100%) rename {miner/src => ethcore/src/miner}/service_transaction_checker.rs (74%) diff --git a/Cargo.lock b/Cargo.lock index 0cc37dba0..08915933c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2146,6 +2146,7 @@ dependencies = [ "parity-updater 1.9.0", "parity-version 1.10.0", "parking_lot 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "patricia-trie 0.1.0", "pretty_assertions 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.2.1", diff --git a/ethcore/light/src/client/header_chain.rs b/ethcore/light/src/client/header_chain.rs index 6de68bbe3..7118e67e3 100644 --- a/ethcore/light/src/client/header_chain.rs +++ b/ethcore/light/src/client/header_chain.rs @@ -497,7 +497,7 @@ impl HeaderChain { if self.best_block.read().number < num { return None } self.candidates.read().get(&num).map(|entry| entry.canonical_hash) } - BlockId::Latest | BlockId::Pending => { + BlockId::Latest => { Some(self.best_block.read().hash) } } @@ -539,7 +539,7 @@ impl HeaderChain { self.candidates.read().get(&num).map(|entry| entry.canonical_hash) .and_then(load_from_db) } - BlockId::Latest | BlockId::Pending => { + BlockId::Latest => { // hold candidates hear to prevent deletion of the header // as we read it. let _candidates = self.candidates.read(); @@ -575,7 +575,7 @@ impl HeaderChain { if self.best_block.read().number < num { return None } candidates.get(&num).map(|era| era.candidates[0].total_difficulty) } - BlockId::Latest | BlockId::Pending => Some(self.best_block.read().total_difficulty) + BlockId::Latest => Some(self.best_block.read().total_difficulty) } } @@ -866,7 +866,6 @@ mod tests { assert!(chain.block_header(BlockId::Earliest).is_some()); assert!(chain.block_header(BlockId::Latest).is_some()); - assert!(chain.block_header(BlockId::Pending).is_some()); } #[test] diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index 4c7ea70ad..c8f627731 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -574,6 +574,12 @@ impl LightChainClient for Client { } } +impl ::ethcore::client::ChainInfo for Client { + fn chain_info(&self) -> BlockChainInfo { + Client::chain_info(self) + } +} + impl ::ethcore::client::EngineClient for Client { fn update_sealing(&self) { } fn submit_seal(&self, _block_hash: H256, _seal: Vec>) { } @@ -587,10 +593,6 @@ impl ::ethcore::client::EngineClient for Client { }) } - fn chain_info(&self) -> BlockChainInfo { - Client::chain_info(self) - } - fn as_full_client(&self) -> Option<&::ethcore::client::BlockChainClient> { None } diff --git a/ethcore/light/src/on_demand/request.rs b/ethcore/light/src/on_demand/request.rs index 5488e2715..04a818991 100644 --- a/ethcore/light/src/on_demand/request.rs +++ b/ethcore/light/src/on_demand/request.rs @@ -948,7 +948,7 @@ mod tests { use trie::recorder::Recorder; use hash::keccak; - use ethcore::client::{BlockChainClient, TestBlockChainClient, EachBlockWith}; + use ethcore::client::{BlockChainClient, BlockInfo, TestBlockChainClient, EachBlockWith}; use ethcore::header::Header; use ethcore::encoded; use ethcore::receipt::{Receipt, TransactionOutcome}; diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index 70d04db9c..1d9af0ac1 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -20,9 +20,9 @@ use std::sync::Arc; use ethcore::blockchain_info::BlockChainInfo; -use ethcore::client::{BlockChainClient, ProvingBlockChainClient}; -use ethcore::encoded; +use ethcore::client::{BlockChainClient, ProvingBlockChainClient, ChainInfo, BlockInfo as ClientBlockInfo}; use ethcore::ids::BlockId; +use ethcore::encoded; use ethereum_types::H256; use parking_lot::RwLock; use transaction::PendingTransaction; @@ -138,7 +138,7 @@ pub trait Provider: Send + Sync { // Implementation of a light client data provider for a client. impl Provider for T { fn chain_info(&self) -> BlockChainInfo { - BlockChainClient::chain_info(self) + ChainInfo::chain_info(self) } fn reorg_depth(&self, a: &H256, b: &H256) -> Option { @@ -150,7 +150,7 @@ impl Provider for T { } fn block_header(&self, id: BlockId) -> Option { - BlockChainClient::block_header(self, id) + ClientBlockInfo::block_header(self, id) } fn transaction_index(&self, req: request::CompleteTransactionIndexRequest) diff --git a/miner/res/service_transaction.json b/ethcore/res/contracts/service_transaction.json similarity index 100% rename from miner/res/service_transaction.json rename to ethcore/res/contracts/service_transaction.json diff --git a/ethcore/src/blockchain/best_block.rs b/ethcore/src/blockchain/best_block.rs index 7c0b62943..1436ced27 100644 --- a/ethcore/src/blockchain/best_block.rs +++ b/ethcore/src/blockchain/best_block.rs @@ -18,7 +18,12 @@ use ethereum_types::{H256, U256}; use bytes::Bytes; use header::BlockNumber; -/// Best block info. +/// Contains information on a best block that is specific to the consensus engine. +/// +/// For GHOST fork-choice rule it would typically describe the block with highest +/// combined difficulty (usually the block with the highest block number). +/// +/// Sometimes refered as 'latest block'. #[derive(Default)] pub struct BestBlock { /// Best block hash. diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index bc4b1fe39..5a5ffae16 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -26,20 +26,24 @@ use itertools::Itertools; use hash::keccak; use bytes::Bytes; use journaldb; -use util_error::UtilError; use trie::{TrieSpec, TrieFactory, Trie}; use kvdb::{DBValue, KeyValueDB, DBTransaction}; +use util_error::UtilError; // other use ethereum_types::{H256, Address, U256}; -use block::*; +use block::{IsBlock, LockedBlock, Drain, ClosedBlock, OpenBlock, enact_verified, SealedBlock}; use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute, TransactionAddress}; use client::ancient_import::AncientVerifier; use client::Error as ClientError; +use client::{ + Nonce, Balance, ChainInfo, BlockInfo, CallContract, TransactionInfo, RegistryInfo, ReopenBlock, PrepareOpenBlock, ScheduleInfo, ImportSealedBlock, BroadcastProposalBlock, ImportBlock, + StateOrBlock, StateInfo, StateClient, Call, AccountData, BlockChain as BlockChainTrait, BlockProducer, SealedBlockImporter +}; use client::{ BlockId, TransactionId, UncleId, TraceId, ClientConfig, BlockChainClient, MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode, - ChainNotify, PruningInfo, ProvingBlockChainClient, + ChainNotify, PruningInfo, ProvingBlockChainClient, EngineInfo }; use encoded; use engines::{EthEngine, EpochTransition}; @@ -48,8 +52,8 @@ use vm::{EnvInfo, LastHashes}; use evm::Schedule; use executive::{Executive, Executed, TransactOptions, contract_address}; use factory::{Factories, VmFactory}; -use header::{BlockNumber, Header, Seal}; -use io::*; +use header::{BlockNumber, Header}; +use io::IoChannel; use log_entry::LocalizedLogEntry; use miner::{Miner, MinerService}; use parking_lot::{Mutex, RwLock}; @@ -135,36 +139,538 @@ impl SleepState { } } +struct Importer { + /// Lock used during block import + pub import_lock: Mutex<()>, // FIXME Maybe wrap the whole `Importer` instead? + + /// Used to verify blocks + pub verifier: Box>, + + /// Queue containing pending blocks + pub block_queue: BlockQueue, + + /// Handles block sealing + pub miner: Arc, + + /// Ancient block verifier: import an ancient sequence of blocks in order from a starting epoch + pub ancient_verifier: Mutex>, + + /// Random number generator used by `AncientVerifier` + pub rng: Mutex, + + /// Ethereum engine to be used during import + pub engine: Arc, +} + /// Blockchain database client backed by a persistent database. Owns and manages a blockchain and a block queue. /// Call `import_block()` to import a block asynchronously; `flush_queue()` flushes the queue. pub struct Client { + /// Flag used to disable the client forever. Not to be confused with `liveness`. + /// + /// For example, auto-updater will disable client forever if there is a + /// hard fork registered on-chain that we don't have capability for. + /// When hard fork block rolls around, the client (if `update` is false) + /// knows it can't proceed further. enabled: AtomicBool, + + /// Operating mode for the client mode: Mutex, + chain: RwLock>, tracedb: RwLock>, engine: Arc, + + /// Client configuration config: ClientConfig, + + /// Database pruning strategy to use for StateDB pruning: journaldb::Algorithm, + + /// Client uses this to store blocks, traces, etc. db: RwLock>, + state_db: RwLock, - block_queue: BlockQueue, + + /// Report on the status of client report: RwLock, - import_lock: Mutex<()>, - verifier: Box, - miner: Arc, + sleep_state: Mutex, + + /// Flag changed by `sleep` and `wake_up` methods. Not to be confused with `enabled`. liveness: AtomicBool, io_channel: Mutex>, + + /// List of actors to be notified on certain chain events notify: RwLock>>, + + /// Count of pending transactions in the queue queue_transactions: AtomicUsize, last_hashes: RwLock>, factories: Factories, + + /// Number of eras kept in a journal before they are pruned history: u64, - ancient_verifier: Mutex>, + + /// An action to be done if a mode/spec_name change happens on_user_defaults_change: Mutex) + 'static + Send>>>, + + /// Link to a registry object useful for looking up names registrar: registry::Registry, registrar_address: Option
, + + /// A closure to call when we want to restart the client exit_handler: Mutex) + 'static + Send>>>, + + importer: Importer, +} + +impl Importer { + pub fn new( + config: &ClientConfig, + engine: Arc, + message_channel: IoChannel, + miner: Arc, + ) -> Result { + let block_queue = BlockQueue::new(config.queue.clone(), engine.clone(), message_channel.clone(), config.verifier_type.verifying_seal()); + + Ok(Importer { + import_lock: Mutex::new(()), + verifier: verification::new(config.verifier_type.clone()), + block_queue, + miner, + ancient_verifier: Mutex::new(None), + rng: Mutex::new(OsRng::new()?), + engine, + }) + } + + fn calculate_enacted_retracted(&self, import_results: &[ImportRoute]) -> (Vec, Vec) { + fn map_to_vec(map: Vec<(H256, bool)>) -> Vec { + map.into_iter().map(|(k, _v)| k).collect() + } + + // In ImportRoute we get all the blocks that have been enacted and retracted by single insert. + // Because we are doing multiple inserts some of the blocks that were enacted in import `k` + // could be retracted in import `k+1`. This is why to understand if after all inserts + // the block is enacted or retracted we iterate over all routes and at the end final state + // will be in the hashmap + let map = import_results.iter().fold(HashMap::new(), |mut map, route| { + for hash in &route.enacted { + map.insert(hash.clone(), true); + } + for hash in &route.retracted { + map.insert(hash.clone(), false); + } + map + }); + + // Split to enacted retracted (using hashmap value) + let (enacted, retracted) = map.into_iter().partition(|&(_k, v)| v); + // And convert tuples to keys + (map_to_vec(enacted), map_to_vec(retracted)) + } + + /// This is triggered by a message coming from a block queue when the block is ready for insertion + pub fn import_verified_blocks(&self, client: &Client) -> usize { + + // Shortcut out if we know we're incapable of syncing the chain. + if !client.enabled.load(AtomicOrdering::Relaxed) { + return 0; + } + + let max_blocks_to_import = 4; + let (imported_blocks, import_results, invalid_blocks, imported, proposed_blocks, duration, is_empty) = { + let mut imported_blocks = Vec::with_capacity(max_blocks_to_import); + let mut invalid_blocks = HashSet::new(); + let mut proposed_blocks = Vec::with_capacity(max_blocks_to_import); + let mut import_results = Vec::with_capacity(max_blocks_to_import); + + let _import_lock = self.import_lock.lock(); + let blocks = self.block_queue.drain(max_blocks_to_import); + if blocks.is_empty() { + return 0; + } + trace_time!("import_verified_blocks"); + let start = precise_time_ns(); + + for block in blocks { + let header = &block.header; + let is_invalid = invalid_blocks.contains(header.parent_hash()); + if is_invalid { + invalid_blocks.insert(header.hash()); + continue; + } + if let Ok(closed_block) = self.check_and_close_block(&block, client) { + if self.engine.is_proposal(&block.header) { + self.block_queue.mark_as_good(&[header.hash()]); + proposed_blocks.push(block.bytes); + } else { + imported_blocks.push(header.hash()); + + let route = self.commit_block(closed_block, &header, &block.bytes, client); + import_results.push(route); + + client.report.write().accrue_block(&block); + } + } else { + invalid_blocks.insert(header.hash()); + } + } + + let imported = imported_blocks.len(); + let invalid_blocks = invalid_blocks.into_iter().collect::>(); + + if !invalid_blocks.is_empty() { + self.block_queue.mark_as_bad(&invalid_blocks); + } + let is_empty = self.block_queue.mark_as_good(&imported_blocks); + let duration_ns = precise_time_ns() - start; + (imported_blocks, import_results, invalid_blocks, imported, proposed_blocks, duration_ns, is_empty) + }; + + { + if !imported_blocks.is_empty() && is_empty { + let (enacted, retracted) = self.calculate_enacted_retracted(&import_results); + + if is_empty { + self.miner.chain_new_blocks(client, &imported_blocks, &invalid_blocks, &enacted, &retracted); + } + + client.notify(|notify| { + notify.new_blocks( + imported_blocks.clone(), + invalid_blocks.clone(), + enacted.clone(), + retracted.clone(), + Vec::new(), + proposed_blocks.clone(), + duration, + ); + }); + } + } + + client.db.read().flush().expect("DB flush failed."); + imported + } + + fn check_and_close_block(&self, block: &PreverifiedBlock, client: &Client) -> Result { + let engine = &*self.engine; + let header = &block.header; + + let chain = client.chain.read(); + // Check the block isn't so old we won't be able to enact it. + let best_block_number = chain.best_block_number(); + if client.pruning_info().earliest_state > header.number() { + warn!(target: "client", "Block import failed for #{} ({})\nBlock is ancient (current best block: #{}).", header.number(), header.hash(), best_block_number); + return Err(()); + } + + // Check if parent is in chain + let parent = match chain.block_header(header.parent_hash()) { + Some(h) => h, + None => { + warn!(target: "client", "Block import failed for #{} ({}): Parent not found ({}) ", header.number(), header.hash(), header.parent_hash()); + return Err(()); + } + }; + + // Verify Block Family + let verify_family_result = self.verifier.verify_block_family( + header, + &parent, + engine, + Some(verification::FullFamilyParams { + block_bytes: &block.bytes, + transactions: &block.transactions, + block_provider: &**chain, + client + }), + ); + + if let Err(e) = verify_family_result { + warn!(target: "client", "Stage 3 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); + return Err(()); + }; + + let verify_external_result = self.verifier.verify_block_external(header, engine); + if let Err(e) = verify_external_result { + warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); + return Err(()); + }; + + // Enact Verified Block + let last_hashes = client.build_last_hashes(header.parent_hash()); + let db = client.state_db.read().boxed_clone_canon(header.parent_hash()); + + let is_epoch_begin = chain.epoch_transition(parent.number(), *header.parent_hash()).is_some(); + let enact_result = enact_verified(block, + engine, + client.tracedb.read().tracing_enabled(), + db, + &parent, + last_hashes, + client.factories.clone(), + is_epoch_begin, + ); + let mut locked_block = enact_result.map_err(|e| { + warn!(target: "client", "Block import failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); + })?; + + if header.number() < engine.params().validate_receipts_transition && header.receipts_root() != locked_block.block().header().receipts_root() { + locked_block = locked_block.strip_receipts(); + } + + // Final Verification + if let Err(e) = self.verifier.verify_block_final(header, locked_block.block().header()) { + warn!(target: "client", "Stage 5 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); + return Err(()); + } + + Ok(locked_block) + } + + /// Import a block with transaction receipts. + /// + /// The block is guaranteed to be the next best blocks in the + /// first block sequence. Does no sealing or transaction validation. + fn import_old_block(&self, block_bytes: Bytes, receipts_bytes: Bytes, db: &KeyValueDB, chain: &BlockChain) -> Result { + let block = BlockView::new(&block_bytes); + let header = block.header(); + let receipts = ::rlp::decode_list(&receipts_bytes); + let hash = header.hash(); + let _import_lock = self.import_lock.lock(); + + { + trace_time!("import_old_block"); + let mut ancient_verifier = self.ancient_verifier.lock(); + + { + // closure for verifying a block. + let verify_with = |verifier: &AncientVerifier| -> Result<(), ::error::Error> { + // verify the block, passing the chain for updating the epoch + // verifier. + let mut rng = OsRng::new().map_err(UtilError::from)?; + verifier.verify(&mut rng, &header, &chain) + }; + + // initialize the ancient block verifier if we don't have one already. + match &mut *ancient_verifier { + &mut Some(ref verifier) => { + verify_with(verifier)? + } + x @ &mut None => { + // load most recent epoch. + trace!(target: "client", "Initializing ancient block restoration."); + let current_epoch_data = chain.epoch_transitions() + .take_while(|&(_, ref t)| t.block_number < header.number()) + .last() + .map(|(_, t)| t.proof) + .expect("At least one epoch entry (genesis) always stored; qed"); + + let current_verifier = self.engine.epoch_verifier(&header, ¤t_epoch_data) + .known_confirmed()?; + let current_verifier = AncientVerifier::new(self.engine.clone(), current_verifier); + + verify_with(¤t_verifier)?; + *x = Some(current_verifier); + } + } + } + + // Commit results + let mut batch = DBTransaction::new(); + chain.insert_unordered_block(&mut batch, &block_bytes, receipts, None, false, true); + // Final commit to the DB + db.write_buffered(batch); + chain.commit(); + } + db.flush().expect("DB flush failed."); + Ok(hash) + } + + // NOTE: the header of the block passed here is not necessarily sealed, as + // it is for reconstructing the state transition. + // + // The header passed is from the original block data and is sealed. + fn commit_block(&self, block: B, header: &Header, block_data: &[u8], client: &Client) -> ImportRoute where B: IsBlock + Drain { + let hash = &header.hash(); + let number = header.number(); + let parent = header.parent_hash(); + let chain = client.chain.read(); + + // Commit results + let receipts = block.receipts().to_owned(); + let traces = block.traces().clone().drain(); + + assert_eq!(header.hash(), BlockView::new(block_data).header_view().hash()); + + //let traces = From::from(block.traces().clone().unwrap_or_else(Vec::new)); + + let mut batch = DBTransaction::new(); + + // CHECK! I *think* this is fine, even if the state_root is equal to another + // already-imported block of the same number. + // TODO: Prove it with a test. + let mut state = block.drain(); + + // check epoch end signal, potentially generating a proof on the current + // state. + self.check_epoch_end_signal( + &header, + block_data, + &receipts, + &state, + &chain, + &mut batch, + client + ); + + state.journal_under(&mut batch, number, hash).expect("DB commit failed"); + let route = chain.insert_block(&mut batch, block_data, receipts.clone()); + + client.tracedb.read().import(&mut batch, TraceImportRequest { + traces: traces.into(), + block_hash: hash.clone(), + block_number: number, + enacted: route.enacted.clone(), + retracted: route.retracted.len() + }); + + let is_canon = route.enacted.last().map_or(false, |h| h == hash); + state.sync_cache(&route.enacted, &route.retracted, is_canon); + // Final commit to the DB + client.db.read().write_buffered(batch); + chain.commit(); + + self.check_epoch_end(&header, &chain, client); + + client.update_last_hashes(&parent, hash); + + if let Err(e) = client.prune_ancient(state, &chain) { + warn!("Failed to prune ancient state data: {}", e); + } + + route + } + + // check for epoch end signal and write pending transition if it occurs. + // state for the given block must be available. + fn check_epoch_end_signal( + &self, + header: &Header, + block_bytes: &[u8], + receipts: &[Receipt], + state_db: &StateDB, + chain: &BlockChain, + batch: &mut DBTransaction, + client: &Client, + ) { + use engines::EpochChange; + + let hash = header.hash(); + let auxiliary = ::machine::AuxiliaryData { + bytes: Some(block_bytes), + receipts: Some(&receipts), + }; + + match self.engine.signals_epoch_end(header, auxiliary) { + EpochChange::Yes(proof) => { + use engines::epoch::PendingTransition; + use engines::Proof; + + let proof = match proof { + Proof::Known(proof) => proof, + Proof::WithState(with_state) => { + let env_info = EnvInfo { + number: header.number(), + author: header.author().clone(), + timestamp: header.timestamp(), + difficulty: header.difficulty().clone(), + last_hashes: client.build_last_hashes(header.parent_hash()), + gas_used: U256::default(), + gas_limit: u64::max_value().into(), + }; + + let call = move |addr, data| { + let mut state_db = state_db.boxed_clone(); + let backend = ::state::backend::Proving::new(state_db.as_hashdb_mut()); + + let transaction = + client.contract_call_tx(BlockId::Hash(*header.parent_hash()), addr, data); + + let mut state = State::from_existing( + backend, + header.state_root().clone(), + self.engine.account_start_nonce(header.number()), + client.factories.clone(), + ).expect("state known to be available for just-imported block; qed"); + + let options = TransactOptions::with_no_tracing().dont_check_nonce(); + let res = Executive::new(&mut state, &env_info, self.engine.machine()) + .transact(&transaction, options); + + let res = match res { + Err(ExecutionError::Internal(e)) => + Err(format!("Internal error: {}", e)), + Err(e) => { + trace!(target: "client", "Proved call failed: {}", e); + Ok((Vec::new(), state.drop().1.extract_proof())) + } + Ok(res) => Ok((res.output, state.drop().1.extract_proof())), + }; + + res.map(|(output, proof)| (output, proof.into_iter().map(|x| x.into_vec()).collect())) + }; + + match with_state.generate_proof(&call) { + Ok(proof) => proof, + Err(e) => { + warn!(target: "client", "Failed to generate transition proof for block {}: {}", hash, e); + warn!(target: "client", "Snapshots produced by this client may be incomplete"); + Vec::new() + } + } + } + }; + + debug!(target: "client", "Block {} signals epoch end.", hash); + + let pending = PendingTransition { proof: proof }; + chain.insert_pending_transition(batch, hash, pending); + }, + EpochChange::No => {}, + EpochChange::Unsure(_) => { + warn!(target: "client", "Detected invalid engine implementation."); + warn!(target: "client", "Engine claims to require more block data, but everything provided."); + } + } + } + + // check for ending of epoch and write transition if it occurs. + fn check_epoch_end<'a>(&self, header: &'a Header, chain: &BlockChain, client: &Client) { + let is_epoch_end = self.engine.is_epoch_end( + header, + &(|hash| chain.block_header(&hash)), + &(|hash| chain.get_pending_transition(hash)), // TODO: limit to current epoch. + ); + + if let Some(proof) = is_epoch_end { + debug!(target: "client", "Epoch transition at block {}", header.hash()); + + let mut batch = DBTransaction::new(); + chain.insert_epoch_transition(&mut batch, header.number(), EpochTransition { + block_hash: header.hash(), + block_number: header.number(), + proof: proof, + }); + + // always write the batch directly since epoch transition proofs are + // fetched from a DB iterator and DB iterators are only available on + // flushed data. + client.db.read().write(batch).expect("DB flush failed"); + } + } } impl Client { @@ -220,10 +726,10 @@ impl Client { let engine = spec.engine.clone(); - let block_queue = BlockQueue::new(config.queue.clone(), engine.clone(), message_channel.clone(), config.verifier_type.verifying_seal()); - let awake = match config.mode { Mode::Dark(..) | Mode::Off => false, _ => true }; + let importer = Importer::new(&config, engine.clone(), message_channel.clone(), miner)?; + let registrar_address = engine.additional_params().get("registrar").and_then(|s| Address::from_str(s).ok()); if let Some(ref addr) = registrar_address { trace!(target: "client", "Found registrar at {}", addr); @@ -238,25 +744,21 @@ impl Client { tracedb: tracedb, engine: engine, pruning: config.pruning.clone(), - verifier: verification::new(config.verifier_type.clone()), config: config, db: RwLock::new(db), state_db: RwLock::new(state_db), - block_queue: block_queue, report: RwLock::new(Default::default()), - import_lock: Mutex::new(()), - miner: miner, io_channel: Mutex::new(message_channel), notify: RwLock::new(Vec::new()), queue_transactions: AtomicUsize::new(0), last_hashes: RwLock::new(VecDeque::new()), factories: factories, history: history, - ancient_verifier: Mutex::new(None), on_user_defaults_change: Mutex::new(None), registrar: registry::Registry::default(), registrar_address, exit_handler: Mutex::new(None), + importer, }); // prune old states. @@ -345,8 +847,8 @@ impl Client { /// Flush the block import queue. pub fn flush_queue(&self) { - self.block_queue.flush(); - while !self.block_queue.queue_info().is_empty() { + self.importer.block_queue.flush(); + while !self.importer.block_queue.queue_info().is_empty() { self.import_verified_blocks(); } } @@ -365,17 +867,17 @@ impl Client { author: header.author(), timestamp: header.timestamp(), difficulty: header.difficulty(), - last_hashes: self.build_last_hashes(header.parent_hash()), + last_hashes: self.build_last_hashes(&header.parent_hash()), gas_used: U256::default(), gas_limit: header.gas_limit(), } }) } - fn build_last_hashes(&self, parent_hash: H256) -> Arc { + fn build_last_hashes(&self, parent_hash: &H256) -> Arc { { let hashes = self.last_hashes.read(); - if hashes.front().map_or(false, |h| h == &parent_hash) { + if hashes.front().map_or(false, |h| h == parent_hash) { let mut res = Vec::from(hashes.clone()); res.resize(256, H256::default()); return Arc::new(res); @@ -383,7 +885,7 @@ impl Client { } let mut last_hashes = LastHashes::new(); last_hashes.resize(256, H256::default()); - last_hashes[0] = parent_hash; + last_hashes[0] = parent_hash.clone(); let chain = self.chain.read(); for i in 0..255 { match chain.block_details(&last_hashes[i]) { @@ -398,425 +900,10 @@ impl Client { Arc::new(last_hashes) } - fn check_and_close_block(&self, block: &PreverifiedBlock) -> Result { - let engine = &*self.engine; - let header = &block.header; - - let chain = self.chain.read(); - // Check the block isn't so old we won't be able to enact it. - let best_block_number = chain.best_block_number(); - if self.pruning_info().earliest_state > header.number() { - warn!(target: "client", "Block import failed for #{} ({})\nBlock is ancient (current best block: #{}).", header.number(), header.hash(), best_block_number); - return Err(()); - } - - // Check if parent is in chain - let parent = match chain.block_header(header.parent_hash()) { - Some(h) => h, - None => { - warn!(target: "client", "Block import failed for #{} ({}): Parent not found ({}) ", header.number(), header.hash(), header.parent_hash()); - return Err(()); - } - }; - - // Verify Block Family - let verify_family_result = self.verifier.verify_block_family( - header, - &parent, - engine, - Some((&block.bytes, &block.transactions, &**chain, self)), - ); - - if let Err(e) = verify_family_result { - warn!(target: "client", "Stage 3 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); - return Err(()); - }; - - let verify_external_result = self.verifier.verify_block_external(header, engine); - if let Err(e) = verify_external_result { - warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); - return Err(()); - }; - - // Enact Verified Block - let last_hashes = self.build_last_hashes(header.parent_hash().clone()); - let db = self.state_db.read().boxed_clone_canon(header.parent_hash()); - - let is_epoch_begin = chain.epoch_transition(parent.number(), *header.parent_hash()).is_some(); - let enact_result = enact_verified(block, - engine, - self.tracedb.read().tracing_enabled(), - db, - &parent, - last_hashes, - self.factories.clone(), - is_epoch_begin, - ); - let mut locked_block = enact_result.map_err(|e| { - warn!(target: "client", "Block import failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); - })?; - - if header.number() < self.engine().params().validate_receipts_transition && header.receipts_root() != locked_block.block().header().receipts_root() { - locked_block = locked_block.strip_receipts(); - } - - // Final Verification - if let Err(e) = self.verifier.verify_block_final(header, locked_block.block().header()) { - warn!(target: "client", "Stage 5 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); - return Err(()); - } - - Ok(locked_block) - } - - fn calculate_enacted_retracted(&self, import_results: &[ImportRoute]) -> (Vec, Vec) { - fn map_to_vec(map: Vec<(H256, bool)>) -> Vec { - map.into_iter().map(|(k, _v)| k).collect() - } - - // In ImportRoute we get all the blocks that have been enacted and retracted by single insert. - // Because we are doing multiple inserts some of the blocks that were enacted in import `k` - // could be retracted in import `k+1`. This is why to understand if after all inserts - // the block is enacted or retracted we iterate over all routes and at the end final state - // will be in the hashmap - let map = import_results.iter().fold(HashMap::new(), |mut map, route| { - for hash in &route.enacted { - map.insert(hash.clone(), true); - } - for hash in &route.retracted { - map.insert(hash.clone(), false); - } - map - }); - - // Split to enacted retracted (using hashmap value) - let (enacted, retracted) = map.into_iter().partition(|&(_k, v)| v); - // And convert tuples to keys - (map_to_vec(enacted), map_to_vec(retracted)) - } /// This is triggered by a message coming from a block queue when the block is ready for insertion pub fn import_verified_blocks(&self) -> usize { - - // Shortcut out if we know we're incapable of syncing the chain. - if !self.enabled.load(AtomicOrdering::Relaxed) { - return 0; - } - - let max_blocks_to_import = 4; - let (imported_blocks, import_results, invalid_blocks, imported, proposed_blocks, duration, is_empty) = { - let mut imported_blocks = Vec::with_capacity(max_blocks_to_import); - let mut invalid_blocks = HashSet::new(); - let mut proposed_blocks = Vec::with_capacity(max_blocks_to_import); - let mut import_results = Vec::with_capacity(max_blocks_to_import); - - let _import_lock = self.import_lock.lock(); - let blocks = self.block_queue.drain(max_blocks_to_import); - if blocks.is_empty() { - return 0; - } - trace_time!("import_verified_blocks"); - let start = precise_time_ns(); - - for block in blocks { - let header = &block.header; - let is_invalid = invalid_blocks.contains(header.parent_hash()); - if is_invalid { - invalid_blocks.insert(header.hash()); - continue; - } - if let Ok(closed_block) = self.check_and_close_block(&block) { - if self.engine.is_proposal(&block.header) { - self.block_queue.mark_as_good(&[header.hash()]); - proposed_blocks.push(block.bytes); - } else { - imported_blocks.push(header.hash()); - - let route = self.commit_block(closed_block, &header, &block.bytes); - import_results.push(route); - - self.report.write().accrue_block(&block); - } - } else { - invalid_blocks.insert(header.hash()); - } - } - - let imported = imported_blocks.len(); - let invalid_blocks = invalid_blocks.into_iter().collect::>(); - - if !invalid_blocks.is_empty() { - self.block_queue.mark_as_bad(&invalid_blocks); - } - let is_empty = self.block_queue.mark_as_good(&imported_blocks); - let duration_ns = precise_time_ns() - start; - (imported_blocks, import_results, invalid_blocks, imported, proposed_blocks, duration_ns, is_empty) - }; - - { - if !imported_blocks.is_empty() && is_empty { - let (enacted, retracted) = self.calculate_enacted_retracted(&import_results); - - if is_empty { - self.miner.chain_new_blocks(self, &imported_blocks, &invalid_blocks, &enacted, &retracted); - } - - self.notify(|notify| { - notify.new_blocks( - imported_blocks.clone(), - invalid_blocks.clone(), - enacted.clone(), - retracted.clone(), - Vec::new(), - proposed_blocks.clone(), - duration, - ); - }); - } - } - - self.db.read().flush().expect("DB flush failed."); - imported - } - - /// Import a block with transaction receipts. - /// The block is guaranteed to be the next best blocks in the first block sequence. - /// Does no sealing or transaction validation. - fn import_old_block(&self, block_bytes: Bytes, receipts_bytes: Bytes) -> Result { - let block = BlockView::new(&block_bytes); - let header = block.header(); - let receipts = ::rlp::decode_list(&receipts_bytes); - let hash = header.hash(); - let _import_lock = self.import_lock.lock(); - - { - trace_time!("import_old_block"); - let chain = self.chain.read(); - let mut ancient_verifier = self.ancient_verifier.lock(); - - { - // closure for verifying a block. - let verify_with = |verifier: &AncientVerifier| -> Result<(), ::error::Error> { - // verify the block, passing the chain for updating the epoch - // verifier. - let mut rng = OsRng::new().map_err(UtilError::from)?; - verifier.verify(&mut rng, &header, &chain) - }; - - // initialize the ancient block verifier if we don't have one already. - match &mut *ancient_verifier { - &mut Some(ref verifier) => { - verify_with(verifier)? - } - x @ &mut None => { - // load most recent epoch. - trace!(target: "client", "Initializing ancient block restoration."); - let current_epoch_data = chain.epoch_transitions() - .take_while(|&(_, ref t)| t.block_number < header.number()) - .last() - .map(|(_, t)| t.proof) - .expect("At least one epoch entry (genesis) always stored; qed"); - - let current_verifier = self.engine.epoch_verifier(&header, ¤t_epoch_data) - .known_confirmed()?; - let current_verifier = AncientVerifier::new(self.engine.clone(), current_verifier); - - verify_with(¤t_verifier)?; - *x = Some(current_verifier); - } - } - } - - // Commit results - let mut batch = DBTransaction::new(); - chain.insert_unordered_block(&mut batch, &block_bytes, receipts, None, false, true); - // Final commit to the DB - self.db.read().write_buffered(batch); - chain.commit(); - } - self.db.read().flush().expect("DB flush failed."); - Ok(hash) - } - - // NOTE: the header of the block passed here is not necessarily sealed, as - // it is for reconstructing the state transition. - // - // The header passed is from the original block data and is sealed. - fn commit_block(&self, block: B, header: &Header, block_data: &[u8]) -> ImportRoute where B: IsBlock + Drain { - let hash = &header.hash(); - let number = header.number(); - let parent = header.parent_hash(); - let chain = self.chain.read(); - - // Commit results - let receipts = block.receipts().to_owned(); - let traces = block.traces().clone().drain(); - - assert_eq!(header.hash(), BlockView::new(block_data).header_view().hash()); - - //let traces = From::from(block.traces().clone().unwrap_or_else(Vec::new)); - - let mut batch = DBTransaction::new(); - - // CHECK! I *think* this is fine, even if the state_root is equal to another - // already-imported block of the same number. - // TODO: Prove it with a test. - let mut state = block.drain(); - - // check epoch end signal, potentially generating a proof on the current - // state. - self.check_epoch_end_signal( - &header, - block_data, - &receipts, - &state, - &chain, - &mut batch, - ); - - state.journal_under(&mut batch, number, hash).expect("DB commit failed"); - let route = chain.insert_block(&mut batch, block_data, receipts.clone()); - - self.tracedb.read().import(&mut batch, TraceImportRequest { - traces: traces.into(), - block_hash: hash.clone(), - block_number: number, - enacted: route.enacted.clone(), - retracted: route.retracted.len() - }); - - let is_canon = route.enacted.last().map_or(false, |h| h == hash); - state.sync_cache(&route.enacted, &route.retracted, is_canon); - // Final commit to the DB - self.db.read().write_buffered(batch); - chain.commit(); - - self.check_epoch_end(&header, &chain); - - self.update_last_hashes(&parent, hash); - - if let Err(e) = self.prune_ancient(state, &chain) { - warn!("Failed to prune ancient state data: {}", e); - } - - route - } - - // check for epoch end signal and write pending transition if it occurs. - // state for the given block must be available. - fn check_epoch_end_signal( - &self, - header: &Header, - block_bytes: &[u8], - receipts: &[Receipt], - state_db: &StateDB, - chain: &BlockChain, - batch: &mut DBTransaction, - ) { - use engines::EpochChange; - - let hash = header.hash(); - let auxiliary = ::machine::AuxiliaryData { - bytes: Some(block_bytes), - receipts: Some(&receipts), - }; - - match self.engine.signals_epoch_end(header, auxiliary) { - EpochChange::Yes(proof) => { - use engines::epoch::PendingTransition; - use engines::Proof; - - let proof = match proof { - Proof::Known(proof) => proof, - Proof::WithState(with_state) => { - let env_info = EnvInfo { - number: header.number(), - author: header.author().clone(), - timestamp: header.timestamp(), - difficulty: header.difficulty().clone(), - last_hashes: self.build_last_hashes(header.parent_hash().clone()), - gas_used: U256::default(), - gas_limit: u64::max_value().into(), - }; - - let call = move |addr, data| { - let mut state_db = state_db.boxed_clone(); - let backend = ::state::backend::Proving::new(state_db.as_hashdb_mut()); - - let transaction = - self.contract_call_tx(BlockId::Hash(*header.parent_hash()), addr, data); - - let mut state = State::from_existing( - backend, - header.state_root().clone(), - self.engine.account_start_nonce(header.number()), - self.factories.clone(), - ).expect("state known to be available for just-imported block; qed"); - - let options = TransactOptions::with_no_tracing().dont_check_nonce(); - let res = Executive::new(&mut state, &env_info, self.engine.machine()) - .transact(&transaction, options); - - let res = match res { - Err(ExecutionError::Internal(e)) => - Err(format!("Internal error: {}", e)), - Err(e) => { - trace!(target: "client", "Proved call failed: {}", e); - Ok((Vec::new(), state.drop().1.extract_proof())) - } - Ok(res) => Ok((res.output, state.drop().1.extract_proof())), - }; - - res.map(|(output, proof)| (output, proof.into_iter().map(|x| x.into_vec()).collect())) - }; - - match with_state.generate_proof(&call) { - Ok(proof) => proof, - Err(e) => { - warn!(target: "client", "Failed to generate transition proof for block {}: {}", hash, e); - warn!(target: "client", "Snapshots produced by this client may be incomplete"); - Vec::new() - } - } - } - }; - - debug!(target: "client", "Block {} signals epoch end.", hash); - - let pending = PendingTransition { proof: proof }; - chain.insert_pending_transition(batch, hash, pending); - }, - EpochChange::No => {}, - EpochChange::Unsure(_) => { - warn!(target: "client", "Detected invalid engine implementation."); - warn!(target: "client", "Engine claims to require more block data, but everything provided."); - } - } - } - - // check for ending of epoch and write transition if it occurs. - fn check_epoch_end<'a>(&self, header: &'a Header, chain: &BlockChain) { - let is_epoch_end = self.engine.is_epoch_end( - header, - &(|hash| chain.block_header(&hash)), - &(|hash| chain.get_pending_transition(hash)), // TODO: limit to current epoch. - ); - - if let Some(proof) = is_epoch_end { - debug!(target: "client", "Epoch transition at block {}", header.hash()); - - let mut batch = DBTransaction::new(); - chain.insert_epoch_transition(&mut batch, header.number(), EpochTransition { - block_hash: header.hash(), - block_number: header.number(), - proof: proof, - }); - - // always write the batch directly since epoch transition proofs are - // fetched from a DB iterator and DB iterators are only available on - // flushed data. - self.db.read().write(batch).expect("DB flush failed"); - } + self.importer.import_verified_blocks(self) } // use a state-proving closure for the given block. @@ -890,13 +977,13 @@ impl Client { self.notify(|notify| { notify.transactions_received(hashes.clone(), peer_id); }); - let results = self.miner.import_external_transactions(self, txs); + let results = self.importer.miner.import_external_transactions(self, txs); results.len() } /// Get shared miner reference. pub fn miner(&self) -> Arc { - self.miner.clone() + self.importer.miner.clone() } /// Replace io channel. Useful for testing. @@ -904,6 +991,18 @@ impl Client { *self.io_channel.lock() = io_channel; } + /// Get a copy of the best block's state. + pub fn latest_state(&self) -> State { + let header = self.best_block_header(); + State::from_existing( + self.state_db.read().boxed_clone_canon(&header.hash()), + header.state_root(), + self.engine.account_start_nonce(header.number()), + self.factories.clone() + ) + .expect("State root of best block header always valid.") + } + /// Attempt to get a copy of a specific block's final state. /// /// This will not fail if given BlockId::Latest. @@ -912,8 +1011,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(self.chain.read().best_block_number()).or_else(|| Some(self.state())), - BlockId::Latest => return Some(self.state()), + BlockId::Latest => return Some(self.latest_state()), _ => {}, } @@ -940,25 +1038,15 @@ impl Client { /// This will not fail if given BlockId::Latest. /// Otherwise, this can fail (but may not) if the DB prunes state. pub fn state_at_beginning(&self, id: BlockId) -> Option> { - // fast path for latest state. - match id { - BlockId::Pending => self.state_at(BlockId::Latest), - id => match self.block_number(id) { - None | Some(0) => None, - Some(n) => self.state_at(BlockId::Number(n - 1)), - } + match self.block_number(id) { + None | Some(0) => None, + Some(n) => self.state_at(BlockId::Number(n - 1)), } } /// Get a copy of the best block's state. - pub fn state(&self) -> State { - let header = self.best_block_header(); - State::from_existing( - self.state_db.read().boxed_clone_canon(&header.hash()), - header.state_root(), - self.engine.account_start_nonce(header.number()), - self.factories.clone()) - .expect("State root of best block header always valid.") + pub fn state(&self) -> Box { + Box::new(self.latest_state()) as Box<_> } /// Get info on the cache. @@ -984,7 +1072,7 @@ impl Client { fn check_garbage(&self) { self.chain.read().collect_garbage(); - self.block_queue.collect_garbage(); + self.importer.block_queue.collect_garbage(); self.tracedb.read().collect_garbage(); } @@ -1063,20 +1151,19 @@ impl Client { self.history } - fn block_hash(chain: &BlockChain, miner: &Miner, id: BlockId) -> Option { + fn block_hash(chain: &BlockChain, 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 => 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(), &self.miner, id).map(|hash| TransactionAddress { + TransactionId::Location(id, index) => Self::block_hash(&self.chain.read(), id).map(|hash| TransactionAddress { block_hash: hash, index: index, }) @@ -1167,7 +1254,6 @@ impl Client { BlockId::Hash(ref hash) => self.chain.read().block_number(hash), BlockId::Earliest => Some(0), BlockId::Latest => Some(self.chain.read().best_block_number()), - BlockId::Pending => Some(self.chain.read().best_block_number() + 1), } } } @@ -1177,11 +1263,11 @@ impl snapshot::DatabaseRestore for Client { fn restore_db(&self, new_db: &str) -> Result<(), EthcoreError> { trace!(target: "snapshot", "Replacing client database with {:?}", new_db); - let _import_lock = self.import_lock.lock(); + let _import_lock = self.importer.import_lock.lock(); let mut state_db = self.state_db.write(); let mut chain = self.chain.write(); let mut tracedb = self.tracedb.write(); - self.miner.clear(); + self.importer.miner.clear(); let db = self.db.write(); db.restore(new_db)?; @@ -1193,29 +1279,176 @@ impl snapshot::DatabaseRestore for Client { } } -impl BlockChainClient for Client { - 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(); +impl Nonce for Client { + fn nonce(&self, address: &Address, id: BlockId) -> Option { + self.state_at(id).and_then(|s| s.nonce(address).ok()) + } +} - // that's just a copy of the state. - let mut state = self.state_at(block).ok_or(CallError::StatePruned)?; - let machine = self.engine.machine(); +impl Balance for Client { + fn balance(&self, address: &Address, state: StateOrBlock) -> Option { + match state { + StateOrBlock::State(s) => s.balance(address).ok(), + StateOrBlock::Block(id) => self.state_at(id).and_then(|s| s.balance(address).ok()) + } + } +} - Self::do_virtual_call(machine, &env_info, &mut state, transaction, analytics) +impl AccountData for Client {} + +impl ChainInfo for Client { + fn chain_info(&self) -> BlockChainInfo { + let mut chain_info = self.chain.read().chain_info(); + chain_info.pending_total_difficulty = chain_info.total_difficulty + self.importer.block_queue.total_difficulty(); + chain_info + } +} + +impl BlockInfo 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)) } - 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(); + fn best_block_header(&self) -> encoded::Header { + self.chain.read().best_block_header() + } + + fn block(&self, id: BlockId) -> Option { + let chain = self.chain.read(); + + Self::block_hash(&chain, id).and_then(|hash| { + chain.block(&hash) + }) + } + + fn code_hash(&self, address: &Address, id: BlockId) -> Option { + self.state_at(id).and_then(|s| s.code_hash(address).ok()) + } +} + +impl TransactionInfo for Client { + fn transaction_block(&self, id: TransactionId) -> Option { + self.transaction_address(id).map(|addr| addr.block_hash) + } +} + +impl BlockChainTrait for Client {} + +impl RegistryInfo for Client { + fn registry_address(&self, name: String, block: BlockId) -> Option
{ + let address = self.registrar_address?; + + self.registrar.functions() + .get_address() + .call(keccak(name.as_bytes()), "A", &|data| self.call_contract(block, address, data)) + .ok() + .and_then(|a| if a.is_zero() { + None + } else { + Some(a) + }) + } +} + +impl CallContract for Client { + fn call_contract(&self, block_id: BlockId, address: Address, data: Bytes) -> Result { + let state_pruned = || CallError::StatePruned.to_string(); + let state = &mut self.state_at(block_id).ok_or_else(&state_pruned)?; + let header = self.block_header(block_id).ok_or_else(&state_pruned)?; + + let transaction = self.contract_call_tx(block_id, address, data); + + self.call(&transaction, Default::default(), state, &header.decode()) + .map_err(|e| format!("{:?}", e)) + .map(|executed| executed.output) + } +} + +impl ImportBlock for Client { + fn import_block(&self, bytes: Bytes) -> Result { + use verification::queue::kind::BlockLike; + use verification::queue::kind::blocks::Unverified; + + // create unverified block here so the `keccak` calculation can be cached. + let unverified = Unverified::new(bytes); + + { + if self.chain.read().is_known(&unverified.hash()) { + return Err(BlockImportError::Import(ImportError::AlreadyInChain)); + } + 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()))); + } + } + Ok(self.importer.block_queue.import(unverified)?) + } + + fn import_block_with_receipts(&self, block_bytes: Bytes, receipts_bytes: Bytes) -> Result { + { + // check block order + let header = BlockView::new(&block_bytes).header_view(); + if self.chain.read().is_known(&header.hash()) { + return Err(BlockImportError::Import(ImportError::AlreadyInChain)); + } + 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()))); + } + } + + self.importer.import_old_block(block_bytes, receipts_bytes, &**self.db.read(), &*self.chain.read()).map_err(Into::into) + } +} + +impl StateClient for Client { + type State = State<::state_db::StateDB>; + + fn latest_state(&self) -> Self::State { + Client::latest_state(self) + } + + fn state_at(&self, id: BlockId) -> Option { + Client::state_at(self, id) + } +} + +impl Call for Client { + type State = State<::state_db::StateDB>; + + fn call(&self, transaction: &SignedTransaction, analytics: CallAnalytics, state: &mut Self::State, header: &Header) -> Result { + let env_info = EnvInfo { + number: header.number(), + author: header.author().clone(), + timestamp: header.timestamp(), + difficulty: header.difficulty().clone(), + last_hashes: self.build_last_hashes(header.parent_hash()), + gas_used: U256::default(), + gas_limit: U256::max_value(), + }; + let machine = self.engine.machine(); + + Self::do_virtual_call(&machine, &env_info, state, transaction, analytics) + } + + fn call_many(&self, transactions: &[(SignedTransaction, CallAnalytics)], state: &mut Self::State, header: &Header) -> Result, CallError> { + let mut env_info = EnvInfo { + number: header.number(), + author: header.author().clone(), + timestamp: header.timestamp(), + difficulty: header.difficulty().clone(), + last_hashes: self.build_last_hashes(header.parent_hash()), + gas_used: U256::default(), + 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()); let machine = self.engine.machine(); for &(ref t, analytics) in transactions { - let ret = Self::do_virtual_call(machine, &env_info, &mut state, t, analytics)?; + let ret = Self::do_virtual_call(machine, &env_info, state, t, analytics)?; env_info.gas_used = ret.cumulative_gas_used; results.push(ret); } @@ -1223,17 +1456,24 @@ impl BlockChainClient for Client { Ok(results) } - fn estimate_gas(&self, t: &SignedTransaction, block: BlockId) -> Result { + fn estimate_gas(&self, t: &SignedTransaction, state: &Self::State, header: &Header) -> Result { let (mut upper, max_upper, env_info) = { - let mut env_info = self.env_info(block).ok_or(CallError::StatePruned)?; - let init = env_info.gas_limit; + let init = *header.gas_limit(); let max = init * U256::from(10); - env_info.gas_limit = max; + + let env_info = EnvInfo { + number: header.number(), + author: header.author().clone(), + timestamp: header.timestamp(), + difficulty: header.difficulty().clone(), + last_hashes: self.build_last_hashes(header.parent_hash()), + gas_used: U256::default(), + gas_limit: max, + }; + (init, max, env_info) }; - // that's just a copy of the state. - let original_state = self.state_at(block).ok_or(CallError::StatePruned)?; let sender = t.sender(); let options = || TransactOptions::with_tracing().dont_check_nonce(); @@ -1242,8 +1482,8 @@ impl BlockChainClient for Client { tx.gas = gas; let tx = tx.fake_sign(sender); - let mut state = original_state.clone(); - Ok(Executive::new(&mut state, &env_info, self.engine.machine()) + let mut clone = state.clone(); + Ok(Executive::new(&mut clone, &env_info, self.engine.machine()) .transact_virtual(&tx, options()) .map(|r| r.exception.is_none()) .unwrap_or(false)) @@ -1286,7 +1526,15 @@ impl BlockChainClient for Client { trace!(target: "estimate_gas", "estimate_gas chopping {} .. {}", lower, upper); binary_chop(lower, upper, cond) } +} +impl EngineInfo for Client { + fn engine(&self) -> &EthEngine { + Client::engine(self) + } +} + +impl BlockChainClient for Client { fn replay(&self, id: TransactionId, analytics: CallAnalytics) -> Result { let address = self.transaction_address(id).ok_or(CallError::TransactionNotFound)?; let block = BlockId::Hash(address.block_hash); @@ -1365,24 +1613,6 @@ impl BlockChainClient for Client { } } - fn best_block_header(&self) -> encoded::Header { - self.chain.read().best_block_header() - } - - fn block_header(&self, id: BlockId) -> Option<::encoded::Header> { - let chain = self.chain.read(); - - 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 { self.block_number_ref(&id) } @@ -1390,63 +1620,22 @@ impl BlockChainClient for Client { fn block_body(&self, id: BlockId) -> Option { let chain = self.chain.read(); - 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(chain.best_block_number()) { - return Some(encoded::Block::new(block.rlp_bytes(Seal::Without))); - } - // fall back to latest - return self.block(BlockId::Latest); - } - - Self::block_hash(&chain, &self.miner, id).and_then(|hash| { - chain.block(&hash) - }) + Self::block_hash(&chain, id).and_then(|hash| chain.block_body(&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, &self.miner, id) { + match Self::block_hash(&chain, id) { Some(ref hash) if chain.is_known(hash) => BlockStatus::InChain, - Some(hash) => self.block_queue.status(&hash).into(), + Some(hash) => self.importer.block_queue.status(&hash).into(), None => BlockStatus::Unknown } } fn block_total_difficulty(&self, id: BlockId) -> Option { let chain = self.chain.read(); - 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 { - self.state_at(id).and_then(|s| s.nonce(address).ok()) + Self::block_hash(&chain, id).and_then(|hash| chain.block_details(&hash)).map(|d| d.total_difficulty) } fn storage_root(&self, address: &Address, id: BlockId) -> Option { @@ -1455,23 +1644,24 @@ impl BlockChainClient for Client { fn block_hash(&self, id: BlockId) -> Option { let chain = self.chain.read(); - Self::block_hash(&chain, &self.miner, id) + Self::block_hash(&chain, id) } - fn code(&self, address: &Address, id: BlockId) -> Option> { - self.state_at(id).and_then(|s| s.code(address).ok()).map(|c| c.map(|c| (&*c).clone())) + fn code(&self, address: &Address, state: StateOrBlock) -> Option> { + let result = match state { + StateOrBlock::State(s) => s.code(address).ok(), + StateOrBlock::Block(id) => self.state_at(id).and_then(|s| s.code(address).ok()) + }; + + // Converting from `Option>>` to `Option>` + result.map(|c| c.map(|c| (&*c).clone())) } - fn code_hash(&self, address: &Address, id: BlockId) -> Option { - self.state_at(id).and_then(|s| s.code_hash(address).ok()) - } - - fn balance(&self, address: &Address, id: BlockId) -> Option { - self.state_at(id).and_then(|s| s.balance(address).ok()) - } - - fn storage_at(&self, address: &Address, position: &H256, id: BlockId) -> Option { - self.state_at(id).and_then(|s| s.storage_at(address, position).ok()) + fn storage_at(&self, address: &Address, position: &H256, state: StateOrBlock) -> Option { + match state { + StateOrBlock::State(s) => s.storage_at(address, position).ok(), + StateOrBlock::Block(id) => self.state_at(id).and_then(|s| s.storage_at(address, position).ok()) + } } fn list_accounts(&self, id: BlockId, after: Option<&Address>, count: u64) -> Option> { @@ -1560,10 +1750,6 @@ impl BlockChainClient for Client { self.transaction_address(id).and_then(|address| self.chain.read().transaction(&address)) } - fn transaction_block(&self, id: TransactionId) -> Option { - self.transaction_address(id).map(|addr| addr.block_hash) - } - fn uncle(&self, id: UncleId) -> Option { let index = id.position; self.block_body(id.block).and_then(|body| body.view().uncle_rlp_at(index)) @@ -1613,52 +1799,12 @@ impl BlockChainClient for Client { self.chain.read().block_receipts(hash).map(|receipts| ::rlp::encode(&receipts).into_vec()) } - fn import_block(&self, bytes: Bytes) -> Result { - use verification::queue::kind::BlockLike; - use verification::queue::kind::blocks::Unverified; - - // create unverified block here so the `keccak` calculation can be cached. - let unverified = Unverified::new(bytes); - - { - if self.chain.read().is_known(&unverified.hash()) { - return Err(BlockImportError::Import(ImportError::AlreadyInChain)); - } - 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()))); - } - } - Ok(self.block_queue.import(unverified)?) - } - - fn import_block_with_receipts(&self, block_bytes: Bytes, receipts_bytes: Bytes) -> Result { - { - // check block order - let header = BlockView::new(&block_bytes).header_view(); - if self.chain.read().is_known(&header.hash()) { - return Err(BlockImportError::Import(ImportError::AlreadyInChain)); - } - 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()))); - } - } - self.import_old_block(block_bytes, receipts_bytes).map_err(Into::into) - } - fn queue_info(&self) -> BlockQueueInfo { - self.block_queue.queue_info() + self.importer.block_queue.queue_info() } fn clear_queue(&self) { - self.block_queue.clear(); - } - - fn chain_info(&self) -> BlockChainInfo { - let mut chain_info = self.chain.read().chain_info(); - chain_info.pending_total_difficulty = chain_info.total_difficulty + self.block_queue.total_difficulty(); - chain_info + self.importer.block_queue.clear(); } fn additional_params(&self) -> BTreeMap { @@ -1718,7 +1864,7 @@ impl BlockChainClient for Client { } fn last_hashes(&self) -> LastHashes { - (*self.build_last_hashes(self.chain.read().best_block_hash())).clone() + (*self.build_last_hashes(&self.chain.read().best_block_hash())).clone() } fn queue_transactions(&self, transactions: Vec, peer_id: usize) { @@ -1744,7 +1890,7 @@ impl BlockChainClient for Client { let chain = self.chain.read(); (chain.best_block_number(), chain.best_block_timestamp()) }; - self.miner.ready_transactions(number, timestamp) + self.importer.miner.ready_transactions(number, timestamp) } fn queue_consensus_message(&self, message: Bytes) { @@ -1775,102 +1921,31 @@ 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, Default::default(), block_id) - .map_err(|e| format!("{:?}", e)) - .map(|executed| { - executed.output - }) - } - fn transact_contract(&self, address: Address, data: Bytes) -> Result { let transaction = Transaction { - nonce: self.latest_nonce(&self.miner.author()), + nonce: self.latest_nonce(&self.importer.miner.author()), action: Action::Call(address), - gas: self.miner.gas_floor_target(), - gas_price: self.miner.sensible_gas_price(), + gas: self.importer.miner.gas_floor_target(), + gas_price: self.importer.miner.sensible_gas_price(), value: U256::zero(), data: data, }; let chain_id = self.engine.signing_chain_id(&self.latest_env_info()); let signature = self.engine.sign(transaction.hash(chain_id))?; let signed = SignedTransaction::new(transaction.with_signature(signature, chain_id))?; - self.miner.import_own_transaction(self, signed.into()) + self.importer.miner.import_own_transaction(self, signed.into()) } fn registrar_address(&self) -> Option
{ self.registrar_address.clone() } - fn registry_address(&self, name: String, block: BlockId) -> Option
{ - let address = match self.registrar_address { - Some(address) => address, - None => return None, - }; - - self.registrar.functions() - .get_address() - .call(keccak(name.as_bytes()), "A", &|data| self.call_contract(block, address, data)) - .ok() - .and_then(|a| if a.is_zero() { - None - } else { - Some(a) - }) - } - fn eip86_transition(&self) -> u64 { self.engine().params().eip86_transition } } -impl MiningBlockChainClient for Client { - fn as_block_chain_client(&self) -> &BlockChainClient { self } - - fn latest_schedule(&self) -> Schedule { - self.engine.schedule(self.latest_env_info().number) - } - - fn prepare_open_block(&self, author: Address, gas_range_target: (U256, U256), extra_data: Bytes) -> OpenBlock { - let engine = &*self.engine; - let chain = self.chain.read(); - let h = chain.best_block_hash(); - let best_header = &chain.block_header(&h) - .expect("h is best block hash: so its header must exist: qed"); - - let is_epoch_begin = chain.epoch_transition(best_header.number(), h).is_some(); - let mut open_block = OpenBlock::new( - engine, - self.factories.clone(), - self.tracedb.read().tracing_enabled(), - self.state_db.read().boxed_clone_canon(&h), - best_header, - self.build_last_hashes(h.clone()), - author, - gas_range_target, - extra_data, - is_epoch_begin, - ).expect("OpenBlock::new only fails if parent state root invalid; state root of best block's header is never invalid; qed"); - - // Add uncles - chain - .find_uncle_headers(&h, engine.maximum_uncle_age()) - .unwrap_or_else(Vec::new) - .into_iter() - .take(engine.maximum_uncle_count(open_block.header().number())) - .foreach(|h| { - open_block.push_uncle(h).expect("pushing maximum_uncle_count; - open_block was just created; - push_uncle is not ok only if more than maximum_uncle_count is pushed; - so all push_uncle are Ok; - qed"); - }); - - open_block - } - +impl ReopenBlock for Client { fn reopen_block(&self, block: ClosedBlock) -> OpenBlock { let engine = &*self.engine; let mut block = block.reopen(engine); @@ -1897,44 +1972,76 @@ impl MiningBlockChainClient for Client { } block } +} - fn vm_factory(&self) -> &VmFactory { - &self.factories.vm +impl PrepareOpenBlock for Client { + fn prepare_open_block(&self, author: Address, gas_range_target: (U256, U256), extra_data: Bytes) -> OpenBlock { + let engine = &*self.engine; + let chain = self.chain.read(); + let h = chain.best_block_hash(); + let best_header = &chain.block_header(&h) + .expect("h is best block hash: so its header must exist: qed"); + + let is_epoch_begin = chain.epoch_transition(best_header.number(), h).is_some(); + let mut open_block = OpenBlock::new( + engine, + self.factories.clone(), + self.tracedb.read().tracing_enabled(), + self.state_db.read().boxed_clone_canon(&h), + best_header, + self.build_last_hashes(&h), + author, + gas_range_target, + extra_data, + is_epoch_begin, + ).expect("OpenBlock::new only fails if parent state root invalid; state root of best block's header is never invalid; qed"); + + // Add uncles + chain + .find_uncle_headers(&h, engine.maximum_uncle_age()) + .unwrap_or_else(Vec::new) + .into_iter() + .take(engine.maximum_uncle_count(open_block.header().number())) + .foreach(|h| { + open_block.push_uncle(h).expect("pushing maximum_uncle_count; + open_block was just created; + push_uncle is not ok only if more than maximum_uncle_count is pushed; + so all push_uncle are Ok; + qed"); + }); + + open_block } +} - fn broadcast_proposal_block(&self, block: SealedBlock) { - self.notify(|notify| { - notify.new_blocks( - vec![], - vec![], - vec![], - vec![], - vec![], - vec![block.rlp_bytes()], - 0, - ); - }); +impl BlockProducer for Client {} + +impl ScheduleInfo for Client { + fn latest_schedule(&self) -> Schedule { + self.engine.schedule(self.latest_env_info().number) } +} +impl ImportSealedBlock for Client { fn import_sealed_block(&self, block: SealedBlock) -> ImportResult { let h = block.header().hash(); let start = precise_time_ns(); let route = { // scope for self.import_lock - let _import_lock = self.import_lock.lock(); + let _import_lock = self.importer.import_lock.lock(); trace_time!("import_sealed_block"); let number = block.header().number(); let block_data = block.rlp_bytes(); let header = block.header().clone(); - let route = self.commit_block(block, &header, &block_data); + let route = self.importer.commit_block(block, &header, &block_data, self); trace!(target: "client", "Imported sealed block #{} ({})", number, h); self.state_db.write().sync_cache(&route.enacted, &route.retracted, false); route }; - let (enacted, retracted) = self.calculate_enacted_retracted(&[route]); - self.miner.chain_new_blocks(self, &[h.clone()], &[], &enacted, &retracted); + let (enacted, retracted) = self.importer.calculate_enacted_retracted(&[route]); + self.importer.miner.chain_new_blocks(self, &[h.clone()], &[], &enacted, &retracted); self.notify(|notify| { notify.new_blocks( vec![h.clone()], @@ -1951,13 +2058,37 @@ impl MiningBlockChainClient for Client { } } +impl BroadcastProposalBlock for Client { + fn broadcast_proposal_block(&self, block: SealedBlock) { + self.notify(|notify| { + notify.new_blocks( + vec![], + vec![], + vec![], + vec![], + vec![], + vec![block.rlp_bytes()], + 0, + ); + }); + } +} + +impl SealedBlockImporter for Client {} + +impl MiningBlockChainClient for Client { + fn vm_factory(&self) -> &VmFactory { + &self.factories.vm + } +} + impl super::traits::EngineClient for Client { fn update_sealing(&self) { - self.miner.update_sealing(self) + self.importer.miner.update_sealing(self) } fn submit_seal(&self, block_hash: H256, seal: Vec) { - if self.miner.submit_seal(self, block_hash, seal).is_err() { + if self.importer.miner.submit_seal(self, block_hash, seal).is_err() { warn!(target: "poa", "Wrong internal seal submission!") } } @@ -1970,10 +2101,6 @@ impl super::traits::EngineClient for Client { self.chain.read().epoch_transition_for(parent_hash) } - fn chain_info(&self) -> BlockChainInfo { - BlockChainClient::chain_info(self) - } - fn as_full_client(&self) -> Option<&BlockChainClient> { Some(self) } fn block_number(&self, id: BlockId) -> Option { @@ -2077,7 +2204,7 @@ mod tests { #[test] fn should_not_cache_details_before_commit() { - use client::BlockChainClient; + use client::{BlockChainClient, ChainInfo}; use tests::helpers::*; use std::thread; diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 0a7ff45b2..56eb5ae1d 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -30,9 +30,12 @@ pub use self::error::Error; pub use self::evm_test_client::{EvmTestClient, EvmTestError, TransactResult}; pub use self::test_client::{TestBlockChainClient, EachBlockWith}; pub use self::chain_notify::ChainNotify; -pub use self::traits::{BlockChainClient, MiningBlockChainClient, EngineClient}; - -pub use self::traits::ProvingBlockChainClient; +pub use self::traits::{ + Nonce, Balance, ChainInfo, BlockInfo, ReopenBlock, PrepareOpenBlock, CallContract, TransactionInfo, RegistryInfo, ScheduleInfo, ImportSealedBlock, BroadcastProposalBlock, ImportBlock, + StateOrBlock, StateClient, Call, EngineInfo, AccountData, BlockChain, BlockProducer, SealedBlockImporter +}; +pub use state::StateInfo; +pub use self::traits::{BlockChainClient, MiningBlockChainClient, EngineClient, ProvingBlockChainClient}; pub use types::ids::*; pub use types::trace_filter::Filter as TraceFilter; diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index cd3ce468e..b806183a2 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -34,9 +34,11 @@ use ethkey::{Generator, Random}; use transaction::{self, Transaction, LocalizedTransaction, PendingTransaction, SignedTransaction, Action}; use blockchain::{TreeRoute, BlockReceipts}; use client::{ - BlockChainClient, MiningBlockChainClient, BlockChainInfo, BlockStatus, BlockId, + Nonce, Balance, ChainInfo, BlockInfo, ReopenBlock, CallContract, TransactionInfo, RegistryInfo, + PrepareOpenBlock, BlockChainClient, MiningBlockChainClient, BlockChainInfo, BlockStatus, BlockId, TransactionId, UncleId, TraceId, TraceFilter, LastHashes, CallAnalytics, BlockImportError, - ProvingBlockChainClient, + ProvingBlockChainClient, ScheduleInfo, ImportSealedBlock, BroadcastProposalBlock, ImportBlock, StateOrBlock, + Call, StateClient, EngineInfo, AccountData, BlockChain, BlockProducer, SealedBlockImporter }; use db::{NUM_COLUMNS, COL_STATE}; use header::{Header as BlockHeader, BlockNumber}; @@ -59,7 +61,11 @@ use executive::Executed; use error::CallError; use trace::LocalizedTrace; use state_db::StateDB; +use header::Header; use encoded; +use engines::EthEngine; +use trie; +use state::StateInfo; /// Test client. pub struct TestBlockChainClient { @@ -316,7 +322,7 @@ impl TestBlockChainClient { BlockId::Hash(hash) => Some(hash), BlockId::Number(n) => self.numbers.read().get(&(n as usize)).cloned(), BlockId::Earliest => self.numbers.read().get(&0).cloned(), - BlockId::Latest | BlockId::Pending => self.numbers.read().get(&(self.numbers.read().len() - 1)).cloned() + BlockId::Latest => self.numbers.read().get(&(self.numbers.read().len() - 1)).cloned() } } @@ -357,13 +363,13 @@ pub fn get_temp_state_db() -> StateDB { StateDB::new(journal_db, 1024 * 1024) } -impl MiningBlockChainClient for TestBlockChainClient { - fn as_block_chain_client(&self) -> &BlockChainClient { self } - - fn latest_schedule(&self) -> Schedule { - Schedule::new_post_eip150(24576, true, true, true) +impl ReopenBlock for TestBlockChainClient { + fn reopen_block(&self, block: ClosedBlock) -> OpenBlock { + block.reopen(&*self.spec.engine) } +} +impl PrepareOpenBlock for TestBlockChainClient { fn prepare_open_block(&self, author: Address, gas_range_target: (U256, U256), extra_data: Bytes) -> OpenBlock { let engine = &*self.spec.engine; let genesis_header = self.spec.genesis_header(); @@ -386,39 +392,221 @@ impl MiningBlockChainClient for TestBlockChainClient { open_block.set_timestamp(*self.latest_block_timestamp.read()); open_block } +} - fn reopen_block(&self, block: ClosedBlock) -> OpenBlock { - block.reopen(&*self.spec.engine) - } - - fn vm_factory(&self) -> &VmFactory { - &self.vm_factory +impl ScheduleInfo for TestBlockChainClient { + fn latest_schedule(&self) -> Schedule { + Schedule::new_post_eip150(24576, true, true, true) } +} +impl ImportSealedBlock for TestBlockChainClient { fn import_sealed_block(&self, _block: SealedBlock) -> ImportResult { Ok(H256::default()) } +} +impl BlockProducer for TestBlockChainClient {} + +impl BroadcastProposalBlock for TestBlockChainClient { fn broadcast_proposal_block(&self, _block: SealedBlock) {} } -impl BlockChainClient for TestBlockChainClient { - fn call(&self, _t: &SignedTransaction, _analytics: CallAnalytics, _block: BlockId) -> Result { +impl SealedBlockImporter for TestBlockChainClient {} + +impl MiningBlockChainClient for TestBlockChainClient { + fn vm_factory(&self) -> &VmFactory { + &self.vm_factory + } +} + +impl Nonce 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)), + _ => None, + } + } + + fn latest_nonce(&self, address: &Address) -> U256 { + self.nonce(address, BlockId::Latest).unwrap() + } +} + +impl Balance for TestBlockChainClient { + fn balance(&self, address: &Address, state: StateOrBlock) -> Option { + match state { + StateOrBlock::Block(BlockId::Latest) | StateOrBlock::State(_) => Some(self.balances.read().get(address).cloned().unwrap_or_else(U256::zero)), + _ => None, + } + } + + fn latest_balance(&self, address: &Address) -> U256 { + self.balance(address, BlockId::Latest.into()).unwrap() + } +} + +impl AccountData for TestBlockChainClient {} + +impl ChainInfo for TestBlockChainClient { + fn chain_info(&self) -> BlockChainInfo { + let number = self.blocks.read().len() as BlockNumber - 1; + BlockChainInfo { + total_difficulty: *self.difficulty.read(), + pending_total_difficulty: *self.difficulty.read(), + genesis_hash: self.genesis_hash.clone(), + best_block_hash: self.last_hash.read().clone(), + best_block_number: number, + best_block_timestamp: number, + first_block_hash: self.first_block.read().as_ref().map(|x| x.0), + first_block_number: self.first_block.read().as_ref().map(|x| x.1), + ancient_block_hash: self.ancient_block.read().as_ref().map(|x| x.0), + ancient_block_number: self.ancient_block.read().as_ref().map(|x| x.1) + } + } +} + +impl BlockInfo for TestBlockChainClient { + fn block_header(&self, id: BlockId) -> Option { + self.block_hash(id) + .and_then(|hash| self.blocks.read().get(&hash).map(|r| Rlp::new(r).at(0).as_raw().to_vec())) + .map(encoded::Header::new) + } + + fn best_block_header(&self) -> encoded::Header { + self.block_header(BlockId::Hash(self.chain_info().best_block_hash)) + .expect("Best block always has header.") + } + + fn block(&self, id: BlockId) -> Option { + self.block_hash(id) + .and_then(|hash| self.blocks.read().get(&hash).cloned()) + .map(encoded::Block::new) + } + + fn code_hash(&self, address: &Address, id: BlockId) -> Option { + match id { + BlockId::Latest => self.code.read().get(address).map(|c| keccak(&c)), + _ => None, + } + } +} + +impl CallContract for TestBlockChainClient { + fn call_contract(&self, _id: BlockId, _address: Address, _data: Bytes) -> Result { Ok(vec![]) } +} + +impl TransactionInfo for TestBlockChainClient { + fn transaction_block(&self, _id: TransactionId) -> Option { + None // Simple default. + } +} + +impl BlockChain for TestBlockChainClient {} + +impl RegistryInfo for TestBlockChainClient { + fn registry_address(&self, _name: String, _block: BlockId) -> Option
{ None } +} + +impl ImportBlock for TestBlockChainClient { + fn import_block(&self, b: Bytes) -> Result { + let header = Rlp::new(&b).val_at::(0); + let h = header.hash(); + let number: usize = header.number() as usize; + if number > self.blocks.read().len() { + panic!("Unexpected block number. Expected {}, got {}", self.blocks.read().len(), number); + } + if number > 0 { + match self.blocks.read().get(header.parent_hash()) { + Some(parent) => { + let parent = Rlp::new(parent).val_at::(0); + if parent.number() != (header.number() - 1) { + panic!("Unexpected block parent"); + } + }, + None => { + panic!("Unknown block parent {:?} for block {}", header.parent_hash(), number); + } + } + } + let len = self.numbers.read().len(); + if number == len { + { + let mut difficulty = self.difficulty.write(); + *difficulty = *difficulty + header.difficulty().clone(); + } + mem::replace(&mut *self.last_hash.write(), h.clone()); + self.blocks.write().insert(h.clone(), b); + self.numbers.write().insert(number, h.clone()); + let mut parent_hash = header.parent_hash().clone(); + if number > 0 { + let mut n = number - 1; + while n > 0 && self.numbers.read()[&n] != parent_hash { + *self.numbers.write().get_mut(&n).unwrap() = parent_hash.clone(); + n -= 1; + parent_hash = Rlp::new(&self.blocks.read()[&parent_hash]).val_at::(0).parent_hash().clone(); + } + } + } + else { + self.blocks.write().insert(h.clone(), b.to_vec()); + } + Ok(h) + } + + fn import_block_with_receipts(&self, b: Bytes, _r: Bytes) -> Result { + self.import_block(b) + } +} + +impl Call for TestBlockChainClient { + // State will not be used by test client anyway, since all methods that accept state are mocked + type State = (); + + fn call(&self, _t: &SignedTransaction, _analytics: CallAnalytics, _state: &mut Self::State, _header: &Header) -> Result { self.execution_result.read().clone().unwrap() } - fn call_many(&self, txs: &[(SignedTransaction, CallAnalytics)], block: BlockId) -> Result, CallError> { + fn call_many(&self, txs: &[(SignedTransaction, CallAnalytics)], state: &mut Self::State, header: &Header) -> Result, CallError> { let mut res = Vec::with_capacity(txs.len()); for &(ref tx, analytics) in txs { - res.push(self.call(tx, analytics, block)?); + res.push(self.call(tx, analytics, state, header)?); } Ok(res) } - fn estimate_gas(&self, _t: &SignedTransaction, _block: BlockId) -> Result { + fn estimate_gas(&self, _t: &SignedTransaction, _state: &Self::State, _header: &Header) -> Result { Ok(21000.into()) } +} +impl StateInfo for () { + fn nonce(&self, _address: &Address) -> trie::Result { unimplemented!() } + fn balance(&self, _address: &Address) -> trie::Result { unimplemented!() } + fn storage_at(&self, _address: &Address, _key: &H256) -> trie::Result { unimplemented!() } + fn code(&self, _address: &Address) -> trie::Result>> { unimplemented!() } +} + +impl StateClient for TestBlockChainClient { + // State will not be used by test client anyway, since all methods that accept state are mocked + type State = (); + + fn latest_state(&self) -> Self::State { + () + } + + fn state_at(&self, _id: BlockId) -> Option { + Some(()) + } +} + +impl EngineInfo for TestBlockChainClient { + fn engine(&self) -> &EthEngine { + unimplemented!() + } +} + +impl BlockChainClient for TestBlockChainClient { fn replay(&self, _id: TransactionId, _analytics: CallAnalytics) -> Result { self.execution_result.read().clone().unwrap() } @@ -435,49 +623,20 @@ impl BlockChainClient for TestBlockChainClient { Self::block_hash(self, id) } - fn nonce(&self, address: &Address, id: BlockId) -> Option { - match id { - BlockId::Latest | BlockId::Pending => Some(self.nonces.read().get(address).cloned().unwrap_or(self.spec.params().account_start_nonce)), - _ => None, - } - } - fn storage_root(&self, _address: &Address, _id: BlockId) -> Option { None } - fn latest_nonce(&self, address: &Address) -> U256 { - self.nonce(address, BlockId::Latest).unwrap() - } - - fn code(&self, address: &Address, id: BlockId) -> Option> { - match id { - BlockId::Latest | BlockId::Pending => Some(self.code.read().get(address).cloned()), + fn code(&self, address: &Address, state: StateOrBlock) -> Option> { + match state { + StateOrBlock::Block(BlockId::Latest) => Some(self.code.read().get(address).cloned()), _ => None, } } - fn code_hash(&self, address: &Address, id: BlockId) -> Option { - match id { - BlockId::Latest | BlockId::Pending => self.code.read().get(address).map(|c| keccak(&c)), - _ => None, - } - } - - fn balance(&self, address: &Address, id: BlockId) -> Option { - match id { - BlockId::Latest | BlockId::Pending => Some(self.balances.read().get(address).cloned().unwrap_or_else(U256::zero)), - _ => None, - } - } - - fn latest_balance(&self, address: &Address) -> U256 { - self.balance(address, BlockId::Latest).unwrap() - } - - fn storage_at(&self, address: &Address, position: &H256, id: BlockId) -> Option { - match id { - BlockId::Latest | BlockId::Pending => Some(self.storage.read().get(&(address.clone(), position.clone())).cloned().unwrap_or_else(H256::new)), + fn storage_at(&self, address: &Address, position: &H256, state: StateOrBlock) -> Option { + match state { + StateOrBlock::Block(BlockId::Latest) => Some(self.storage.read().get(&(address.clone(), position.clone())).cloned().unwrap_or_else(H256::new)), _ => None, } } @@ -493,10 +652,6 @@ impl BlockChainClient for TestBlockChainClient { None // Simple default. } - fn transaction_block(&self, _id: TransactionId) -> Option { - None // Simple default. - } - fn uncle(&self, _id: UncleId) -> Option { None // Simple default. } @@ -522,17 +677,6 @@ impl BlockChainClient for TestBlockChainClient { unimplemented!(); } - fn best_block_header(&self) -> encoded::Header { - self.block_header(BlockId::Hash(self.chain_info().best_block_hash)) - .expect("Best block always has header.") - } - - fn block_header(&self, id: BlockId) -> Option { - self.block_hash(id) - .and_then(|hash| self.blocks.read().get(&hash).map(|r| Rlp::new(r).at(0).as_raw().to_vec())) - .map(encoded::Header::new) - } - fn block_number(&self, _id: BlockId) -> Option { unimplemented!() } @@ -546,12 +690,6 @@ impl BlockChainClient for TestBlockChainClient { })) } - fn block(&self, id: BlockId) -> Option { - self.block_hash(id) - .and_then(|hash| self.blocks.read().get(&hash).cloned()) - .map(encoded::Block::new) - } - fn block_extra_info(&self, id: BlockId) -> Option> { self.block(id) .map(|block| block.view().header()) @@ -564,7 +702,6 @@ impl BlockChainClient for TestBlockChainClient { 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::Earliest => BlockStatus::InChain, - BlockId::Pending => BlockStatus::Pending, _ => BlockStatus::Unknown, } } @@ -628,55 +765,6 @@ impl BlockChainClient for TestBlockChainClient { None } - fn import_block(&self, b: Bytes) -> Result { - let header = Rlp::new(&b).val_at::(0); - let h = header.hash(); - let number: usize = header.number() as usize; - if number > self.blocks.read().len() { - panic!("Unexpected block number. Expected {}, got {}", self.blocks.read().len(), number); - } - if number > 0 { - match self.blocks.read().get(header.parent_hash()) { - Some(parent) => { - let parent = Rlp::new(parent).val_at::(0); - if parent.number() != (header.number() - 1) { - panic!("Unexpected block parent"); - } - }, - None => { - panic!("Unknown block parent {:?} for block {}", header.parent_hash(), number); - } - } - } - let len = self.numbers.read().len(); - if number == len { - { - let mut difficulty = self.difficulty.write(); - *difficulty = *difficulty + header.difficulty().clone(); - } - mem::replace(&mut *self.last_hash.write(), h.clone()); - self.blocks.write().insert(h.clone(), b); - self.numbers.write().insert(number, h.clone()); - let mut parent_hash = header.parent_hash().clone(); - if number > 0 { - let mut n = number - 1; - while n > 0 && self.numbers.read()[&n] != parent_hash { - *self.numbers.write().get_mut(&n).unwrap() = parent_hash.clone(); - n -= 1; - parent_hash = Rlp::new(&self.blocks.read()[&parent_hash]).val_at::(0).parent_hash().clone(); - } - } - } - else { - self.blocks.write().insert(h.clone(), b.to_vec()); - } - Ok(h) - } - - fn import_block_with_receipts(&self, b: Bytes, _r: Bytes) -> Result { - self.import_block(b) - } - fn queue_info(&self) -> QueueInfo { QueueInfo { verified_queue_size: self.queue_size.load(AtomicOrder::Relaxed), @@ -695,22 +783,6 @@ impl BlockChainClient for TestBlockChainClient { Default::default() } - fn chain_info(&self) -> BlockChainInfo { - let number = self.blocks.read().len() as BlockNumber - 1; - BlockChainInfo { - total_difficulty: *self.difficulty.read(), - pending_total_difficulty: *self.difficulty.read(), - genesis_hash: self.genesis_hash.clone(), - best_block_hash: self.last_hash.read().clone(), - best_block_number: number, - best_block_timestamp: number, - first_block_hash: self.first_block.read().as_ref().map(|x| x.0), - first_block_number: self.first_block.read().as_ref().map(|x| x.1), - ancient_block_hash: self.ancient_block.read().as_ref().map(|x| x.0), - ancient_block_number: self.ancient_block.read().as_ref().map(|x| x.1) - } - } - fn filter_traces(&self, _filter: TraceFilter) -> Option> { self.traces.read().clone() } @@ -762,8 +834,6 @@ impl BlockChainClient for TestBlockChainClient { } } - fn call_contract(&self, _id: BlockId, _address: Address, _data: Bytes) -> Result { Ok(vec![]) } - fn transact_contract(&self, address: Address, data: Bytes) -> Result { let transaction = Transaction { nonce: self.latest_nonce(&self.miner.author()), @@ -781,8 +851,6 @@ impl BlockChainClient for TestBlockChainClient { fn registrar_address(&self) -> Option
{ None } - fn registry_address(&self, _name: String, _block: BlockId) -> Option
{ None } - fn eip86_transition(&self) -> u64 { u64::max_value() } } @@ -821,10 +889,6 @@ impl super::traits::EngineClient for TestBlockChainClient { None } - fn chain_info(&self) -> BlockChainInfo { - BlockChainClient::chain_info(self) - } - fn as_full_client(&self) -> Option<&BlockChainClient> { Some(self) } fn block_number(&self, id: BlockId) -> Option { diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 1c623fd05..1228f8768 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -32,6 +32,9 @@ use receipt::LocalizedReceipt; use trace::LocalizedTrace; use transaction::{LocalizedTransaction, PendingTransaction, SignedTransaction, ImportResult as TransactionImportResult}; use verification::queue::QueueInfo as BlockQueueInfo; +use state::StateInfo; +use header::Header; +use engines::EthEngine; use ethereum_types::{H256, U256, Address}; use bytes::Bytes; @@ -46,80 +49,198 @@ use types::block_status::BlockStatus; use types::mode::Mode; use types::pruning_info::PruningInfo; -/// Blockchain database client. Owns and manages a blockchain and a block queue. -pub trait BlockChainClient : Sync + Send { +/// State information to be used during client query +pub enum StateOrBlock { + /// State to be used, may be pending + State(Box), - /// Get raw block header data by block id. - fn block_header(&self, id: BlockId) -> Option; + /// Id of an existing block from a chain to get state from + Block(BlockId) +} - /// Look up the block number for the given block ID. - fn block_number(&self, id: BlockId) -> Option; +impl From for StateOrBlock { + fn from(info: S) -> StateOrBlock { + StateOrBlock::State(Box::new(info) as Box<_>) + } +} - /// Get raw block body data by block id. - /// Block body is an RLP list of two items: uncles and transactions. - fn block_body(&self, id: BlockId) -> Option; +impl From> for StateOrBlock { + fn from(info: Box) -> StateOrBlock { + StateOrBlock::State(info) + } +} - /// Get raw block data by block header hash. - fn block(&self, id: BlockId) -> Option; - - /// Get block status by block header hash. - fn block_status(&self, id: BlockId) -> BlockStatus; - - /// Get block total difficulty. - fn block_total_difficulty(&self, id: BlockId) -> Option; +impl From for StateOrBlock { + fn from(id: BlockId) -> StateOrBlock { + StateOrBlock::Block(id) + } +} +/// Provides `nonce` and `latest_nonce` methods +pub trait Nonce { /// Attempt to get address nonce at given block. /// May not fail on BlockId::Latest. fn nonce(&self, address: &Address, id: BlockId) -> Option; - /// Attempt to get address storage root at given block. - /// May not fail on BlockId::Latest. - fn storage_root(&self, address: &Address, id: BlockId) -> Option; - /// Get address nonce at the latest block's state. fn latest_nonce(&self, address: &Address) -> U256 { self.nonce(address, BlockId::Latest) .expect("nonce will return Some when given BlockId::Latest. nonce was given BlockId::Latest. \ Therefore nonce has returned Some; qed") } +} + +/// Provides `balance` and `latest_balance` methods +pub trait Balance { + /// Get address balance at the given block's state. + /// + /// May not return None if given BlockId::Latest. + /// Returns None if and only if the block's root hash has been pruned from the DB. + fn balance(&self, address: &Address, state: StateOrBlock) -> Option; + + /// Get address balance at the latest block's state. + fn latest_balance(&self, address: &Address) -> U256 { + self.balance(address, BlockId::Latest.into()) + .expect("balance will return Some if given BlockId::Latest. balance was given BlockId::Latest \ + Therefore balance has returned Some; qed") + } +} + +/// Provides methods to access account info +pub trait AccountData: Nonce + Balance {} + +/// Provides `chain_info` method +pub trait ChainInfo { + /// Get blockchain information. + fn chain_info(&self) -> BlockChainInfo; +} + +/// Provides various information on a block by it's ID +pub trait BlockInfo { + /// Get raw block header data by block id. + fn block_header(&self, id: BlockId) -> Option; + + /// Get the best block header. + fn best_block_header(&self) -> encoded::Header; + + /// Get raw block data by block header hash. + fn block(&self, id: BlockId) -> Option; + + /// Get address code hash at given block's state. + fn code_hash(&self, address: &Address, id: BlockId) -> Option; +} + +/// Provides various information on a transaction by it's ID +pub trait TransactionInfo { + /// Get the hash of block that contains the transaction, if any. + fn transaction_block(&self, id: TransactionId) -> Option; +} + +/// Provides methods to access chain state +pub trait StateClient { + /// Type representing chain state + type State: StateInfo; + + /// Get a copy of the best block's state. + fn latest_state(&self) -> Self::State; + + /// Attempt to get a copy of a specific block's final state. + /// + /// This will not fail if given BlockId::Latest. + /// Otherwise, this can fail (but may not) if the DB prunes state or the block + /// is unknown. + fn state_at(&self, id: BlockId) -> Option; +} + +/// Provides various blockchain information, like block header, chain state etc. +pub trait BlockChain: ChainInfo + BlockInfo + TransactionInfo {} + +/// Provides information on a blockchain service and it's registry +pub trait RegistryInfo { + /// Get the address of a particular blockchain service, if available. + fn registry_address(&self, name: String, block: BlockId) -> Option
; +} + +// FIXME Why these methods belong to BlockChainClient and not MiningBlockChainClient? +/// Provides methods to import block into blockchain +pub trait ImportBlock { + /// Import a block into the blockchain. + fn import_block(&self, bytes: Bytes) -> Result; + + /// Import a block with transaction receipts. Does no sealing and transaction validation. + fn import_block_with_receipts(&self, block_bytes: Bytes, receipts_bytes: Bytes) -> Result; +} + +/// Provides `call_contract` method +pub trait CallContract { + /// Like `call`, but with various defaults. Designed to be used for calling contracts. + fn call_contract(&self, id: BlockId, address: Address, data: Bytes) -> Result; +} + +/// Provides `call` and `call_many` methods +pub trait Call { + /// Type representing chain state + type State: StateInfo; + + /// Makes a non-persistent transaction call. + fn call(&self, tx: &SignedTransaction, analytics: CallAnalytics, state: &mut Self::State, header: &Header) -> 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)], state: &mut Self::State, header: &Header) -> Result, CallError>; + + /// Estimates how much gas will be necessary for a call. + fn estimate_gas(&self, t: &SignedTransaction, state: &Self::State, header: &Header) -> Result; +} + +/// Provides `engine` method +pub trait EngineInfo { + /// Get underlying engine object + fn engine(&self) -> &EthEngine; +} + +/// Blockchain database client. Owns and manages a blockchain and a block queue. +pub trait BlockChainClient : Sync + Send + AccountData + BlockChain + CallContract + RegistryInfo + ImportBlock { + /// Look up the block number for the given block ID. + fn block_number(&self, id: BlockId) -> Option; + + /// Get raw block body data by block id. + /// Block body is an RLP list of two items: uncles and transactions. + fn block_body(&self, id: BlockId) -> Option; + + /// Get block status by block header hash. + fn block_status(&self, id: BlockId) -> BlockStatus; + + /// Get block total difficulty. + fn block_total_difficulty(&self, id: BlockId) -> Option; + + /// Attempt to get address storage root at given block. + /// May not fail on BlockId::Latest. + fn storage_root(&self, address: &Address, id: BlockId) -> Option; /// Get block hash. fn block_hash(&self, id: BlockId) -> Option; /// Get address code at given block's state. - fn code(&self, address: &Address, id: BlockId) -> Option>; + fn code(&self, address: &Address, state: StateOrBlock) -> Option>; /// Get address code at the latest block's state. fn latest_code(&self, address: &Address) -> Option { - self.code(address, BlockId::Latest) + self.code(address, BlockId::Latest.into()) .expect("code will return Some if given BlockId::Latest; qed") } /// Get address code hash at given block's state. - fn code_hash(&self, address: &Address, id: BlockId) -> Option; - - /// Get address balance at the given block's state. - /// - /// May not return None if given BlockId::Latest. - /// Returns None if and only if the block's root hash has been pruned from the DB. - fn balance(&self, address: &Address, id: BlockId) -> Option; - - /// Get address balance at the latest block's state. - fn latest_balance(&self, address: &Address) -> U256 { - self.balance(address, BlockId::Latest) - .expect("balance will return Some if given BlockId::Latest. balance was given BlockId::Latest \ - Therefore balance has returned Some; qed") - } /// Get value of the storage at given position at the given block's state. /// /// May not return None if given BlockId::Latest. /// Returns None if and only if the block's root hash has been pruned from the DB. - fn storage_at(&self, address: &Address, position: &H256, id: BlockId) -> Option; + fn storage_at(&self, address: &Address, position: &H256, state: StateOrBlock) -> Option; /// Get value of the storage at given position at the latest block's state. fn latest_storage_at(&self, address: &Address, position: &H256) -> H256 { - self.storage_at(address, position, BlockId::Latest) + self.storage_at(address, position, BlockId::Latest.into()) .expect("storage_at will return Some if given BlockId::Latest. storage_at was given BlockId::Latest. \ Therefore storage_at has returned Some; qed") } @@ -135,9 +256,6 @@ pub trait BlockChainClient : Sync + Send { /// Get transaction with given hash. fn transaction(&self, id: TransactionId) -> Option; - /// Get the hash of block that contains the transaction, if any. - fn transaction_block(&self, id: TransactionId) -> Option; - /// Get uncle with given id. fn uncle(&self, id: UncleId) -> Option; @@ -157,40 +275,18 @@ pub trait BlockChainClient : Sync + Send { /// Get raw block receipts data by block header hash. fn block_receipts(&self, hash: &H256) -> Option; - /// Import a block into the blockchain. - fn import_block(&self, bytes: Bytes) -> Result; - - /// Import a block with transaction receipts. Does no sealing and transaction validation. - fn import_block_with_receipts(&self, block_bytes: Bytes, receipts_bytes: Bytes) -> Result; - /// Get block queue information. fn queue_info(&self) -> BlockQueueInfo; /// Clear block queue and abort all import activity. fn clear_queue(&self); - /// Get blockchain information. - fn chain_info(&self) -> BlockChainInfo; - /// Get the registrar address, if it exists. fn additional_params(&self) -> BTreeMap; - /// Get the best block header. - fn best_block_header(&self) -> encoded::Header; - /// Returns logs matching given filter. fn logs(&self, filter: Filter) -> Vec; - /// Makes a non-persistent transaction call. - 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; - /// Replays a given transaction for inspection. fn replay(&self, t: TransactionId, analytics: CallAnalytics) -> Result; @@ -270,52 +366,64 @@ pub trait BlockChainClient : Sync + Send { /// Returns information about pruning/data availability. fn pruning_info(&self) -> PruningInfo; - /// Like `call`, but with various defaults. Designed to be used for calling contracts. - fn call_contract(&self, id: BlockId, address: Address, data: Bytes) -> Result; - /// Import a transaction: used for misbehaviour reporting. fn transact_contract(&self, address: Address, data: Bytes) -> Result; /// Get the address of the registry itself. fn registrar_address(&self) -> Option
; - /// Get the address of a particular blockchain service, if available. - fn registry_address(&self, name: String, block: BlockId) -> Option
; - /// Get the EIP-86 transition block number. fn eip86_transition(&self) -> u64; } -/// Extended client interface used for mining -pub trait MiningBlockChainClient: BlockChainClient { +/// Provides `reopen_block` method +pub trait ReopenBlock { + /// Reopens an OpenBlock and updates uncles. + fn reopen_block(&self, block: ClosedBlock) -> OpenBlock; +} + +/// Provides `prepare_open_block` method +pub trait PrepareOpenBlock { /// Returns OpenBlock prepared for closing. fn prepare_open_block(&self, author: Address, gas_range_target: (U256, U256), extra_data: Bytes ) -> OpenBlock; +} - /// Reopens an OpenBlock and updates uncles. - fn reopen_block(&self, block: ClosedBlock) -> OpenBlock; - - /// Returns EvmFactory. - fn vm_factory(&self) -> &VmFactory; - - /// Broadcast a block proposal. - fn broadcast_proposal_block(&self, block: SealedBlock); - - /// Import sealed block. Skips all verifications. - fn import_sealed_block(&self, block: SealedBlock) -> ImportResult; +/// Provides methods used for sealing new state +pub trait BlockProducer: PrepareOpenBlock + ReopenBlock {} +/// Provides `latest_schedule` method +pub trait ScheduleInfo { /// Returns latest schedule. fn latest_schedule(&self) -> Schedule; +} - /// Returns base of this trait - fn as_block_chain_client(&self) -> &BlockChainClient; +///Provides `import_sealed_block` method +pub trait ImportSealedBlock { + /// Import sealed block. Skips all verifications. + fn import_sealed_block(&self, block: SealedBlock) -> ImportResult; +} + +/// Provides `broadcast_proposal_block` method +pub trait BroadcastProposalBlock { + /// Broadcast a block proposal. + fn broadcast_proposal_block(&self, block: SealedBlock); +} + +/// Provides methods to import sealed block and broadcast a block proposal +pub trait SealedBlockImporter: ImportSealedBlock + BroadcastProposalBlock {} + +/// Extended client interface used for mining +pub trait MiningBlockChainClient: BlockChainClient + BlockProducer + ScheduleInfo + SealedBlockImporter { + /// Returns EvmFactory. + fn vm_factory(&self) -> &VmFactory; } /// Client facilities used by internally sealing Engines. -pub trait EngineClient: Sync + Send { +pub trait EngineClient: Sync + Send + ChainInfo { /// Make a new block and seal it. fn update_sealing(&self); @@ -332,9 +440,6 @@ pub trait EngineClient: Sync + Send { /// The block corresponding the the parent hash must be stored already. fn epoch_transition_for(&self, parent_hash: H256) -> Option<::engines::EpochTransition>; - /// Get block chain info. - fn chain_info(&self) -> BlockChainInfo; - /// Attempt to cast the engine client to a full client. fn as_full_client(&self) -> Option<&BlockChainClient>; diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index dfac00bea..0d8081c44 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -781,6 +781,7 @@ mod tests { use block::*; use error::{Error, BlockError}; use header::Header; + use client::ChainInfo; use miner::MinerService; use tests::helpers::*; use account_provider::AccountProvider; diff --git a/ethcore/src/engines/validator_set/contract.rs b/ethcore/src/engines/validator_set/contract.rs index 20c43345c..0e27d594d 100644 --- a/ethcore/src/engines/validator_set/contract.rs +++ b/ethcore/src/engines/validator_set/contract.rs @@ -145,7 +145,7 @@ mod tests { use account_provider::AccountProvider; use miner::MinerService; use types::ids::BlockId; - use client::BlockChainClient; + use client::{BlockChainClient, ChainInfo, BlockInfo, CallContract}; use tests::helpers::generate_dummy_client_with_spec_and_accounts; use super::super::ValidatorSet; use super::ValidatorContract; diff --git a/ethcore/src/engines/validator_set/multi.rs b/ethcore/src/engines/validator_set/multi.rs index b607544cb..2794b57a2 100644 --- a/ethcore/src/engines/validator_set/multi.rs +++ b/ethcore/src/engines/validator_set/multi.rs @@ -148,7 +148,7 @@ mod tests { use std::collections::BTreeMap; use hash::keccak; use account_provider::AccountProvider; - use client::BlockChainClient; + use client::{BlockChainClient, ChainInfo, BlockInfo, ImportBlock}; use engines::EpochChange; use engines::validator_set::ValidatorSet; use ethkey::Secret; diff --git a/ethcore/src/engines/validator_set/safe_contract.rs b/ethcore/src/engines/validator_set/safe_contract.rs index 7093ab896..490a762a6 100644 --- a/ethcore/src/engines/validator_set/safe_contract.rs +++ b/ethcore/src/engines/validator_set/safe_contract.rs @@ -456,7 +456,7 @@ mod tests { use spec::Spec; use account_provider::AccountProvider; use transaction::{Transaction, Action}; - use client::BlockChainClient; + use client::{ChainInfo, BlockInfo, ImportBlock}; use ethkey::Secret; use miner::MinerService; use tests::helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data}; diff --git a/ethcore/src/json_tests/chain.rs b/ethcore/src/json_tests/chain.rs index e82bc7740..64414450b 100644 --- a/ethcore/src/json_tests/chain.rs +++ b/ethcore/src/json_tests/chain.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . use std::sync::Arc; -use client::{EvmTestClient, BlockChainClient, Client, ClientConfig}; +use client::{EvmTestClient, Client, ClientConfig, ChainInfo, ImportBlock}; use block::Block; use spec::Genesis; use ethjson; diff --git a/ethcore/src/machine.rs b/ethcore/src/machine.rs index bd38130dc..b4f7651f8 100644 --- a/ethcore/src/machine.rs +++ b/ethcore/src/machine.rs @@ -22,7 +22,7 @@ use std::sync::Arc; use block::{ExecutedBlock, IsBlock}; use builtin::Builtin; -use client::BlockChainClient; +use client::{BlockInfo, CallContract}; use error::Error; use executive::Executive; use header::{BlockNumber, Header}; @@ -366,7 +366,7 @@ impl EthereumMachine { /// Does verification of the transaction against the parent state. // TODO: refine the bound here to be a "state provider" or similar as opposed // to full client functionality. - pub fn verify_transaction(&self, t: &SignedTransaction, header: &Header, client: &BlockChainClient) -> Result<(), Error> { + pub fn verify_transaction(&self, t: &SignedTransaction, header: &Header, client: &C) -> Result<(), Error> { if let Some(ref filter) = self.tx_filter.as_ref() { if !filter.transaction_allowed(header.parent_hash(), t, client) { return Err(transaction::Error::NotAllowed.into()) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 56a8104c4..f0152686a 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -36,7 +36,7 @@ use ethcore_miner::transaction_queue::{ TransactionOrigin, }; use ethcore_miner::work_notify::{WorkPoster, NotifyWork}; -use ethcore_miner::service_transaction_checker::ServiceTransactionChecker; +use miner::service_transaction_checker::ServiceTransactionChecker; use miner::{MinerService, MinerStatus}; use price_info::fetch::Client as FetchClient; use price_info::{Client as PriceInfoClient, PriceInfo}; @@ -50,9 +50,11 @@ use transaction::{ Error as TransactionError, }; use using_queue::{UsingQueue, GetAction}; - use block::{ClosedBlock, IsBlock, Block}; -use client::{MiningBlockChainClient, BlockId, TransactionId}; +use client::{ + AccountData, BlockChain, RegistryInfo, ScheduleInfo, CallContract, BlockProducer, SealedBlockImporter +}; +use client::{BlockId, TransactionId, MiningBlockChainClient}; use executive::contract_address; use header::{Header, BlockNumber}; use receipt::{Receipt, RichReceipt}; @@ -386,7 +388,7 @@ impl Miner { } /// Prepares new block for sealing including top transactions from queue. - fn prepare_block(&self, chain: &MiningBlockChainClient) -> (ClosedBlock, Option) { + fn prepare_block(&self, chain: &C) -> (ClosedBlock, Option) { trace_time!("prepare_block"); let chain_info = chain.chain_info(); let (transactions, mut open_block, original_work_hash) = { @@ -439,7 +441,7 @@ impl Miner { let hash = tx.hash(); let start = Instant::now(); // Check whether transaction type is allowed for sender - let result = match self.engine.machine().verify_transaction(&tx, open_block.header(), chain.as_block_chain_client()) { + let result = match self.engine.machine().verify_transaction(&tx, open_block.header(), chain) { Err(Error::Transaction(TransactionError::NotAllowed)) => { Err(TransactionError::NotAllowed.into()) } @@ -567,7 +569,9 @@ impl Miner { } /// Attempts to perform internal sealing (one that does not require work) and handles the result depending on the type of Seal. - fn seal_and_import_block_internally(&self, chain: &MiningBlockChainClient, block: ClosedBlock) -> bool { + fn seal_and_import_block_internally(&self, chain: &C, block: ClosedBlock) -> bool + where C: BlockChain + SealedBlockImporter + { if !block.transactions().is_empty() || self.forced_sealing() || Instant::now() > *self.next_mandatory_reseal.read() { trace!(target: "miner", "seal_block_internally: attempting internal seal."); @@ -647,7 +651,7 @@ impl Miner { } } - fn update_gas_limit(&self, client: &MiningBlockChainClient) { + fn update_gas_limit(&self, client: &C) { let gas_limit = client.best_block_header().gas_limit(); let mut queue = self.transaction_queue.write(); queue.set_gas_limit(gas_limit); @@ -658,7 +662,7 @@ impl Miner { } /// Returns true if we had to prepare new pending block. - fn prepare_work_sealing(&self, client: &MiningBlockChainClient) -> bool { + fn prepare_work_sealing(&self, client: &C) -> bool { trace!(target: "miner", "prepare_work_sealing: entering"); let prepare_new = { let mut sealing_work = self.sealing_work.lock(); @@ -690,9 +694,9 @@ impl Miner { prepare_new } - fn add_transactions_to_queue( + fn add_transactions_to_queue( &self, - client: &MiningBlockChainClient, + client: &C, transactions: Vec, default_origin: TransactionOrigin, condition: Option, @@ -718,7 +722,7 @@ impl Miner { }, Ok(transaction) => { // This check goes here because verify_transaction takes SignedTransaction parameter - self.engine.machine().verify_transaction(&transaction, &best_block_header, client.as_block_chain_client())?; + self.engine.machine().verify_transaction(&transaction, &best_block_header, client)?; let origin = self.accounts.as_ref().and_then(|accounts| { match accounts.has_account(transaction.sender()).unwrap_or(false) { @@ -774,8 +778,9 @@ impl Miner { const SEALING_TIMEOUT_IN_BLOCKS : u64 = 5; impl MinerService for Miner { + type State = State<::state_db::StateDB>; - fn clear_and_reset(&self, chain: &MiningBlockChainClient) { + fn clear_and_reset(&self, chain: &C) { self.transaction_queue.write().clear(); // -------------------------------------------------------------------------- // | NOTE Code below requires transaction_queue and sealing_work locks. | @@ -891,16 +896,16 @@ impl MinerService for Miner { self.gas_range_target.read().1 } - fn import_external_transactions( + fn import_external_transactions( &self, - chain: &MiningBlockChainClient, + client: &C, transactions: Vec ) -> Vec> { trace!(target: "external_tx", "Importing external transactions"); let results = { let mut transaction_queue = self.transaction_queue.write(); self.add_transactions_to_queue( - chain, transactions, TransactionOrigin::External, None, &mut transaction_queue + client, transactions, TransactionOrigin::External, None, &mut transaction_queue ) }; @@ -909,14 +914,14 @@ impl MinerService for Miner { // | NOTE Code below requires transaction_queue and sealing_work locks. | // | Make sure to release the locks before calling that method. | // -------------------------------------------------------------------------- - self.update_sealing(chain); + self.update_sealing(client); } results } - fn import_own_transaction( + fn import_own_transaction( &self, - chain: &MiningBlockChainClient, + chain: &C, pending: PendingTransaction, ) -> Result { @@ -1040,7 +1045,7 @@ impl MinerService for Miner { } } - fn remove_pending_transaction(&self, chain: &MiningBlockChainClient, hash: &H256) -> Option { + fn remove_pending_transaction(&self, chain: &C, hash: &H256) -> Option { let mut queue = self.transaction_queue.write(); let tx = queue.find(hash); if tx.is_some() { @@ -1110,7 +1115,10 @@ impl MinerService for Miner { /// Update sealing if required. /// Prepare the block and work if the Engine does not seal internally. - fn update_sealing(&self, chain: &MiningBlockChainClient) { + fn update_sealing(&self, chain: &C) + where C: AccountData + BlockChain + RegistryInfo + + CallContract + BlockProducer + SealedBlockImporter + { trace!(target: "miner", "update_sealing"); const NO_NEW_CHAIN_WITH_FORKS: &str = "Your chain specification contains one or more hard forks which are required to be \ on by default. Please remove these forks and start your chain again."; @@ -1150,9 +1158,12 @@ impl MinerService for Miner { self.sealing_work.lock().queue.is_in_use() } - fn map_sealing_work(&self, chain: &MiningBlockChainClient, f: F) -> Option where F: FnOnce(&ClosedBlock) -> T { + fn map_sealing_work(&self, client: &C, f: F) -> Option + where C: AccountData + BlockChain + BlockProducer + CallContract, + F: FnOnce(&ClosedBlock) -> T + { trace!(target: "miner", "map_sealing_work: entering"); - self.prepare_work_sealing(chain); + self.prepare_work_sealing(client); trace!(target: "miner", "map_sealing_work: sealing prepared"); let mut sealing_work = self.sealing_work.lock(); let ret = sealing_work.queue.use_last_ref(); @@ -1160,7 +1171,7 @@ impl MinerService for Miner { ret.map(f) } - fn submit_seal(&self, chain: &MiningBlockChainClient, block_hash: H256, seal: Vec) -> Result<(), Error> { + fn submit_seal(&self, chain: &C, block_hash: H256, seal: Vec) -> Result<(), Error> { let result = if let Some(b) = self.sealing_work.lock().queue.get_used_if( if self.options.enable_resubmission { @@ -1188,7 +1199,10 @@ impl MinerService for Miner { }) } - fn chain_new_blocks(&self, chain: &MiningBlockChainClient, imported: &[H256], _invalid: &[H256], enacted: &[H256], retracted: &[H256]) { + fn chain_new_blocks(&self, chain: &C, imported: &[H256], _invalid: &[H256], enacted: &[H256], retracted: &[H256]) + where C: AccountData + BlockChain + CallContract + RegistryInfo + + BlockProducer + ScheduleInfo + SealedBlockImporter + { trace!(target: "miner", "chain_new_blocks"); // 1. We ignore blocks that were `imported` unless resealing on new uncles is enabled. @@ -1234,6 +1248,18 @@ impl MinerService for Miner { self.update_sealing(chain); } } + + fn pending_state(&self, latest_block_number: BlockNumber) -> Option { + Miner::pending_state(self, latest_block_number) + } + + fn pending_block_header(&self, latest_block_number: BlockNumber) -> Option
{ + Miner::pending_block_header(self, latest_block_number) + } + + fn pending_block(&self, latest_block_number: BlockNumber) -> Option { + Miner::pending_block(self, latest_block_number) + } } /// Action when service transaction is received @@ -1245,31 +1271,22 @@ enum ServiceTransactionAction { } impl ServiceTransactionAction { - pub fn check(&self, client: &MiningBlockChainClient, tx: &SignedTransaction) -> Result { + pub fn check(&self, client: &C, tx: &SignedTransaction) -> Result + { match *self { ServiceTransactionAction::Refuse => Err("configured to refuse service transactions".to_owned()), - ServiceTransactionAction::Check(ref checker) => checker.check(&client, tx), + ServiceTransactionAction::Check(ref checker) => checker.check(client, tx), } } } -impl<'a> ::ethcore_miner::service_transaction_checker::ContractCaller for &'a MiningBlockChainClient { - fn registry_address(&self, name: &str) -> Option
{ - MiningBlockChainClient::registry_address(*self, name.into(), BlockId::Latest) - } - - fn call_contract(&self, block: BlockId, address: Address, data: Vec) -> Result, String> { - MiningBlockChainClient::call_contract(*self, block, address, data) - } -} - -struct TransactionDetailsProvider<'a> { - client: &'a MiningBlockChainClient, +struct TransactionDetailsProvider<'a, C: 'a> { + client: &'a C, service_transaction_action: &'a ServiceTransactionAction, } -impl<'a> TransactionDetailsProvider<'a> { - pub fn new(client: &'a MiningBlockChainClient, service_transaction_action: &'a ServiceTransactionAction) -> Self { +impl<'a, C> TransactionDetailsProvider<'a, C> { + pub fn new(client: &'a C, service_transaction_action: &'a ServiceTransactionAction) -> Self { TransactionDetailsProvider { client: client, service_transaction_action: service_transaction_action, @@ -1277,7 +1294,9 @@ impl<'a> TransactionDetailsProvider<'a> { } } -impl<'a> TransactionQueueDetailsProvider for TransactionDetailsProvider<'a> { +impl<'a, C> TransactionQueueDetailsProvider for TransactionDetailsProvider<'a, C> + where C: AccountData + CallContract + RegistryInfo + ScheduleInfo +{ fn fetch_account(&self, address: &Address) -> AccountDetails { AccountDetails { nonce: self.client.latest_nonce(address), @@ -1300,12 +1319,14 @@ mod tests { use ethcore_miner::transaction_queue::PrioritizationStrategy; use ethereum_types::U256; use ethkey::{Generator, Random}; + use client::{TestBlockChainClient, EachBlockWith, ChainInfo}; use hash::keccak; + use header::BlockNumber; use rustc_hex::FromHex; - use transaction::Transaction; - - use client::{BlockChainClient, TestBlockChainClient, EachBlockWith}; + use spec::Spec; + use transaction::{SignedTransaction, Transaction, PendingTransaction, Action}; use miner::MinerService; + use tests::helpers::{generate_dummy_client, generate_dummy_client_with_spec_and_accounts}; #[test] diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 34c416cc1..5f451fdc2 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -40,6 +40,7 @@ mod miner; mod stratum; +mod service_transaction_checker; pub use self::miner::{Miner, MinerOptions, Banning, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit}; pub use self::stratum::{Stratum, Error as StratumError, Options as StratumOptions}; @@ -47,18 +48,24 @@ pub use self::stratum::{Stratum, Error as StratumError, Options as StratumOption pub use ethcore_miner::local_transactions::Status as LocalTransactionStatus; use std::collections::BTreeMap; -use ethereum_types::{H256, U256, Address}; -use bytes::Bytes; -use block::ClosedBlock; -use client::{MiningBlockChainClient}; +use block::{ClosedBlock, Block}; +use bytes::Bytes; +use client::{ + MiningBlockChainClient, CallContract, RegistryInfo, ScheduleInfo, + BlockChain, AccountData, BlockProducer, SealedBlockImporter +}; use error::{Error}; -use header::BlockNumber; +use ethereum_types::{H256, U256, Address}; +use header::{BlockNumber, Header}; use receipt::{RichReceipt, Receipt}; use transaction::{UnverifiedTransaction, PendingTransaction, ImportResult as TransactionImportResult}; +use state::StateInfo; /// Miner client API pub trait MinerService : Send + Sync { + /// Type representing chain state + type State: StateInfo + 'static; /// Returns miner's status. fn status(&self) -> MinerStatus; @@ -107,42 +114,46 @@ pub trait MinerService : Send + Sync { fn set_tx_gas_limit(&self, limit: U256); /// Imports transactions to transaction queue. - fn import_external_transactions(&self, chain: &MiningBlockChainClient, transactions: Vec) -> + fn import_external_transactions(&self, client: &C, transactions: Vec) -> Vec>; /// Imports own (node owner) transaction to queue. - fn import_own_transaction(&self, chain: &MiningBlockChainClient, transaction: PendingTransaction) -> + fn import_own_transaction(&self, chain: &C, transaction: PendingTransaction) -> Result; /// Returns hashes of transactions currently in pending fn pending_transactions_hashes(&self, best_block: BlockNumber) -> Vec; /// Removes all transactions from the queue and restart mining operation. - fn clear_and_reset(&self, chain: &MiningBlockChainClient); + fn clear_and_reset(&self, chain: &C); /// Called when blocks are imported to chain, updates transactions queue. - fn chain_new_blocks(&self, chain: &MiningBlockChainClient, imported: &[H256], invalid: &[H256], enacted: &[H256], retracted: &[H256]); + fn chain_new_blocks(&self, chain: &C, imported: &[H256], invalid: &[H256], enacted: &[H256], retracted: &[H256]) + where C: AccountData + BlockChain + CallContract + RegistryInfo + BlockProducer + ScheduleInfo + SealedBlockImporter; /// PoW chain - can produce work package fn can_produce_work_package(&self) -> bool; /// New chain head event. Restart mining operation. - fn update_sealing(&self, chain: &MiningBlockChainClient); + fn update_sealing(&self, chain: &C) + where C: AccountData + BlockChain + RegistryInfo + CallContract + BlockProducer + SealedBlockImporter; /// Submit `seal` as a valid solution for the header of `pow_hash`. /// Will check the seal, but not actually insert the block into the chain. - fn submit_seal(&self, chain: &MiningBlockChainClient, pow_hash: H256, seal: Vec) -> Result<(), Error>; + fn submit_seal(&self, chain: &C, pow_hash: H256, seal: Vec) -> Result<(), Error>; /// Get the sealing work package and if `Some`, apply some transform. - fn map_sealing_work(&self, chain: &MiningBlockChainClient, f: F) -> Option - where F: FnOnce(&ClosedBlock) -> T, Self: Sized; + fn map_sealing_work(&self, client: &C, f: F) -> Option + where C: AccountData + BlockChain + BlockProducer + CallContract, + F: FnOnce(&ClosedBlock) -> T, + Self: Sized; /// Query pending transactions for hash. fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option; /// Removes transaction from the queue. /// NOTE: The transaction is not removed from pending block if mining. - fn remove_pending_transaction(&self, chain: &MiningBlockChainClient, hash: &H256) -> Option; + fn remove_pending_transaction(&self, chain: &C, hash: &H256) -> Option; /// Get a list of all pending transactions in the queue. fn pending_transactions(&self) -> Vec; @@ -173,6 +184,15 @@ pub trait MinerService : Send + Sync { /// Suggested gas limit. fn sensible_gas_limit(&self) -> U256 { 21000.into() } + + /// Get `Some` `clone()` of the current pending block's state or `None` if we're not sealing. + fn pending_state(&self, latest_block_number: BlockNumber) -> Option; + + /// Get `Some` `clone()` of the current pending block header or `None` if we're not sealing. + fn pending_block_header(&self, latest_block_number: BlockNumber) -> Option
; + + /// Get `Some` `clone()` of the current pending block or `None` if we're not sealing. + fn pending_block(&self, latest_block_number: BlockNumber) -> Option; } /// Mining status diff --git a/miner/src/service_transaction_checker.rs b/ethcore/src/miner/service_transaction_checker.rs similarity index 74% rename from miner/src/service_transaction_checker.rs rename to ethcore/src/miner/service_transaction_checker.rs index 806acf29b..a555829c5 100644 --- a/miner/src/service_transaction_checker.rs +++ b/ethcore/src/miner/service_transaction_checker.rs @@ -16,23 +16,14 @@ //! A service transactions contract checker. -use ethereum_types::Address; +use client::{RegistryInfo, CallContract}; use transaction::SignedTransaction; use types::ids::BlockId; -use_contract!(service_transaction, "ServiceTransaction", "res/service_transaction.json"); +use_contract!(service_transaction, "ServiceTransaction", "res/contracts/service_transaction.json"); const SERVICE_TRANSACTION_CONTRACT_REGISTRY_NAME: &'static str = "service_transaction_checker"; -/// A contract calling interface. -pub trait ContractCaller { - /// Returns address of contract from the registry, given it's name - fn registry_address(&self, name: &str) -> Option
; - - /// Executes a contract call at given block. - fn call_contract(&self, BlockId, Address, Vec) -> Result, String>; -} - /// Service transactions checker. #[derive(Default)] pub struct ServiceTransactionChecker { @@ -41,10 +32,10 @@ pub struct ServiceTransactionChecker { impl ServiceTransactionChecker { /// Checks if service transaction can be appended to the transaction queue. - pub fn check(&self, client: &ContractCaller, tx: &SignedTransaction) -> Result { + pub fn check(&self, client: &C, tx: &SignedTransaction) -> Result { assert!(tx.gas_price.is_zero()); - let address = client.registry_address(SERVICE_TRANSACTION_CONTRACT_REGISTRY_NAME) + let address = client.registry_address(SERVICE_TRANSACTION_CONTRACT_REGISTRY_NAME.to_owned(), BlockId::Latest) .ok_or_else(|| "contract is not configured")?; trace!(target: "txqueue", "Checking service transaction checker contract from {}", address); diff --git a/ethcore/src/snapshot/service.rs b/ethcore/src/snapshot/service.rs index 14cf65b9a..ae1f6feca 100644 --- a/ethcore/src/snapshot/service.rs +++ b/ethcore/src/snapshot/service.rs @@ -27,7 +27,7 @@ use super::{ManifestData, StateRebuilder, Rebuilder, RestorationStatus, Snapshot use super::io::{SnapshotReader, LooseReader, SnapshotWriter, LooseWriter}; use blockchain::BlockChain; -use client::{BlockChainClient, Client}; +use client::{Client, ChainInfo}; use engines::EthEngine; use error::Error; use ids::BlockId; diff --git a/ethcore/src/snapshot/tests/helpers.rs b/ethcore/src/snapshot/tests/helpers.rs index 8499b9904..51f417149 100644 --- a/ethcore/src/snapshot/tests/helpers.rs +++ b/ethcore/src/snapshot/tests/helpers.rs @@ -25,7 +25,7 @@ use hash::{KECCAK_NULL_RLP}; use account_db::AccountDBMut; use basic_account::BasicAccount; use blockchain::BlockChain; -use client::{BlockChainClient, Client}; +use client::{Client, ChainInfo}; use engines::EthEngine; use snapshot::{StateRebuilder}; use snapshot::io::{SnapshotReader, PackedWriter, PackedReader}; diff --git a/ethcore/src/snapshot/tests/proof_of_authority.rs b/ethcore/src/snapshot/tests/proof_of_authority.rs index c237acf78..6cc2aaffa 100644 --- a/ethcore/src/snapshot/tests/proof_of_authority.rs +++ b/ethcore/src/snapshot/tests/proof_of_authority.rs @@ -21,7 +21,7 @@ use std::sync::Arc; use std::str::FromStr; use account_provider::AccountProvider; -use client::{Client, BlockChainClient}; +use client::{Client, BlockChainClient, ChainInfo}; use ethkey::Secret; use snapshot::tests::helpers as snapshot_helpers; use spec::Spec; diff --git a/ethcore/src/snapshot/tests/service.rs b/ethcore/src/snapshot/tests/service.rs index 35e98f805..52b4b3cc9 100644 --- a/ethcore/src/snapshot/tests/service.rs +++ b/ethcore/src/snapshot/tests/service.rs @@ -19,7 +19,7 @@ use std::sync::Arc; use tempdir::TempDir; -use client::{BlockChainClient, Client}; +use client::{Client, BlockInfo}; use ids::BlockId; use snapshot::service::{Service, ServiceParams}; use snapshot::{self, ManifestData, SnapshotService}; diff --git a/ethcore/src/snapshot/watcher.rs b/ethcore/src/snapshot/watcher.rs index b3291af7e..44783551f 100644 --- a/ethcore/src/snapshot/watcher.rs +++ b/ethcore/src/snapshot/watcher.rs @@ -17,7 +17,7 @@ //! Watcher for snapshot-related chain events. use parking_lot::Mutex; -use client::{BlockChainClient, Client, ChainNotify}; +use client::{BlockInfo, Client, ChainNotify}; use ids::BlockId; use service::ClientIoMessage; diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index 1b868c97b..09f6953f0 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -334,6 +334,28 @@ pub enum CleanupMode<'a> { TrackTouched(&'a mut HashSet
), } +/// Provides subset of `State` methods to query state information +pub trait StateInfo { + /// Get the nonce of account `a`. + fn nonce(&self, a: &Address) -> trie::Result; + + /// Get the balance of account `a`. + fn balance(&self, a: &Address) -> trie::Result; + + /// Mutate storage of account `address` so that it is `value` for `key`. + fn storage_at(&self, address: &Address, key: &H256) -> trie::Result; + + /// Get accounts' code. + fn code(&self, a: &Address) -> trie::Result>>; +} + +impl StateInfo for State { + fn nonce(&self, a: &Address) -> trie::Result { State::nonce(self, a) } + fn balance(&self, a: &Address) -> trie::Result { State::balance(self, a) } + fn storage_at(&self, address: &Address, key: &H256) -> trie::Result { State::storage_at(self, address, key) } + fn code(&self, address: &Address) -> trie::Result>> { State::code(self, address) } +} + const SEC_TRIE_DB_UNWRAP_STR: &'static str = "A state can only be created with valid root. Creating a SecTrieDB with a valid root will not fail. \ Therefore creating a SecTrieDB with this state's root will not fail."; diff --git a/ethcore/src/state_db.rs b/ethcore/src/state_db.rs index c315875fd..fc4012335 100644 --- a/ethcore/src/state_db.rs +++ b/ethcore/src/state_db.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//! State database abstraction. +//! State database abstraction. For more info, see the doc for `StateDB` use std::collections::{VecDeque, HashSet}; use std::sync::Arc; @@ -33,13 +33,17 @@ use bloom_journal::{Bloom, BloomJournal}; use db::COL_ACCOUNT_BLOOM; use byteorder::{LittleEndian, ByteOrder}; -/// Number of bytes allocated in the memory for accounts bloom. +/// Value used to initialize bloom bitmap size. +/// +/// Bitmap size is the size in bytes (not bits) that will be allocated in memory. pub const ACCOUNT_BLOOM_SPACE: usize = 1048576; -/// Estimated maximum number of accounts in memory bloom. +/// Value used to initialize bloom items count. +/// +/// Items count is an estimation of the maximum number of items to store. pub const DEFAULT_ACCOUNT_PRESET: usize = 1000000; -/// Database key represening number of account hashes. +/// Key for a value storing amount of hashes pub const ACCOUNT_BLOOM_HASHCOUNT_KEY: &'static [u8] = b"account_hash_count"; const STATE_CACHE_BLOCKS: usize = 12; @@ -175,7 +179,7 @@ impl StateDB { bloom } - /// Commit bloom to a database transaction + /// Commit blooms journal to the database transaction pub fn commit_bloom(batch: &mut DBTransaction, journal: BloomJournal) -> Result<(), UtilError> { assert!(journal.hash_functions <= 255); batch.put(COL_ACCOUNT_BLOOM, ACCOUNT_BLOOM_HASHCOUNT_KEY, &[journal.hash_functions as u8]); @@ -305,12 +309,12 @@ impl StateDB { } } - /// Returns immutable reference to underlying hashdb. + /// Conversion method to interpret self as `HashDB` reference pub fn as_hashdb(&self) -> &HashDB { self.db.as_hashdb() } - /// Returns mutable reference to underlying hashdb. + /// Conversion method to interpret self as mutable `HashDB` reference pub fn as_hashdb_mut(&mut self) -> &mut HashDB { self.db.as_hashdb_mut() } diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index a7967dbd3..30685425b 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -18,7 +18,7 @@ use std::str::FromStr; use std::sync::Arc; use hash::keccak; use io::IoChannel; -use client::{BlockChainClient, MiningBlockChainClient, Client, ClientConfig, BlockId}; +use client::{BlockChainClient, Client, ClientConfig, BlockId, ChainInfo, BlockInfo, PrepareOpenBlock, ImportSealedBlock, ImportBlock}; use state::{self, State, CleanupMode}; use executive::{Executive, TransactOptions}; use ethereum; diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index b23245b61..e6d23ec0f 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -19,7 +19,7 @@ use ethereum_types::{H256, U256}; use block::{OpenBlock, Drain}; use blockchain::{BlockChain, Config as BlockChainConfig}; use bytes::Bytes; -use client::{BlockChainClient, ChainNotify, Client, ClientConfig}; +use client::{Client, ClientConfig, ChainInfo, ImportBlock, ChainNotify}; use ethereum::ethash::EthashParams; use ethkey::KeyPair; use evm::Factory as EvmFactory; diff --git a/ethcore/src/trace/db.rs b/ethcore/src/trace/db.rs index cbdb81a9c..b9c2f49ef 100644 --- a/ethcore/src/trace/db.rs +++ b/ethcore/src/trace/db.rs @@ -65,7 +65,12 @@ enum CacheId { Bloom(H256), } -/// Trace database. +/// Database to store transaction execution trace. +/// +/// Whenever a transaction is executed by EVM it's execution trace is stored +/// in trace database. Each trace has information, which contracts have been +/// touched, which have been created during the execution of transaction, and +/// which calls failed. pub struct TraceDB where T: DatabaseExtras { // cache traces: RwLock>, diff --git a/ethcore/src/tx_filter.rs b/ethcore/src/tx_filter.rs index 75bd16842..569e836d5 100644 --- a/ethcore/src/tx_filter.rs +++ b/ethcore/src/tx_filter.rs @@ -19,7 +19,7 @@ use std::collections::HashMap; use std::collections::hash_map::Entry; use ethereum_types::{H256, Address}; -use client::{BlockChainClient, BlockId, ChainNotify}; +use client::{BlockInfo, CallContract, BlockId, ChainNotify}; use bytes::Bytes; use parking_lot::Mutex; use spec::CommonParams; @@ -64,7 +64,7 @@ impl TransactionFilter { } /// Check if transaction is allowed at given block. - pub fn transaction_allowed(&self, parent_hash: &H256, transaction: &SignedTransaction, client: &BlockChainClient) -> bool { + pub fn transaction_allowed(&self, parent_hash: &H256, transaction: &SignedTransaction, client: &C) -> bool { let mut cache = self.permission_cache.lock(); let len = cache.len(); let tx_type = match transaction.action { diff --git a/ethcore/src/verification/canon_verifier.rs b/ethcore/src/verification/canon_verifier.rs index 9c942b9b6..3d0fd77c6 100644 --- a/ethcore/src/verification/canon_verifier.rs +++ b/ethcore/src/verification/canon_verifier.rs @@ -16,6 +16,7 @@ //! Canonical verifier. +use client::{BlockInfo, CallContract}; use engines::EthEngine; use error::Error; use header::Header; @@ -25,13 +26,13 @@ use super::verification; /// A canonial verifier -- this does full verification. pub struct CanonVerifier; -impl Verifier for CanonVerifier { +impl Verifier for CanonVerifier { fn verify_block_family( &self, header: &Header, parent: &Header, engine: &EthEngine, - do_full: Option, + do_full: Option>, ) -> Result<(), Error> { verification::verify_block_family(header, parent, engine, do_full) } diff --git a/ethcore/src/verification/mod.rs b/ethcore/src/verification/mod.rs index c83d9e20f..d5fd4e847 100644 --- a/ethcore/src/verification/mod.rs +++ b/ethcore/src/verification/mod.rs @@ -28,6 +28,8 @@ pub use self::canon_verifier::CanonVerifier; pub use self::noop_verifier::NoopVerifier; pub use self::queue::{BlockQueue, Config as QueueConfig, VerificationQueue, QueueInfo}; +use client::{BlockInfo, CallContract}; + /// Verifier type. #[derive(Debug, PartialEq, Clone)] pub enum VerifierType { @@ -47,7 +49,7 @@ impl Default for VerifierType { } /// Create a new verifier based on type. -pub fn new(v: VerifierType) -> Box { +pub fn new(v: VerifierType) -> Box> { match v { VerifierType::Canon | VerifierType::CanonNoSeal => Box::new(CanonVerifier), VerifierType::Noop => Box::new(NoopVerifier), diff --git a/ethcore/src/verification/noop_verifier.rs b/ethcore/src/verification/noop_verifier.rs index b1c2ec1bc..24b117bbc 100644 --- a/ethcore/src/verification/noop_verifier.rs +++ b/ethcore/src/verification/noop_verifier.rs @@ -16,6 +16,7 @@ //! No-op verifier. +use client::{BlockInfo, CallContract}; use engines::EthEngine; use error::Error; use header::Header; @@ -25,13 +26,13 @@ use super::{verification, Verifier}; #[allow(dead_code)] pub struct NoopVerifier; -impl Verifier for NoopVerifier { +impl Verifier for NoopVerifier { fn verify_block_family( &self, _: &Header, _t: &Header, _: &EthEngine, - _: Option + _: Option> ) -> Result<(), Error> { Ok(()) } diff --git a/ethcore/src/verification/verification.rs b/ethcore/src/verification/verification.rs index ab1d11fd1..87aedd88f 100644 --- a/ethcore/src/verification/verification.rs +++ b/ethcore/src/verification/verification.rs @@ -33,7 +33,7 @@ use triehash::ordered_trie_root; use unexpected::{Mismatch, OutOfBounds}; use blockchain::*; -use client::BlockChainClient; +use client::{BlockInfo, CallContract}; use engines::EthEngine; use error::{BlockError, Error}; use header::{BlockNumber, Header}; @@ -109,24 +109,36 @@ pub fn verify_block_unordered(header: Header, bytes: Bytes, engine: &EthEngine, }) } -/// Parameters for full verification of block family: block bytes, transactions, blockchain, and state access. -pub type FullFamilyParams<'a> = (&'a [u8], &'a [SignedTransaction], &'a BlockProvider, &'a BlockChainClient); +/// Parameters for full verification of block family +pub struct FullFamilyParams<'a, C: BlockInfo + CallContract + 'a> { + /// Serialized block bytes + pub block_bytes: &'a [u8], + + /// Signed transactions + pub transactions: &'a [SignedTransaction], + + /// Block provider to use during verification + pub block_provider: &'a BlockProvider, + + /// Engine client to use during verification + pub client: &'a C, +} /// Phase 3 verification. Check block information against parent and uncles. -pub fn verify_block_family(header: &Header, parent: &Header, engine: &EthEngine, do_full: Option) -> Result<(), Error> { +pub fn verify_block_family(header: &Header, parent: &Header, engine: &EthEngine, do_full: Option>) -> Result<(), Error> { // TODO: verify timestamp verify_parent(&header, &parent, engine.params().gas_limit_bound_divisor)?; engine.verify_block_family(&header, &parent)?; - let (bytes, txs, bc, client) = match do_full { + let params = match do_full { Some(x) => x, None => return Ok(()), }; - verify_uncles(header, bytes, bc, engine)?; + verify_uncles(header, params.block_bytes, params.block_provider, engine)?; - for transaction in txs { - engine.machine().verify_transaction(transaction, header, client)?; + for transaction in params.transactions { + engine.machine().verify_transaction(transaction, header, params.client)?; } Ok(()) @@ -486,12 +498,12 @@ mod tests { let parent = bc.block_header(header.parent_hash()) .ok_or(BlockError::UnknownParent(header.parent_hash().clone()))?; - let full_params: FullFamilyParams = ( - bytes, - &transactions[..], - bc as &BlockProvider, - &client as &::client::BlockChainClient - ); + let full_params = FullFamilyParams { + block_bytes: bytes, + transactions: &transactions[..], + block_provider: bc as &BlockProvider, + client: &client, + }; verify_block_family(&header, &parent, engine, Some(full_params)) } diff --git a/ethcore/src/verification/verifier.rs b/ethcore/src/verification/verifier.rs index 5141cea2f..a9ca22a4c 100644 --- a/ethcore/src/verification/verifier.rs +++ b/ethcore/src/verification/verifier.rs @@ -16,20 +16,23 @@ //! A generic verifier trait. +use client::{BlockInfo, CallContract}; use engines::EthEngine; use error::Error; use header::Header; use super::verification; /// Should be used to verify blocks. -pub trait Verifier: Send + Sync { +pub trait Verifier: Send + Sync + where C: BlockInfo + CallContract +{ /// Verify a block relative to its parent and uncles. - fn verify_block_family( + fn verify_block_family( &self, header: &Header, parent: &Header, engine: &EthEngine, - do_full: Option + do_full: Option> ) -> Result<(), Error>; /// Do a final verification check for an enacted header vs its expected counterpart. diff --git a/ethcore/types/src/ids.rs b/ethcore/types/src/ids.rs index ad60b3b38..e304698a4 100644 --- a/ethcore/types/src/ids.rs +++ b/ethcore/types/src/ids.rs @@ -31,8 +31,6 @@ pub enum BlockId { Earliest, /// Latest mined block. Latest, - /// Pending block. - Pending, } /// Uniquely identifies transaction. diff --git a/miner/src/lib.rs b/miner/src/lib.rs index 9a2ebae13..e8a86bd02 100644 --- a/miner/src/lib.rs +++ b/miner/src/lib.rs @@ -31,10 +31,6 @@ extern crate parking_lot; extern crate table; extern crate transient_hashmap; -#[macro_use] -extern crate ethabi_derive; -#[macro_use] -extern crate ethabi_contract; #[cfg(test)] extern crate ethkey; #[macro_use] @@ -45,6 +41,5 @@ extern crate rustc_hex; pub mod banning_queue; pub mod external; pub mod local_transactions; -pub mod service_transaction_checker; pub mod transaction_queue; pub mod work_notify; diff --git a/parity/blockchain.rs b/parity/blockchain.rs index 602a45730..594bffe2b 100644 --- a/parity/blockchain.rs +++ b/parity/blockchain.rs @@ -26,7 +26,7 @@ use ethereum_types::{U256, H256, Address}; use bytes::ToPretty; use rlp::PayloadInfo; use ethcore::service::ClientService; -use ethcore::client::{Mode, DatabaseCompactionProfile, VMType, BlockImportError, BlockChainClient, BlockId}; +use ethcore::client::{Mode, DatabaseCompactionProfile, VMType, BlockImportError, Nonce, Balance, BlockChainClient, BlockId, BlockInfo, ImportBlock}; use ethcore::db::NUM_COLUMNS; use ethcore::error::ImportError; use ethcore::miner::Miner; @@ -650,7 +650,7 @@ fn execute_export_state(cmd: ExportState) -> Result<(), String> { } for account in accounts.into_iter() { - let balance = client.balance(&account, at).unwrap_or_else(U256::zero); + let balance = client.balance(&account, at.into()).unwrap_or_else(U256::zero); if cmd.min_balance.map_or(false, |m| balance < m) || cmd.max_balance.map_or(false, |m| balance > m) { last = Some(account); continue; //filtered out @@ -660,7 +660,7 @@ fn execute_export_state(cmd: ExportState) -> Result<(), String> { out.write(b",").expect("Write error"); } out.write_fmt(format_args!("\n\"0x{:x}\": {{\"balance\": \"{:x}\", \"nonce\": \"{:x}\"", account, balance, client.nonce(&account, at).unwrap_or_else(U256::zero))).expect("Write error"); - let code = client.code(&account, at).unwrap_or(None).unwrap_or_else(Vec::new); + let code = client.code(&account, at.into()).unwrap_or(None).unwrap_or_else(Vec::new); if !code.is_empty() { out.write_fmt(format_args!(", \"code_hash\": \"0x{:x}\"", keccak(&code))).expect("Write error"); if cmd.code { @@ -683,7 +683,7 @@ fn execute_export_state(cmd: ExportState) -> Result<(), String> { if last_storage.is_some() { out.write(b",").expect("Write error"); } - out.write_fmt(format_args!("\n\t\"0x{:x}\": \"0x{:x}\"", key, client.storage_at(&account, &key, at).unwrap_or_else(Default::default))).expect("Write error"); + out.write_fmt(format_args!("\n\t\"0x{:x}\": \"0x{:x}\"", key, client.storage_at(&account, &key, at.into()).unwrap_or_else(Default::default))).expect("Write error"); last_storage = Some(key); } } diff --git a/parity/dapps.rs b/parity/dapps.rs index 5c52b204c..449fc41bf 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -20,7 +20,7 @@ use std::sync::Arc; use bytes::Bytes; use dir::default_data_path; use dir::helpers::replace_home; -use ethcore::client::{Client, BlockChainClient, BlockId}; +use ethcore::client::{Client, BlockChainClient, BlockId, CallContract}; use ethsync::LightSync; use futures::{Future, future, IntoFuture}; use hash_fetch::fetch::Client as FetchClient; diff --git a/parity/informant.rs b/parity/informant.rs index 018b4b99e..0d2539713 100644 --- a/parity/informant.rs +++ b/parity/informant.rs @@ -22,7 +22,7 @@ use std::sync::{Arc}; use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering as AtomicOrdering}; use std::time::{Instant, Duration}; -use ethcore::client::{BlockId, BlockChainClient, BlockChainInfo, BlockQueueInfo, ChainNotify, ClientReport, Client}; +use ethcore::client::{BlockId, BlockChainClient, ChainInfo, BlockInfo, BlockChainInfo, BlockQueueInfo, ChainNotify, ClientReport, Client}; use ethcore::header::BlockNumber; use ethcore::service::ClientIoMessage; use ethcore::snapshot::{RestorationStatus, SnapshotService as SS}; diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 96a8e836f..58970b1a7 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -60,6 +60,7 @@ node-health = { path = "../dapps/node-health" } parity-reactor = { path = "../util/reactor" } parity-updater = { path = "../updater" } parity-version = { path = "../util/version" } +patricia-trie = { path = "../util/patricia_trie" } rlp = { path = "../util/rlp" } stats = { path = "../util/stats" } vm = { path = "../ethcore/vm" } diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index d8e5495cc..4a2003d65 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -68,6 +68,7 @@ extern crate rlp; extern crate stats; extern crate keccak_hash as hash; extern crate hardware_wallet; +extern crate patricia_trie as trie; #[macro_use] extern crate log; diff --git a/rpc/src/v1/helpers/light_fetch.rs b/rpc/src/v1/helpers/light_fetch.rs index eccf521d9..f0c389d57 100644 --- a/rpc/src/v1/helpers/light_fetch.rs +++ b/rpc/src/v1/helpers/light_fetch.rs @@ -197,7 +197,19 @@ impl LightFetch { let (sync, on_demand, client) = (self.sync.clone(), self.on_demand.clone(), self.client.clone()); let req: CallRequestHelper = req.into(); - let id = num.unwrap_or_default().into(); + + // Note: Here we treat `Pending` as `Latest`. + // Since light clients don't produce pending blocks + // (they don't have state) we can safely fallback to `Latest`. + let id = match num.unwrap_or_default() { + BlockNumber::Num(n) => BlockId::Number(n), + BlockNumber::Earliest => BlockId::Earliest, + BlockNumber::Latest => BlockId::Latest, + BlockNumber::Pending => { + warn!("`Pending` is deprecated and may be removed in future versions. Falling back to `Latest`"); + BlockId::Latest + } + }; let from = req.from.unwrap_or(Address::zero()); let nonce_fut = match req.nonce { @@ -308,7 +320,7 @@ impl LightFetch { let best_number = self.client.chain_info().best_block_number; let block_number = |id| match id { BlockId::Earliest => Some(0), - BlockId::Latest | BlockId::Pending => Some(best_number), + BlockId::Latest => Some(best_number), BlockId::Hash(h) => self.client.block_header(BlockId::Hash(h)).map(|hdr| hdr.number()), BlockId::Number(x) => Some(x), }; diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 08ce7fbbc..4305dc631 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -28,16 +28,17 @@ use parking_lot::Mutex; use ethash::SeedHashCompute; use ethcore::account_provider::{AccountProvider, DappId}; use ethcore::block::IsBlock; -use ethcore::client::{MiningBlockChainClient, BlockId, TransactionId, UncleId}; +use ethcore::client::{MiningBlockChainClient, BlockId, TransactionId, UncleId, StateOrBlock, StateClient, StateInfo, Call, EngineInfo}; use ethcore::ethereum::Ethash; use ethcore::filter::Filter as EthcoreFilter; -use ethcore::header::{Header as BlockHeader, BlockNumber as EthBlockNumber}; +use ethcore::header::{BlockNumber as EthBlockNumber, Seal}; use ethcore::log_entry::LogEntry; use ethcore::miner::MinerService; use ethcore::snapshot::SnapshotService; +use ethcore::encoded; use ethsync::{SyncProvider}; use miner::external::ExternalMinerService; -use transaction::SignedTransaction; +use transaction::{SignedTransaction, LocalizedTransaction}; use jsonrpc_core::{BoxFuture, Result}; use jsonrpc_core::futures::future; @@ -51,11 +52,11 @@ use v1::traits::Eth; use v1::types::{ RichBlock, Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, CallRequest, Index, Filter, Log, Receipt, Work, - H64 as RpcH64, H256 as RpcH256, H160 as RpcH160, U256 as RpcU256, + H64 as RpcH64, H256 as RpcH256, H160 as RpcH160, U256 as RpcU256, block_number_to_id, }; use v1::metadata::Metadata; -const EXTRA_INFO_PROOF: &'static str = "Object exists in in blockchain (fetched earlier), extra_info is always available if object exists; qed"; +const EXTRA_INFO_PROOF: &'static str = "Object exists in blockchain (fetched earlier), extra_info is always available if object exists; qed"; /// Eth RPC options pub struct EthClientOptions { @@ -109,11 +110,43 @@ pub struct EthClient where eip86_transition: u64, } -impl EthClient where - C: MiningBlockChainClient, +enum BlockNumberOrId { + Number(BlockNumber), + Id(BlockId), +} + +impl From for BlockNumberOrId { + fn from(value: BlockId) -> BlockNumberOrId { + BlockNumberOrId::Id(value) + } +} + +impl From for BlockNumberOrId { + fn from(value: BlockNumber) -> BlockNumberOrId { + BlockNumberOrId::Number(value) + } +} + +enum PendingOrBlock { + Block(BlockId), + Pending, +} + +struct PendingUncleId { + id: PendingOrBlock, + position: usize, +} + +enum PendingTransactionId { + Hash(H256), + Location(PendingOrBlock, usize) +} + +impl EthClient where + C: MiningBlockChainClient + StateClient + Call + EngineInfo, SN: SnapshotService, S: SyncProvider, - M: MinerService, + M: MinerService, EM: ExternalMinerService { /// Creates new EthClient. @@ -145,9 +178,46 @@ impl EthClient where unwrap_provider(&self.accounts) } - fn block(&self, id: BlockId, include_txs: bool) -> Result> { + fn rich_block(&self, id: BlockNumberOrId, include_txs: bool) -> Result> { let client = &self.client; - match (client.block(id.clone()), client.block_total_difficulty(id)) { + + let client_query = |id| (client.block(id), client.block_total_difficulty(id), client.block_extra_info(id)); + + let (block, difficulty, extra) = match id { + BlockNumberOrId::Number(BlockNumber::Pending) => { + let info = self.client.chain_info(); + let pending_block = self.miner.pending_block(info.best_block_number); + let difficulty = { + let latest_difficulty = self.client.block_total_difficulty(BlockId::Latest).expect("blocks in chain have details; qed"); + let pending_difficulty = self.miner.pending_block_header(info.best_block_number).map(|header| *header.difficulty()); + + if let Some(difficulty) = pending_difficulty { + difficulty + latest_difficulty + } else { + latest_difficulty + } + }; + + let extra = pending_block.as_ref().map(|b| self.client.engine().extra_info(&b.header)); + + (pending_block.map(|b| encoded::Block::new(b.rlp_bytes(Seal::Without))), Some(difficulty), extra) + }, + + BlockNumberOrId::Number(num) => { + let id = match num { + BlockNumber::Latest => BlockId::Latest, + BlockNumber::Earliest => BlockId::Earliest, + BlockNumber::Num(n) => BlockId::Number(n), + BlockNumber::Pending => unreachable!(), // Already covered + }; + + client_query(id) + }, + + BlockNumberOrId::Id(id) => client_query(id), + }; + + match (block, difficulty) { (Some(block), Some(total_difficulty)) => { let view = block.header_view(); Ok(Some(RichBlock { @@ -176,29 +246,109 @@ impl EthClient where }, extra_data: Bytes::new(view.extra_data()), }, - extra_info: client.block_extra_info(id.clone()).expect(EXTRA_INFO_PROOF), + extra_info: extra.expect(EXTRA_INFO_PROOF), })) }, _ => Ok(None) } } - fn transaction(&self, id: TransactionId) -> Result> { - match self.client.transaction(id) { + fn transaction(&self, id: PendingTransactionId) -> Result> { + let client_transaction = |id| match self.client.transaction(id) { Some(t) => Ok(Some(Transaction::from_localized(t, self.eip86_transition))), None => Ok(None), + }; + + match id { + PendingTransactionId::Hash(hash) => client_transaction(TransactionId::Hash(hash)), + + PendingTransactionId::Location(PendingOrBlock::Block(block), index) => { + client_transaction(TransactionId::Location(block, index)) + }, + + PendingTransactionId::Location(PendingOrBlock::Pending, index) => { + let info = self.client.chain_info(); + let pending_block = match self.miner.pending_block(info.best_block_number) { + Some(block) => block, + None => return Ok(None), + }; + + // Implementation stolen from `extract_transaction_at_index` + let transaction = pending_block.transactions.get(index) + // Verify if transaction signature is correct. + .and_then(|tx| SignedTransaction::new(tx.clone()).ok()) + .map(|signed_tx| { + let (signed, sender, _) = signed_tx.deconstruct(); + let block_hash = pending_block.header.hash(); + let block_number = pending_block.header.number(); + let transaction_index = index; + let cached_sender = Some(sender); + + LocalizedTransaction { + signed, + block_number, + block_hash, + transaction_index, + cached_sender, + } + }) + .map(|tx| Transaction::from_localized(tx, self.eip86_transition)); + + Ok(transaction) + } } } - fn uncle(&self, id: UncleId) -> Result> { + fn uncle(&self, id: PendingUncleId) -> Result> { let client = &self.client; - let uncle: BlockHeader = match client.uncle(id) { - Some(hdr) => hdr.decode(), - None => { return Ok(None); } - }; - let parent_difficulty = match client.block_total_difficulty(BlockId::Hash(uncle.parent_hash().clone())) { - Some(difficulty) => difficulty, - None => { return Ok(None); } + + let (uncle, parent_difficulty, extra) = match id { + PendingUncleId { id: PendingOrBlock::Pending, position } => { + let info = self.client.chain_info(); + + let pending_block = match self.miner.pending_block(info.best_block_number) { + Some(block) => block, + None => return Ok(None), + }; + + let uncle = match pending_block.uncles.get(position) { + Some(uncle) => uncle.clone(), + None => return Ok(None), + }; + + let difficulty = { + let latest_difficulty = self.client.block_total_difficulty(BlockId::Latest).expect("blocks in chain have details; qed"); + let pending_difficulty = self.miner.pending_block_header(info.best_block_number).map(|header| *header.difficulty()); + + if let Some(difficulty) = pending_difficulty { + difficulty + latest_difficulty + } else { + latest_difficulty + } + }; + + let extra = self.client.engine().extra_info(&pending_block.header); + + (uncle, difficulty, extra) + }, + + PendingUncleId { id: PendingOrBlock::Block(block_id), position } => { + let uncle_id = UncleId { block: block_id, position }; + + let uncle = match client.uncle(uncle_id) { + Some(hdr) => hdr.decode(), + None => { return Ok(None); } + }; + + let parent_difficulty = match client.block_total_difficulty(BlockId::Hash(uncle.parent_hash().clone())) { + Some(difficulty) => difficulty, + None => { return Ok(None); } + }; + + let extra = client.uncle_extra_info(uncle_id).expect(EXTRA_INFO_PROOF); + + (uncle, parent_difficulty, extra) + } }; let size = client.block(BlockId::Hash(uncle.hash())) @@ -229,7 +379,7 @@ impl EthClient where uncles: vec![], transactions: BlockTransactions::Hashes(vec![]), }, - extra_info: client.uncle_extra_info(id).expect(EXTRA_INFO_PROOF), + extra_info: extra, }; Ok(Some(block)) } @@ -241,6 +391,24 @@ impl EthClient where .and_then(|_| store.dapp_addresses(dapp)) .map_err(|e| errors::account("Could not fetch accounts.", e)) } + + fn get_state(&self, number: BlockNumber) -> StateOrBlock { + match number { + BlockNumber::Num(num) => BlockId::Number(num).into(), + BlockNumber::Earliest => BlockId::Earliest.into(), + BlockNumber::Latest => BlockId::Latest.into(), + + BlockNumber::Pending => { + let info = self.client.chain_info(); + + self.miner + .pending_state(info.best_block_number) + .map(|s| Box::new(s) as Box) + .unwrap_or(Box::new(self.client.latest_state()) as Box) + .into() + } + } + } } pub fn pending_logs(miner: &M, best_block: EthBlockNumber, filter: &EthcoreFilter) -> Vec where M: MinerService { @@ -265,20 +433,27 @@ pub fn pending_logs(miner: &M, best_block: EthBlockNumber, filter: &EthcoreFi fn check_known(client: &C, number: BlockNumber) -> Result<()> where C: MiningBlockChainClient { use ethcore::block_status::BlockStatus; - match client.block_status(number.into()) { + let id = match number { + BlockNumber::Pending => return Ok(()), + + BlockNumber::Num(n) => BlockId::Number(n), + BlockNumber::Latest => BlockId::Latest, + BlockNumber::Earliest => BlockId::Earliest, + }; + + match client.block_status(id) { BlockStatus::InChain => Ok(()), - BlockStatus::Pending => Ok(()), _ => Err(errors::unknown_block()), } } const MAX_QUEUE_SIZE_TO_MINE_ON: usize = 4; // because uncles go back 6. -impl Eth for EthClient where - C: MiningBlockChainClient + 'static, +impl Eth for EthClient where + C: MiningBlockChainClient + StateClient + Call + EngineInfo + 'static, SN: SnapshotService + 'static, S: SyncProvider + 'static, - M: MinerService + 'static, + M: MinerService + 'static, EM: ExternalMinerService + 'static, { type Metadata = Metadata; @@ -357,10 +532,10 @@ impl Eth for EthClient where fn balance(&self, address: RpcH160, num: Trailing) -> BoxFuture { let address = address.into(); - let id = num.unwrap_or_default(); + let num = num.unwrap_or_default(); - try_bf!(check_known(&*self.client, id.clone())); - let res = match self.client.balance(&address, id.into()) { + try_bf!(check_known(&*self.client, num.clone())); + let res = match self.client.balance(&address, self.get_state(num)) { Some(balance) => Ok(balance.into()), None => Err(errors::state_pruned()), }; @@ -372,10 +547,10 @@ impl Eth for EthClient where let address: Address = RpcH160::into(address); let position: U256 = RpcU256::into(pos); - let id = num.unwrap_or_default(); + let num = 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()) { + try_bf!(check_known(&*self.client, num.clone())); + let res = match self.client.storage_at(&address, &H256::from(position), self.get_state(num)) { Some(s) => Ok(s.into()), None => Err(errors::state_pruned()), }; @@ -390,15 +565,33 @@ 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.client.nonce(&address, BlockNumber::Pending.into())); + .or_else(|| self.client.nonce(&address, BlockId::Latest)); + match nonce { 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()) { + }, + + BlockNumber::Pending => { + let info = self.client.chain_info(); + let nonce = self.miner + .pending_state(info.best_block_number) + .and_then(|s| s.nonce(&address).ok()) + .or_else(|| { + warn!("Fallback to `BlockId::Latest`"); + self.client.nonce(&address, BlockId::Latest) + }); + + match nonce { + Some(nonce) => Ok(nonce.into()), + None => Err(errors::database("latest nonce missing")) + } + }, + + number => { + try_bf!(check_known(&*self.client, number.clone())); + match self.client.nonce(&address, block_number_to_id(number)) { Some(nonce) => Ok(nonce.into()), None => Err(errors::state_pruned()), } @@ -419,7 +612,7 @@ impl Eth for EthClient where self.miner.status().transactions_in_pending_block.into() ), _ => - self.client.block(num.into()) + self.client.block(block_number_to_id(num)) .map(|block| block.transactions_count().into()) })) } @@ -432,7 +625,7 @@ impl Eth for EthClient where fn block_uncles_count_by_number(&self, num: BlockNumber) -> BoxFuture> { Box::new(future::ok(match num { BlockNumber::Pending => Some(0.into()), - _ => self.client.block(num.into()) + _ => self.client.block(block_number_to_id(num)) .map(|block| block.uncles_count().into() ), })) @@ -441,10 +634,10 @@ impl Eth for EthClient where fn code_at(&self, address: RpcH160, num: Trailing) -> BoxFuture { let address: Address = RpcH160::into(address); - let id = num.unwrap_or_default(); - try_bf!(check_known(&*self.client, id.clone())); + let num = num.unwrap_or_default(); + try_bf!(check_known(&*self.client, num.clone())); - let res = match self.client.code(&address, id.into()) { + let res = match self.client.code(&address, self.get_state(num)) { Some(code) => Ok(code.map_or_else(Bytes::default, Bytes::new)), None => Err(errors::state_pruned()), }; @@ -453,17 +646,17 @@ impl Eth for EthClient where } fn block_by_hash(&self, hash: RpcH256, include_txs: bool) -> BoxFuture> { - Box::new(future::done(self.block(BlockId::Hash(hash.into()), include_txs))) + Box::new(future::done(self.rich_block(BlockId::Hash(hash.into()).into(), include_txs))) } fn block_by_number(&self, num: BlockNumber, include_txs: bool) -> BoxFuture> { - Box::new(future::done(self.block(num.into(), include_txs))) + Box::new(future::done(self.rich_block(num.into(), include_txs))) } fn transaction_by_hash(&self, hash: RpcH256) -> BoxFuture> { let hash: H256 = hash.into(); let block_number = self.client.chain_info().best_block_number; - let tx = try_bf!(self.transaction(TransactionId::Hash(hash))).or_else(|| { + let tx = try_bf!(self.transaction(PendingTransactionId::Hash(hash))).or_else(|| { self.miner.transaction(block_number, &hash) .map(|t| Transaction::from_pending(t, block_number, self.eip86_transition)) }); @@ -472,15 +665,20 @@ impl Eth for EthClient where } fn transaction_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> BoxFuture> { - Box::new(future::done( - self.transaction(TransactionId::Location(BlockId::Hash(hash.into()), index.value())) - )) + let id = PendingTransactionId::Location(PendingOrBlock::Block(BlockId::Hash(hash.into())), index.value()); + Box::new(future::done(self.transaction(id))) } fn transaction_by_block_number_and_index(&self, num: BlockNumber, index: Index) -> BoxFuture> { - Box::new(future::done( - self.transaction(TransactionId::Location(num.into(), index.value())) - )) + let block_id = match num { + BlockNumber::Latest => PendingOrBlock::Block(BlockId::Latest), + BlockNumber::Earliest => PendingOrBlock::Block(BlockId::Earliest), + BlockNumber::Num(num) => PendingOrBlock::Block(BlockId::Number(num)), + BlockNumber::Pending => PendingOrBlock::Pending, + }; + + let transaction_id = PendingTransactionId::Location(block_id, index.value()); + Box::new(future::done(self.transaction(transaction_id))) } fn transaction_receipt(&self, hash: RpcH256) -> BoxFuture> { @@ -497,17 +695,22 @@ impl Eth for EthClient where } fn uncle_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> BoxFuture> { - Box::new(future::done(self.uncle(UncleId { - block: BlockId::Hash(hash.into()), + Box::new(future::done(self.uncle(PendingUncleId { + id: PendingOrBlock::Block(BlockId::Hash(hash.into())), position: index.value() }))) } fn uncle_by_block_number_and_index(&self, num: BlockNumber, index: Index) -> BoxFuture> { - Box::new(future::done(self.uncle(UncleId { - block: num.into(), - position: index.value() - }))) + let id = match num { + BlockNumber::Latest => PendingUncleId { id: PendingOrBlock::Block(BlockId::Latest), position: index.value() }, + BlockNumber::Earliest => PendingUncleId { id: PendingOrBlock::Block(BlockId::Earliest), position: index.value() }, + BlockNumber::Num(num) => PendingUncleId { id: PendingOrBlock::Block(BlockId::Number(num)), position: index.value() }, + + BlockNumber::Pending => PendingUncleId { id: PendingOrBlock::Pending, position: index.value() }, + }; + + Box::new(future::done(self.uncle(id))) } fn compilers(&self) -> Result> { @@ -630,7 +833,28 @@ impl Eth for EthClient where let signed = try_bf!(fake_sign::sign_call(request, meta.is_dapp())); let num = num.unwrap_or_default(); - let result = self.client.call(&signed, Default::default(), num.into()); + + let (mut state, header) = if num == BlockNumber::Pending { + let info = self.client.chain_info(); + let state = try_bf!(self.miner.pending_state(info.best_block_number).ok_or(errors::state_pruned())); + let header = try_bf!(self.miner.pending_block_header(info.best_block_number).ok_or(errors::state_pruned())); + + (state, header) + } else { + let id = match num { + BlockNumber::Num(num) => BlockId::Number(num), + BlockNumber::Earliest => BlockId::Earliest, + BlockNumber::Latest => BlockId::Latest, + BlockNumber::Pending => unreachable!(), // Already covered + }; + + let state = try_bf!(self.client.state_at(id).ok_or(errors::state_pruned())); + let header = try_bf!(self.client.block_header(id).ok_or(errors::state_pruned())); + + (state, header.decode()) + }; + + let result = self.client.call(&signed, Default::default(), &mut state, &header); Box::new(future::done(result .map(|b| b.output.into()) @@ -641,7 +865,29 @@ impl Eth for EthClient where fn estimate_gas(&self, meta: Self::Metadata, request: CallRequest, num: Trailing) -> BoxFuture { let request = CallRequest::into(request); let signed = try_bf!(fake_sign::sign_call(request, meta.is_dapp())); - Box::new(future::done(self.client.estimate_gas(&signed, num.unwrap_or_default().into()) + let num = num.unwrap_or_default(); + + let (state, header) = if num == BlockNumber::Pending { + let info = self.client.chain_info(); + let state = try_bf!(self.miner.pending_state(info.best_block_number).ok_or(errors::state_pruned())); + let header = try_bf!(self.miner.pending_block_header(info.best_block_number).ok_or(errors::state_pruned())); + + (state, header) + } else { + let id = match num { + BlockNumber::Num(num) => BlockId::Number(num), + BlockNumber::Earliest => BlockId::Earliest, + BlockNumber::Latest => BlockId::Latest, + BlockNumber::Pending => unreachable!(), // Already covered + }; + + let state = try_bf!(self.client.state_at(id).ok_or(errors::state_pruned())); + let header = try_bf!(self.client.block_header(id).ok_or(errors::state_pruned())); + + (state, header.decode()) + }; + + Box::new(future::done(self.client.estimate_gas(&signed, &state, &header) .map(Into::into) .map_err(errors::call) )) diff --git a/rpc/src/v1/impls/light/eth.rs b/rpc/src/v1/impls/light/eth.rs index 7a5fb2886..e29f088c8 100644 --- a/rpc/src/v1/impls/light/eth.rs +++ b/rpc/src/v1/impls/light/eth.rs @@ -65,6 +65,23 @@ pub struct EthClient { gas_price_percentile: usize, } +impl EthClient { + fn num_to_id(num: BlockNumber) -> BlockId { + // Note: Here we treat `Pending` as `Latest`. + // Since light clients don't produce pending blocks + // (they don't have state) we can safely fallback to `Latest`. + match num { + BlockNumber::Num(n) => BlockId::Number(n), + BlockNumber::Earliest => BlockId::Earliest, + BlockNumber::Latest => BlockId::Latest, + BlockNumber::Pending => { + warn!("`Pending` is deprecated and may be removed in future versions. Falling back to `Latest`"); + BlockId::Latest + } + } + } +} + impl Clone for EthClient { fn clone(&self) -> Self { // each instance should have its own poll manager. @@ -264,7 +281,7 @@ impl Eth for EthClient { } fn balance(&self, address: RpcH160, num: Trailing) -> BoxFuture { - Box::new(self.fetcher().account(address.into(), num.unwrap_or_default().into()) + Box::new(self.fetcher().account(address.into(), Self::num_to_id(num.unwrap_or_default())) .map(|acc| acc.map_or(0.into(), |a| a.balance).into())) } @@ -277,11 +294,11 @@ impl Eth for EthClient { } fn block_by_number(&self, num: BlockNumber, include_txs: bool) -> BoxFuture> { - Box::new(self.rich_block(num.into(), include_txs).map(Some)) + Box::new(self.rich_block(Self::num_to_id(num), include_txs).map(Some)) } fn transaction_count(&self, address: RpcH160, num: Trailing) -> BoxFuture { - Box::new(self.fetcher().account(address.into(), num.unwrap_or_default().into()) + Box::new(self.fetcher().account(address.into(), Self::num_to_id(num.unwrap_or_default())) .map(|acc| acc.map_or(0.into(), |a| a.nonce).into())) } @@ -304,7 +321,7 @@ impl Eth for EthClient { fn block_transaction_count_by_number(&self, num: BlockNumber) -> BoxFuture> { let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone()); - Box::new(self.fetcher().header(num.into()).and_then(move |hdr| { + Box::new(self.fetcher().header(Self::num_to_id(num)).and_then(move |hdr| { if hdr.transactions_root() == KECCAK_NULL_RLP { Either::A(future::ok(Some(U256::from(0).into()))) } else { @@ -336,7 +353,7 @@ impl Eth for EthClient { fn block_uncles_count_by_number(&self, num: BlockNumber) -> BoxFuture> { let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone()); - Box::new(self.fetcher().header(num.into()).and_then(move |hdr| { + Box::new(self.fetcher().header(Self::num_to_id(num)).and_then(move |hdr| { if hdr.uncles_hash() == KECCAK_EMPTY_LIST_RLP { Either::B(future::ok(Some(U256::from(0).into()))) } else { @@ -350,7 +367,7 @@ impl Eth for EthClient { } fn code_at(&self, address: RpcH160, num: Trailing) -> BoxFuture { - Box::new(self.fetcher().code(address.into(), num.unwrap_or_default().into()).map(Into::into)) + Box::new(self.fetcher().code(address.into(), Self::num_to_id(num.unwrap_or_default())).map(Into::into)) } fn send_raw_transaction(&self, raw: Bytes) -> Result { @@ -422,7 +439,7 @@ impl Eth for EthClient { fn transaction_by_block_number_and_index(&self, num: BlockNumber, idx: Index) -> BoxFuture> { let eip86 = self.client.eip86_transition(); - Box::new(self.fetcher().block(num.into()).map(move |block| { + Box::new(self.fetcher().block(Self::num_to_id(num)).map(move |block| { light_fetch::extract_transaction_at_index(block, idx.value(), eip86) })) } @@ -467,7 +484,7 @@ impl Eth for EthClient { fn uncle_by_block_number_and_index(&self, num: BlockNumber, idx: Index) -> BoxFuture> { let client = self.client.clone(); - Box::new(self.fetcher().block(num.into()).map(move |block| { + Box::new(self.fetcher().block(Self::num_to_id(num)).map(move |block| { extract_uncle_at_index(block, idx, client) })) } diff --git a/rpc/src/v1/impls/light/parity.rs b/rpc/src/v1/impls/light/parity.rs index 060d7288b..3b2fab9a3 100644 --- a/rpc/src/v1/impls/light/parity.rs +++ b/rpc/src/v1/impls/light/parity.rs @@ -27,6 +27,7 @@ use ethsync::LightSyncProvider; use ethcore::account_provider::AccountProvider; use ethcore_logger::RotatingLogger; use node_health::{NodeHealth, Health}; +use ethcore::ids::BlockId; use light::client::LightChainClient; @@ -405,7 +406,16 @@ impl Parity for ParityClient { } }; - Box::new(self.fetcher().header(number.unwrap_or_default().into()).map(from_encoded)) + // Note: Here we treat `Pending` as `Latest`. + // Since light clients don't produce pending blocks + // (they don't have state) we can safely fallback to `Latest`. + let id = match number.unwrap_or_default() { + BlockNumber::Num(n) => BlockId::Number(n), + BlockNumber::Earliest => BlockId::Earliest, + BlockNumber::Latest | BlockNumber::Pending => BlockId::Latest, + }; + + Box::new(self.fetcher().header(id).map(from_encoded)) } fn ipfs_cid(&self, content: Bytes) -> Result { diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index b822bfd56..95c810aa1 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -27,10 +27,11 @@ use ethkey::{Brain, Generator}; use ethstore::random_phrase; use ethsync::{SyncProvider, ManageNetwork}; use ethcore::account_provider::AccountProvider; -use ethcore::client::{MiningBlockChainClient}; +use ethcore::client::{MiningBlockChainClient, StateClient, Call}; use ethcore::ids::BlockId; use ethcore::miner::MinerService; use ethcore::mode::Mode; +use ethcore::state::StateInfo; use ethcore_logger::RotatingLogger; use node_health::{NodeHealth, Health}; use updater::{Service as UpdateService}; @@ -48,7 +49,8 @@ use v1::types::{ TransactionStats, LocalTransactionStatus, BlockNumber, ConsensusCapability, VersionInfo, OperationsInfo, DappId, ChainStatus, - AccountInfo, HwAccountInfo, RichHeader + AccountInfo, HwAccountInfo, RichHeader, + block_number_to_id }; use Host; @@ -112,9 +114,10 @@ impl ParityClient where } } -impl Parity for ParityClient where - C: MiningBlockChainClient + 'static, - M: MinerService + 'static, +impl Parity for ParityClient where + S: StateInfo + 'static, + C: MiningBlockChainClient + StateClient + Call + 'static, + M: MinerService + 'static, U: UpdateService + 'static, { type Metadata = Metadata; @@ -275,14 +278,32 @@ impl Parity for ParityClient where } fn list_accounts(&self, count: u64, after: Option, block_number: Trailing) -> Result>> { + let number = match block_number.unwrap_or_default() { + BlockNumber::Pending => { + warn!("BlockNumber::Pending is unsupported"); + return Ok(None); + }, + + num => block_number_to_id(num) + }; + Ok(self.client - .list_accounts(block_number.unwrap_or_default().into(), after.map(Into::into).as_ref(), count) + .list_accounts(number, after.map(Into::into).as_ref(), count) .map(|a| a.into_iter().map(Into::into).collect())) } fn list_storage_keys(&self, address: H160, count: u64, after: Option, block_number: Trailing) -> Result>> { + let number = match block_number.unwrap_or_default() { + BlockNumber::Pending => { + warn!("BlockNumber::Pending is unsupported"); + return Ok(None); + }, + + num => block_number_to_id(num) + }; + Ok(self.client - .list_storage(block_number.unwrap_or_default().into(), &address.into(), after.map(Into::into).as_ref(), count) + .list_storage(number, &address.into(), after.map(Into::into).as_ref(), count) .map(|a| a.into_iter().map(Into::into).collect())) } @@ -396,17 +417,31 @@ impl Parity for ParityClient where } fn block_header(&self, number: Trailing) -> BoxFuture { - const EXTRA_INFO_PROOF: &'static str = "Object exists in in blockchain (fetched earlier), extra_info is always available if object exists; qed"; + const EXTRA_INFO_PROOF: &str = "Object exists in blockchain (fetched earlier), extra_info is always available if object exists; qed"; + let number = number.unwrap_or_default(); - let id: BlockId = number.unwrap_or_default().into(); - let encoded = match self.client.block_header(id.clone()) { - Some(encoded) => encoded, - None => return Box::new(future::err(errors::unknown_block())), + let (header, extra) = if number == BlockNumber::Pending { + let info = self.client.chain_info(); + let header = try_bf!(self.miner.pending_block_header(info.best_block_number).ok_or(errors::unknown_block())); + + (header.encoded(), None) + } else { + let id = match number { + BlockNumber::Num(num) => BlockId::Number(num), + BlockNumber::Earliest => BlockId::Earliest, + BlockNumber::Latest => BlockId::Latest, + BlockNumber::Pending => unreachable!(), // Already covered + }; + + let header = try_bf!(self.client.block_header(id.clone()).ok_or(errors::unknown_block())); + let info = self.client.block_extra_info(id).expect(EXTRA_INFO_PROOF); + + (header, Some(info)) }; Box::new(future::ok(RichHeader { - inner: encoded.into(), - extra_info: self.client.block_extra_info(id).expect(EXTRA_INFO_PROOF), + inner: header.into(), + extra_info: extra.unwrap_or_default(), })) } @@ -414,7 +449,7 @@ impl Parity for ParityClient where ipfs::cid(content) } - fn call(&self, meta: Self::Metadata, requests: Vec, block: Trailing) -> Result> { + fn call(&self, meta: Self::Metadata, requests: Vec, num: Trailing) -> Result> { let requests = requests .into_iter() .map(|request| Ok(( @@ -423,9 +458,29 @@ impl Parity for ParityClient where ))) .collect::>>()?; - let block = block.unwrap_or_default(); + let num = num.unwrap_or_default(); - self.client.call_many(&requests, block.into()) + let (mut state, header) = if num == BlockNumber::Pending { + let info = self.client.chain_info(); + let state = self.miner.pending_state(info.best_block_number).ok_or(errors::state_pruned())?; + let header = self.miner.pending_block_header(info.best_block_number).ok_or(errors::state_pruned())?; + + (state, header) + } else { + let id = match num { + BlockNumber::Num(num) => BlockId::Number(num), + BlockNumber::Earliest => BlockId::Earliest, + BlockNumber::Latest => BlockId::Latest, + BlockNumber::Pending => unreachable!(), // Already covered + }; + + let state = self.client.state_at(id).ok_or(errors::state_pruned())?; + let header = self.client.block_header(id).ok_or(errors::state_pruned())?; + + (state, header.decode()) + }; + + self.client.call_many(&requests, &mut state, &header) .map(|res| res.into_iter().map(|res| res.output.into()).collect()) .map_err(errors::call) } diff --git a/rpc/src/v1/impls/traces.rs b/rpc/src/v1/impls/traces.rs index 6ee10e237..0ee1afd5f 100644 --- a/rpc/src/v1/impls/traces.rs +++ b/rpc/src/v1/impls/traces.rs @@ -18,7 +18,7 @@ use std::sync::Arc; -use ethcore::client::{MiningBlockChainClient, CallAnalytics, TransactionId, TraceId}; +use ethcore::client::{MiningBlockChainClient, CallAnalytics, TransactionId, TraceId, StateClient, StateInfo, Call, BlockId}; use rlp::UntrustedRlp; use transaction::SignedTransaction; @@ -27,7 +27,7 @@ use jsonrpc_macros::Trailing; use v1::Metadata; use v1::traits::Traces; use v1::helpers::{errors, fake_sign}; -use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, TraceOptions, H256}; +use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, TraceOptions, H256, block_number_to_id}; fn to_call_analytics(flags: TraceOptions) -> CallAnalytics { CallAnalytics { @@ -51,7 +51,10 @@ impl TracesClient { } } -impl Traces for TracesClient where C: MiningBlockChainClient + 'static { +impl Traces for TracesClient where + S: StateInfo + 'static, + C: MiningBlockChainClient + StateClient + Call + 'static +{ type Metadata = Metadata; fn filter(&self, filter: TraceFilter) -> Result>> { @@ -60,7 +63,12 @@ impl Traces for TracesClient where C: MiningBlockChainClient + 'static { } fn block_traces(&self, block_number: BlockNumber) -> Result>> { - Ok(self.client.block_traces(block_number.into()) + let id = match block_number { + BlockNumber::Pending => return Ok(None), + num => block_number_to_id(num) + }; + + Ok(self.client.block_traces(id) .map(|traces| traces.into_iter().map(LocalizedTrace::from).collect())) } @@ -85,7 +93,18 @@ impl Traces for TracesClient where C: MiningBlockChainClient + 'static { let request = CallRequest::into(request); let signed = fake_sign::sign_call(request, meta.is_dapp())?; - self.client.call(&signed, to_call_analytics(flags), block.into()) + let id = match block { + BlockNumber::Num(num) => BlockId::Number(num), + BlockNumber::Earliest => BlockId::Earliest, + BlockNumber::Latest => BlockId::Latest, + + BlockNumber::Pending => return Err(errors::invalid_params("`BlockNumber::Pending` is not supported", ())), + }; + + let mut state = self.client.state_at(id).ok_or(errors::state_pruned())?; + let header = self.client.block_header(id).ok_or(errors::state_pruned())?; + + self.client.call(&signed, to_call_analytics(flags), &mut state, &header.decode()) .map(TraceResults::from) .map_err(errors::call) } @@ -101,7 +120,18 @@ impl Traces for TracesClient where C: MiningBlockChainClient + 'static { }) .collect::>>()?; - self.client.call_many(&requests, block.into()) + let id = match block { + BlockNumber::Num(num) => BlockId::Number(num), + BlockNumber::Earliest => BlockId::Earliest, + BlockNumber::Latest => BlockId::Latest, + + BlockNumber::Pending => return Err(errors::invalid_params("`BlockNumber::Pending` is not supported", ())), + }; + + let mut state = self.client.state_at(id).ok_or(errors::state_pruned())?; + let header = self.client.block_header(id).ok_or(errors::state_pruned())?; + + self.client.call_many(&requests, &mut state, &header.decode()) .map(|results| results.into_iter().map(TraceResults::from).collect()) .map_err(errors::call) } @@ -112,7 +142,18 @@ impl Traces for TracesClient where C: MiningBlockChainClient + 'static { 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, to_call_analytics(flags), block.into()) + let id = match block { + BlockNumber::Num(num) => BlockId::Number(num), + BlockNumber::Earliest => BlockId::Earliest, + BlockNumber::Latest => BlockId::Latest, + + BlockNumber::Pending => return Err(errors::invalid_params("`BlockNumber::Pending` is not supported", ())), + }; + + let mut state = self.client.state_at(id).ok_or(errors::state_pruned())?; + let header = self.client.block_header(id).ok_or(errors::state_pruned())?; + + self.client.call(&signed, to_call_analytics(flags), &mut state, &header.decode()) .map(TraceResults::from) .map_err(errors::call) } @@ -124,7 +165,15 @@ impl Traces for TracesClient where C: MiningBlockChainClient + 'static { } fn replay_block_transactions(&self, block_number: BlockNumber, flags: TraceOptions) -> Result> { - self.client.replay_block_transactions(block_number.into(), to_call_analytics(flags)) + let id = match block_number { + BlockNumber::Num(num) => BlockId::Number(num), + BlockNumber::Earliest => BlockId::Earliest, + BlockNumber::Latest => BlockId::Latest, + + BlockNumber::Pending => return Err(errors::invalid_params("`BlockNumber::Pending` is not supported", ())), + }; + + self.client.replay_block_transactions(id, to_call_analytics(flags)) .map(|results| results.into_iter().map(TraceResults::from).collect()) .map_err(errors::call) } diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index c8444d304..84dd64644 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -22,10 +22,10 @@ use std::time::Duration; use ethereum_types::{U256, H256, Address}; use ethcore::account_provider::AccountProvider; use ethcore::block::Block; -use ethcore::client::{BlockChainClient, Client, ClientConfig}; +use ethcore::client::{BlockChainClient, Client, ClientConfig, ChainInfo, ImportBlock}; use ethcore::ethereum; use ethcore::ids::BlockId; -use ethcore::miner::{MinerOptions, Banning, GasPricer, MinerService, Miner, PendingSet, GasLimit}; +use ethcore::miner::{MinerOptions, Banning, GasPricer, Miner, PendingSet, GasLimit}; use ethcore::spec::{Genesis, Spec}; use ethcore::views::BlockView; use ethjson::blockchain::BlockChain; @@ -101,7 +101,7 @@ fn make_spec(chain: &BlockChain) -> Spec { struct EthTester { client: Arc, - _miner: Arc, + _miner: Arc, _snapshot: Arc, accounts: Arc, handler: IoHandler, diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 994cd544a..ca9993bae 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -18,16 +18,19 @@ use std::collections::{BTreeMap, HashMap}; use std::collections::hash_map::Entry; -use ethereum_types::{H256, U256, Address}; + use bytes::Bytes; use ethcore::account_provider::SignError as AccountError; -use ethcore::block::ClosedBlock; -use ethcore::client::MiningBlockChainClient; +use ethcore::block::{Block, ClosedBlock}; +use ethcore::client::{Nonce, PrepareOpenBlock, StateClient, EngineInfo}; +use ethcore::engines::EthEngine; use ethcore::error::Error; -use ethcore::header::BlockNumber; +use ethcore::header::{BlockNumber, Header}; +use ethcore::ids::BlockId; use ethcore::miner::{MinerService, MinerStatus}; -use miner::local_transactions::Status as LocalTransactionStatus; use ethcore::receipt::{Receipt, RichReceipt}; +use ethereum_types::{H256, U256, Address}; +use miner::local_transactions::Status as LocalTransactionStatus; use parking_lot::{RwLock, Mutex}; use transaction::{UnverifiedTransaction, SignedTransaction, PendingTransaction, ImportResult as TransactionImportResult}; @@ -92,7 +95,39 @@ impl TestMinerService { } } +impl StateClient for TestMinerService { + // State will not be used by test client anyway, since all methods that accept state are mocked + type State = (); + + fn latest_state(&self) -> Self::State { + () + } + + fn state_at(&self, _id: BlockId) -> Option { + Some(()) + } +} + +impl EngineInfo for TestMinerService { + fn engine(&self) -> &EthEngine { + unimplemented!() + } +} + impl MinerService for TestMinerService { + type State = (); + + fn pending_state(&self, _latest_block_number: BlockNumber) -> Option { + None + } + + fn pending_block_header(&self, _latest_block_number: BlockNumber) -> Option
{ + None + } + + fn pending_block(&self, _latest_block_number: BlockNumber) -> Option { + None + } /// Returns miner's status. fn status(&self) -> MinerStatus { @@ -164,7 +199,7 @@ impl MinerService for TestMinerService { } /// Imports transactions to transaction queue. - fn import_external_transactions(&self, _chain: &MiningBlockChainClient, transactions: Vec) -> + fn import_external_transactions(&self, _chain: &C, transactions: Vec) -> Vec> { // lets assume that all txs are valid let transactions: Vec<_> = transactions.into_iter().map(|tx| SignedTransaction::new(tx).unwrap()).collect(); @@ -181,7 +216,7 @@ impl MinerService for TestMinerService { } /// Imports transactions to transaction queue. - fn import_own_transaction(&self, chain: &MiningBlockChainClient, pending: PendingTransaction) -> + fn import_own_transaction(&self, chain: &C, pending: PendingTransaction) -> Result { // keep the pending nonces up to date @@ -201,12 +236,12 @@ impl MinerService for TestMinerService { } /// Removes all transactions from the queue and restart mining operation. - fn clear_and_reset(&self, _chain: &MiningBlockChainClient) { + fn clear_and_reset(&self, _chain: &C) { unimplemented!(); } /// Called when blocks are imported to chain, updates transactions queue. - fn chain_new_blocks(&self, _chain: &MiningBlockChainClient, _imported: &[H256], _invalid: &[H256], _enacted: &[H256], _retracted: &[H256]) { + fn chain_new_blocks(&self, _chain: &C, _imported: &[H256], _invalid: &[H256], _enacted: &[H256], _retracted: &[H256]) { unimplemented!(); } @@ -216,11 +251,11 @@ impl MinerService for TestMinerService { } /// New chain head event. Restart mining operation. - fn update_sealing(&self, _chain: &MiningBlockChainClient) { + fn update_sealing(&self, _chain: &C) { unimplemented!(); } - fn map_sealing_work(&self, chain: &MiningBlockChainClient, f: F) -> Option where F: FnOnce(&ClosedBlock) -> T { + fn map_sealing_work(&self, chain: &C, f: F) -> Option where F: FnOnce(&ClosedBlock) -> T { let open_block = chain.prepare_open_block(self.author(), *self.gas_range_target.write(), self.extra_data()); Some(f(&open_block.close())) } @@ -229,7 +264,7 @@ impl MinerService for TestMinerService { self.pending_transactions.lock().get(hash).cloned().map(Into::into) } - fn remove_pending_transaction(&self, _chain: &MiningBlockChainClient, hash: &H256) -> Option { + fn remove_pending_transaction(&self, _chain: &C, hash: &H256) -> Option { self.pending_transactions.lock().remove(hash).map(Into::into) } @@ -279,7 +314,7 @@ impl MinerService for TestMinerService { /// Submit `seal` as a valid solution for the header of `pow_hash`. /// Will check the seal, but not actually insert the block into the chain. - fn submit_seal(&self, _chain: &MiningBlockChainClient, _pow_hash: H256, _seal: Vec) -> Result<(), Error> { + fn submit_seal(&self, _chain: &C, _pow_hash: H256, _seal: Vec) -> Result<(), Error> { unimplemented!(); } diff --git a/rpc/src/v1/tests/mocked/eth_pubsub.rs b/rpc/src/v1/tests/mocked/eth_pubsub.rs index 0cf119432..1a0186c97 100644 --- a/rpc/src/v1/tests/mocked/eth_pubsub.rs +++ b/rpc/src/v1/tests/mocked/eth_pubsub.rs @@ -82,7 +82,7 @@ fn should_subscribe_to_new_heads() { fn should_subscribe_to_logs() { use ethcore::log_entry::{LocalizedLogEntry, LogEntry}; use ethcore::ids::BlockId; - use ethcore::client::BlockChainClient; + use ethcore::client::BlockInfo; // given let el = EventLoop::spawn(); diff --git a/rpc/src/v1/types/block_number.rs b/rpc/src/v1/types/block_number.rs index 9570f4420..b6c1860f5 100644 --- a/rpc/src/v1/types/block_number.rs +++ b/rpc/src/v1/types/block_number.rs @@ -91,14 +91,14 @@ impl<'a> Visitor<'a> for BlockNumberVisitor { } } -impl Into for BlockNumber { - fn into(self) -> BlockId { - match self { - BlockNumber::Num(n) => BlockId::Number(n), - BlockNumber::Earliest => BlockId::Earliest, - BlockNumber::Latest => BlockId::Latest, - BlockNumber::Pending => BlockId::Pending, - } +/// Converts `BlockNumber` to `BlockId`, panics on `BlockNumber::Pending` +pub fn block_number_to_id(number: BlockNumber) -> BlockId { + match number { + BlockNumber::Num(num) => BlockId::Number(num), + BlockNumber::Earliest => BlockId::Earliest, + BlockNumber::Latest => BlockId::Latest, + + BlockNumber::Pending => panic!("`BlockNumber::Pending` should be handled manually") } } @@ -122,11 +122,17 @@ mod tests { } #[test] - fn block_number_into() { - assert_eq!(BlockId::Number(100), BlockNumber::Num(100).into()); - assert_eq!(BlockId::Earliest, BlockNumber::Earliest.into()); - assert_eq!(BlockId::Latest, BlockNumber::Latest.into()); - assert_eq!(BlockId::Pending, BlockNumber::Pending.into()); + fn normal_block_number_to_id() { + assert_eq!(block_number_to_id(BlockNumber::Num(100)), BlockId::Number(100)); + assert_eq!(block_number_to_id(BlockNumber::Earliest), BlockId::Earliest); + assert_eq!(block_number_to_id(BlockNumber::Latest), BlockId::Latest); + } + + #[test] + #[should_panic] + fn pending_block_number_to_id() { + // Since this function is not allowed to be called in such way, panic should happen + block_number_to_id(BlockNumber::Pending); } } diff --git a/rpc/src/v1/types/filter.rs b/rpc/src/v1/types/filter.rs index 82cd5db30..52217459c 100644 --- a/rpc/src/v1/types/filter.rs +++ b/rpc/src/v1/types/filter.rs @@ -72,9 +72,15 @@ pub struct Filter { impl Into for Filter { fn into(self) -> EthFilter { + let num_to_id = |num| match num { + BlockNumber::Num(n) => BlockId::Number(n), + BlockNumber::Earliest => BlockId::Earliest, + BlockNumber::Latest | BlockNumber::Pending => BlockId::Latest, + }; + EthFilter { - from_block: self.from_block.map_or_else(|| BlockId::Latest, Into::into), - to_block: self.to_block.map_or_else(|| BlockId::Latest, Into::into), + from_block: self.from_block.map_or_else(|| BlockId::Latest, &num_to_id), + to_block: self.to_block.map_or_else(|| BlockId::Latest, &num_to_id), address: self.address.and_then(|address| match address { VariadicValue::Null => None, VariadicValue::Single(a) => Some(vec![a.into()]), diff --git a/rpc/src/v1/types/mod.rs b/rpc/src/v1/types/mod.rs index 65d1feeb4..7f1e51fad 100644 --- a/rpc/src/v1/types/mod.rs +++ b/rpc/src/v1/types/mod.rs @@ -50,7 +50,7 @@ pub mod pubsub; pub use self::account_info::{AccountInfo, ExtAccountInfo, HwAccountInfo}; pub use self::bytes::Bytes; pub use self::block::{RichBlock, Block, BlockTransactions, Header, RichHeader, Rich}; -pub use self::block_number::BlockNumber; +pub use self::block_number::{BlockNumber, block_number_to_id}; pub use self::call_request::CallRequest; pub use self::confirmations::{ ConfirmationPayload, ConfirmationRequest, ConfirmationResponse, ConfirmationResponseWithToken, diff --git a/rpc/src/v1/types/trace_filter.rs b/rpc/src/v1/types/trace_filter.rs index abd873c59..3a64f5248 100644 --- a/rpc/src/v1/types/trace_filter.rs +++ b/rpc/src/v1/types/trace_filter.rs @@ -44,8 +44,17 @@ pub struct TraceFilter { impl Into for TraceFilter { fn into(self) -> client::TraceFilter { - let start = self.from_block.map_or(BlockId::Latest, Into::into); - let end = self.to_block.map_or(BlockId::Latest, Into::into); + let num_to_id = |num| match num { + BlockNumber::Num(n) => BlockId::Number(n), + BlockNumber::Earliest => BlockId::Earliest, + BlockNumber::Latest => BlockId::Latest, + BlockNumber::Pending => { + warn!("Pending traces are not supported and might be removed in future versions. Falling back to Latest"); + BlockId::Latest + } + }; + let start = self.from_block.map_or(BlockId::Latest, &num_to_id); + let end = self.to_block.map_or(BlockId::Latest, &num_to_id); client::TraceFilter { range: start..end, from_address: self.from_address.map_or_else(Vec::new, |x| x.into_iter().map(Into::into).collect()), diff --git a/secret_store/src/acl_storage.rs b/secret_store/src/acl_storage.rs index 8dfde4122..f3d116145 100644 --- a/secret_store/src/acl_storage.rs +++ b/secret_store/src/acl_storage.rs @@ -18,7 +18,7 @@ use std::sync::Arc; use std::collections::{HashMap, HashSet}; use parking_lot::{Mutex, RwLock}; use ethkey::public_to_address; -use ethcore::client::{BlockChainClient, BlockId, ChainNotify}; +use ethcore::client::{BlockId, ChainNotify, CallContract, RegistryInfo}; use ethereum_types::{H256, Address}; use bytes::Bytes; use trusted_client::TrustedClient; diff --git a/secret_store/src/key_server_set.rs b/secret_store/src/key_server_set.rs index d5de48815..f069368b0 100644 --- a/secret_store/src/key_server_set.rs +++ b/secret_store/src/key_server_set.rs @@ -18,8 +18,8 @@ use std::sync::Arc; use std::net::SocketAddr; use std::collections::{BTreeMap, HashSet}; use parking_lot::Mutex; +use ethcore::client::{Client, BlockChainClient, BlockId, ChainNotify, CallContract, RegistryInfo}; use ethcore::filter::Filter; -use ethcore::client::{Client, BlockChainClient, BlockId, ChainNotify}; use ethkey::public_to_address; use hash::keccak; use ethereum_types::{H256, Address}; @@ -281,7 +281,7 @@ impl CachedContract { fn start_migration(&mut self, migration_id: H256) { // trust is not needed here, because it is the reaction to the read of the trusted client - if let (Some(client), Some(contract_address)) = (self.client.get_untrusted(), self.contract_address) { + if let (Some(client), Some(contract_address)) = (self.client.get_untrusted(), self.contract_address.as_ref()) { // check if we need to send start migration transaction if !update_last_transaction_block(&*client, &migration_id, &mut self.start_migration_tx) { return; @@ -291,7 +291,7 @@ impl CachedContract { let transaction_data = self.contract.functions().start_migration().input(migration_id); // send transaction - if let Err(error) = client.transact_contract(contract_address, transaction_data) { + if let Err(error) = client.transact_contract(*contract_address, transaction_data) { warn!(target: "secretstore_net", "{}: failed to submit auto-migration start transaction: {}", self.self_key_pair.public(), error); } else { diff --git a/secret_store/src/listener/service_contract.rs b/secret_store/src/listener/service_contract.rs index dd7af393d..f2d13f192 100644 --- a/secret_store/src/listener/service_contract.rs +++ b/secret_store/src/listener/service_contract.rs @@ -17,7 +17,7 @@ use std::sync::Arc; use parking_lot::RwLock; use ethcore::filter::Filter; -use ethcore::client::{Client, BlockChainClient, BlockId}; +use ethcore::client::{Client, BlockChainClient, BlockId, RegistryInfo, CallContract}; use ethkey::{Public, Signature, public_to_address}; use hash::keccak; use ethereum_types::{H256, U256, Address}; diff --git a/secret_store/src/trusted_client.rs b/secret_store/src/trusted_client.rs index 688e4099d..8a0f83d44 100644 --- a/secret_store/src/trusted_client.rs +++ b/secret_store/src/trusted_client.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . use std::sync::{Arc, Weak}; -use ethcore::client::{Client, BlockChainClient}; +use ethcore::client::{Client, BlockChainClient, ChainInfo}; use ethsync::SyncProvider; #[derive(Clone)] diff --git a/sync/src/chain.rs b/sync/src/chain.rs index c5c291bd0..d569991a5 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -2232,7 +2232,7 @@ mod tests { use ::SyncConfig; use super::{PeerInfo, PeerAsking}; use ethcore::header::*; - use ethcore::client::{BlockChainClient, EachBlockWith, TestBlockChainClient}; + use ethcore::client::{BlockChainClient, EachBlockWith, TestBlockChainClient, ChainInfo, BlockInfo}; use ethcore::miner::MinerService; use transaction::UnverifiedTransaction; diff --git a/sync/src/light_sync/tests/mod.rs b/sync/src/light_sync/tests/mod.rs index 1653d09d3..9fd270838 100644 --- a/sync/src/light_sync/tests/mod.rs +++ b/sync/src/light_sync/tests/mod.rs @@ -16,7 +16,7 @@ use tests::helpers::TestNet; -use ethcore::client::{BlockChainClient, BlockId, EachBlockWith}; +use ethcore::client::{BlockInfo, BlockId, EachBlockWith}; mod test_net; diff --git a/sync/src/tests/chain.rs b/sync/src/tests/chain.rs index f9ce17b5b..517ff0d99 100644 --- a/sync/src/tests/chain.rs +++ b/sync/src/tests/chain.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . use std::sync::Arc; -use ethcore::client::{TestBlockChainClient, BlockChainClient, BlockId, EachBlockWith}; +use ethcore::client::{TestBlockChainClient, BlockChainClient, BlockId, EachBlockWith, ChainInfo, BlockInfo}; use chain::{SyncState}; use super::helpers::*; use SyncConfig; diff --git a/sync/src/tests/consensus.rs b/sync/src/tests/consensus.rs index b6d915bda..33ecbb7f4 100644 --- a/sync/src/tests/consensus.rs +++ b/sync/src/tests/consensus.rs @@ -18,7 +18,7 @@ use std::sync::Arc; use hash::keccak; use ethereum_types::{U256, Address}; use io::{IoHandler, IoContext, IoChannel}; -use ethcore::client::{BlockChainClient, Client}; +use ethcore::client::{Client, ChainInfo}; use ethcore::service::ClientIoMessage; use ethcore::spec::Spec; use ethcore::miner::MinerService; diff --git a/util/journaldb/src/lib.rs b/util/journaldb/src/lib.rs index 949c0609f..23b56fa03 100644 --- a/util/journaldb/src/lib.rs +++ b/util/journaldb/src/lib.rs @@ -52,7 +52,7 @@ pub mod overlaydb; /// Export the `JournalDB` trait. pub use self::traits::JournalDB; -/// A journal database algorithm. +/// Journal database operating strategy. #[derive(Debug, PartialEq, Clone, Copy)] pub enum Algorithm { /// Keep all keys forever.