// Copyright 2015-2017 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Parity is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Parity. If not, see . //! Light client implementation. Stores data from light sync use std::sync::{Weak, Arc}; use ethcore::block_status::BlockStatus; use ethcore::client::{ClientReport, EnvInfo}; use ethcore::engines::Engine; use ethcore::error::BlockImportError; use ethcore::ids::BlockId; use ethcore::header::Header; use ethcore::verification::queue::{self, HeaderQueue}; use ethcore::blockchain_info::BlockChainInfo; use ethcore::spec::Spec; use ethcore::service::ClientIoMessage; use ethcore::encoded; use io::IoChannel; use util::{H256, U256, Mutex, RwLock}; use util::kvdb::{KeyValueDB, CompactionProfile}; use self::header_chain::{AncestryIter, HeaderChain}; use cache::Cache; pub use self::service::Service; mod header_chain; mod service; /// Configuration for the light client. #[derive(Debug, Clone)] pub struct Config { /// Verification queue config. pub queue: queue::Config, /// Chain column in database. pub chain_column: Option, /// Database cache size. `None` => rocksdb default. pub db_cache_size: Option, /// State db compaction profile pub db_compaction: CompactionProfile, /// Should db have WAL enabled? pub db_wal: bool, /// Should it do full verification of blocks? pub verify_full: bool, /// Should it check the seal of blocks? pub check_seal: bool, } impl Default for Config { fn default() -> Config { Config { queue: Default::default(), chain_column: None, db_cache_size: None, db_compaction: CompactionProfile::default(), db_wal: true, verify_full: true, check_seal: true, } } } /// Trait for interacting with the header chain abstractly. pub trait LightChainClient: Send + Sync { /// Get chain info. fn chain_info(&self) -> BlockChainInfo; /// Queue header to be verified. Required that all headers queued have their /// parent queued prior. fn queue_header(&self, header: Header) -> Result; /// Attempt to get a block hash by block id. fn block_hash(&self, id: BlockId) -> Option; /// Attempt to get block header by block id. fn block_header(&self, id: BlockId) -> Option; /// Get the best block header. fn best_block_header(&self) -> encoded::Header; /// Get a block's chain score by ID. fn score(&self, id: BlockId) -> Option; /// Get an iterator over a block and its ancestry. fn ancestry_iter<'a>(&'a self, start: BlockId) -> Box + 'a>; /// Get the signing chain ID. fn signing_chain_id(&self) -> Option; /// Get environment info for execution at a given block. /// Fails if that block's header is not stored. fn env_info(&self, id: BlockId) -> Option; /// Get a handle to the consensus engine. fn engine(&self) -> &Arc; /// Query whether a block is known. fn is_known(&self, hash: &H256) -> bool; /// Clear the queue. fn clear_queue(&self); /// Flush the queue. fn flush_queue(&self); /// Get queue info. fn queue_info(&self) -> queue::QueueInfo; /// Get the `i`th CHT root. fn cht_root(&self, i: usize) -> Option; /// Get the EIP-86 transition block number. fn eip86_transition(&self) -> u64; /// Get a report of import activity since the last call. fn report(&self) -> ClientReport; } /// An actor listening to light chain events. pub trait LightChainNotify: Send + Sync { /// Notifies about imported headers. fn new_headers(&self, good: &[H256]); } /// Something which can be treated as a `LightChainClient`. pub trait AsLightClient { /// The kind of light client this can be treated as. type Client: LightChainClient; /// Access the underlying light client. fn as_light_client(&self) -> &Self::Client; } impl AsLightClient for T { type Client = Self; fn as_light_client(&self) -> &Self { self } } /// Light client implementation. pub struct Client { queue: HeaderQueue, engine: Arc, chain: HeaderChain, report: RwLock, import_lock: Mutex<()>, db: Arc, listeners: RwLock>>, verify_full: bool, } impl Client { /// Create a new `Client`. pub fn new(config: Config, db: Arc, chain_col: Option, spec: &Spec, io_channel: IoChannel, cache: Arc>) -> Result { let gh = ::rlp::encode(&spec.genesis_header()); Ok(Client { queue: HeaderQueue::new(config.queue, spec.engine.clone(), io_channel, config.check_seal), engine: spec.engine.clone(), chain: HeaderChain::new(db.clone(), chain_col, &gh, cache)?, report: RwLock::new(ClientReport::default()), import_lock: Mutex::new(()), db: db, listeners: RwLock::new(vec![]), verify_full: config.verify_full, }) } /// Adds a new `LightChainNotify` listener. pub fn add_listener(&self, listener: Weak) { self.listeners.write().push(listener); } /// Create a new `Client` backed purely in-memory. /// This will ignore all database options in the configuration. pub fn in_memory(config: Config, spec: &Spec, io_channel: IoChannel, cache: Arc>) -> Self { let db = ::util::kvdb::in_memory(0); Client::new(config, Arc::new(db), None, spec, io_channel, cache).expect("New DB creation infallible; qed") } /// Import a header to the queue for additional verification. pub fn import_header(&self, header: Header) -> Result { self.queue.import(header).map_err(Into::into) } /// Inquire about the status of a given header. pub fn status(&self, hash: &H256) -> BlockStatus { match self.queue.status(hash) { queue::Status::Unknown => self.chain.status(hash), other => other.into(), } } /// Get the chain info. pub fn chain_info(&self) -> BlockChainInfo { let best_hdr = self.chain.best_header(); let best_td = self.chain.best_block().total_difficulty; let first_block = self.chain.first_block(); let genesis_hash = self.chain.genesis_hash(); BlockChainInfo { total_difficulty: best_td, pending_total_difficulty: best_td + self.queue.total_difficulty(), genesis_hash: genesis_hash, best_block_hash: best_hdr.hash(), best_block_number: best_hdr.number(), best_block_timestamp: best_hdr.timestamp(), ancient_block_hash: if first_block.is_some() { Some(genesis_hash) } else { None }, ancient_block_number: if first_block.is_some() { Some(0) } else { None }, first_block_hash: first_block.as_ref().map(|first| first.hash), first_block_number: first_block.as_ref().map(|first| first.number), } } /// Get the header queue info. pub fn queue_info(&self) -> queue::QueueInfo { self.queue.queue_info() } /// Attempt to get a block hash by block id. pub fn block_hash(&self, id: BlockId) -> Option { self.chain.block_hash(id) } /// Get a block header by Id. pub fn block_header(&self, id: BlockId) -> Option { self.chain.block_header(id) } /// Get the best block header. pub fn best_block_header(&self) -> encoded::Header { self.chain.best_header() } /// Get a block's chain score. pub fn score(&self, id: BlockId) -> Option { self.chain.score(id) } /// Get an iterator over a block and its ancestry. pub fn ancestry_iter(&self, start: BlockId) -> AncestryIter { self.chain.ancestry_iter(start) } /// Get the signing chain id. pub fn signing_chain_id(&self) -> Option { self.engine.signing_chain_id(&self.latest_env_info()) } /// Flush the header queue. pub fn flush_queue(&self) { self.queue.flush() } /// Get the `i`th CHT root. pub fn cht_root(&self, i: usize) -> Option { self.chain.cht_root(i) } /// Import a set of pre-verified headers from the queue. pub fn import_verified(&self) { const MAX: usize = 256; let _lock = self.import_lock.lock(); let mut bad = Vec::new(); let mut good = Vec::new(); for verified_header in self.queue.drain(MAX) { let (num, hash) = (verified_header.number(), verified_header.hash()); trace!(target: "client", "importing block {}", num); if self.verify_full && !self.check_header(&mut bad, &verified_header) { continue } // TODO: `epoch_end_signal`, `is_epoch_end`. // proofs we get from the network would be _complete_, whereas we need // _incomplete_ signals let mut tx = self.db.transaction(); let pending = match self.chain.insert(&mut tx, verified_header) { Ok(pending) => { good.push(hash); self.report.write().blocks_imported += 1; pending } Err(e) => { debug!(target: "client", "Error importing header {:?}: {}", (num, hash), e); bad.push(hash); continue; } }; self.db.write_buffered(tx); self.chain.apply_pending(pending); } if let Err(e) = self.db.flush() { panic!("Database flush failed: {}. Check disk health and space.", e); } self.queue.mark_as_bad(&bad); self.queue.mark_as_good(&good); self.notify(|listener| listener.new_headers(&good)); } /// Get a report about blocks imported. pub fn report(&self) -> ClientReport { self.report.read().clone() } /// Get blockchain mem usage in bytes. pub fn chain_mem_used(&self) -> usize { use util::HeapSizeOf; self.chain.heap_size_of_children() } /// Get a handle to the verification engine. pub fn engine(&self) -> &Arc { &self.engine } /// Get the latest environment info. pub fn latest_env_info(&self) -> EnvInfo { self.env_info(BlockId::Latest) .expect("Best block header and recent hashes always stored; qed") } /// Get environment info for a given block. pub fn env_info(&self, id: BlockId) -> Option { let header = match self.block_header(id) { Some(hdr) => hdr, None => return None, }; Some(EnvInfo { number: header.number(), author: header.author(), timestamp: header.timestamp(), difficulty: header.difficulty(), last_hashes: self.build_last_hashes(header.parent_hash()), gas_used: Default::default(), gas_limit: header.gas_limit(), }) } fn build_last_hashes(&self, mut parent_hash: H256) -> Arc> { let mut v = Vec::with_capacity(256); for _ in 0..255 { v.push(parent_hash); match self.block_header(BlockId::Hash(parent_hash)) { Some(header) => parent_hash = header.hash(), None => break, } } Arc::new(v) } fn notify(&self, f: F) { for listener in &*self.listeners.read() { if let Some(listener) = listener.upgrade() { f(&*listener) } } } // return false if should skip, true otherwise. may push onto bad if // should skip. fn check_header(&self, bad: &mut Vec, verified_header: &Header) -> bool { let hash = verified_header.hash(); let parent_header = match self.chain.block_header(BlockId::Hash(*verified_header.parent_hash())) { Some(header) => header, None => { trace!(target: "client", "No parent for block ({}, {})", verified_header.number(), hash); return false // skip import of block with missing parent. } }; // Verify Block Family let verify_family_result = self.engine.verify_block_family(&verified_header, &parent_header.decode(), None); if let Err(e) = verify_family_result { warn!(target: "client", "Stage 3 block verification failed for #{} ({})\nError: {:?}", verified_header.number(), verified_header.hash(), e); bad.push(hash); return false; }; // "external" verification. let verify_external_result = self.engine.verify_block_external(&verified_header, None); if let Err(e) = verify_external_result { warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", verified_header.number(), verified_header.hash(), e); bad.push(hash); return false; }; true } } impl LightChainClient for Client { fn chain_info(&self) -> BlockChainInfo { Client::chain_info(self) } fn queue_header(&self, header: Header) -> Result { self.import_header(header) } fn block_hash(&self, id: BlockId) -> Option { Client::block_hash(self, id) } fn block_header(&self, id: BlockId) -> Option { Client::block_header(self, id) } fn best_block_header(&self) -> encoded::Header { Client::best_block_header(self) } fn score(&self, id: BlockId) -> Option { Client::score(self, id) } fn ancestry_iter<'a>(&'a self, start: BlockId) -> Box + 'a> { Box::new(Client::ancestry_iter(self, start)) } fn signing_chain_id(&self) -> Option { Client::signing_chain_id(self) } fn env_info(&self, id: BlockId) -> Option { Client::env_info(self, id) } fn engine(&self) -> &Arc { Client::engine(self) } fn is_known(&self, hash: &H256) -> bool { self.status(hash) == BlockStatus::InChain } fn clear_queue(&self) { self.queue.clear() } fn flush_queue(&self) { Client::flush_queue(self); } fn queue_info(&self) -> queue::QueueInfo { self.queue.queue_info() } fn cht_root(&self, i: usize) -> Option { Client::cht_root(self, i) } fn eip86_transition(&self) -> u64 { self.engine().params().eip86_transition } fn report(&self) -> ClientReport { Client::report(self) } }