// Copyright 2017-2018 Parity Technologies (UK) Ltd. // This file is part of Substrate. // Substrate 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. // Substrate 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 Substrate. If not, see . //! Light client blockchin backend. Only stores headers and justifications of recent //! blocks. CHT roots are stored for headers of ancient blocks. use std::sync::Weak; use futures::{Future, IntoFuture}; use parking_lot::Mutex; use primitives::AuthorityId; use runtime_primitives::{Justification, generic::BlockId}; use runtime_primitives::traits::{Block as BlockT, Header as HeaderT,NumberFor, Zero}; use backend::NewBlockState; use blockchain::{Backend as BlockchainBackend, BlockStatus, Cache as BlockchainCache, HeaderBackend as BlockchainHeaderBackend, Info as BlockchainInfo}; use cht; use error::{ErrorKind as ClientErrorKind, Result as ClientResult}; use light::fetcher::{Fetcher, RemoteHeaderRequest}; /// Light client blockchain storage. pub trait Storage: BlockchainHeaderBackend { /// Store new header. Should refuse to revert any finalized blocks. fn import_header( &self, header: Block::Header, authorities: Option>, state: NewBlockState, ) -> ClientResult<()>; /// Mark historic header as finalized. fn finalize_header(&self, block: BlockId) -> ClientResult<()>; /// Get last finalized header. fn last_finalized(&self) -> ClientResult; /// Get headers CHT root for given block. Fails if the block is not pruned (not a part of any CHT). fn header_cht_root(&self, cht_size: u64, block: NumberFor) -> ClientResult; /// Get changes trie CHT root for given block. Fails if the block is not pruned (not a part of any CHT). fn changes_trie_cht_root(&self, cht_size: u64, block: NumberFor) -> ClientResult; /// Get storage cache. fn cache(&self) -> Option<&BlockchainCache>; } /// Light client blockchain. pub struct Blockchain { fetcher: Mutex>, storage: S, } impl Blockchain { /// Create new light blockchain backed with given storage. pub fn new(storage: S) -> Self { Self { fetcher: Mutex::new(Default::default()), storage, } } /// Sets fetcher reference. pub fn set_fetcher(&self, fetcher: Weak) { *self.fetcher.lock() = fetcher; } /// Get fetcher weak reference. pub fn fetcher(&self) -> Weak { self.fetcher.lock().clone() } /// Get storage reference. pub fn storage(&self) -> &S { &self.storage } } impl BlockchainHeaderBackend for Blockchain where Block: BlockT, S: Storage, F: Fetcher { fn header(&self, id: BlockId) -> ClientResult> { match self.storage.header(id)? { Some(header) => Ok(Some(header)), None => { let number = match id { BlockId::Hash(hash) => match self.storage.number(hash)? { Some(number) => number, None => return Ok(None), }, BlockId::Number(number) => number, }; // if the header is from future or genesis (we never prune genesis) => return if number.is_zero() || self.storage.status(BlockId::Number(number))? != BlockStatus::InChain { return Ok(None); } self.fetcher().upgrade().ok_or(ClientErrorKind::NotAvailableOnLightClient)? .remote_header(RemoteHeaderRequest { cht_root: self.storage.header_cht_root(cht::SIZE, number)?, block: number, retry_count: None, }) .into_future().wait() .map(Some) } } } fn info(&self) -> ClientResult> { self.storage.info() } fn status(&self, id: BlockId) -> ClientResult { self.storage.status(id) } fn number(&self, hash: Block::Hash) -> ClientResult>> { self.storage.number(hash) } fn hash(&self, number: <::Header as HeaderT>::Number) -> ClientResult> { self.storage.hash(number) } } impl BlockchainBackend for Blockchain where Block: BlockT, S: Storage, F: Fetcher { fn body(&self, _id: BlockId) -> ClientResult>> { // TODO [light]: fetch from remote node Ok(None) } fn justification(&self, _id: BlockId) -> ClientResult> { Ok(None) } fn last_finalized(&self) -> ClientResult { self.storage.last_finalized() } fn cache(&self) -> Option<&BlockchainCache> { self.storage.cache() } fn leaves(&self) -> ClientResult> { unimplemented!() } } #[cfg(test)] pub mod tests { use std::collections::HashMap; use test_client::runtime::{Hash, Block, Header}; use blockchain::Info; use light::fetcher::tests::OkCallFetcher; use super::*; pub type DummyBlockchain = Blockchain; pub struct DummyStorage { pub changes_tries_cht_roots: HashMap, } impl DummyStorage { pub fn new() -> Self { DummyStorage { changes_tries_cht_roots: HashMap::new(), } } } impl BlockchainHeaderBackend for DummyStorage { fn header(&self, _id: BlockId) -> ClientResult> { Err(ClientErrorKind::Backend("Test error".into()).into()) } fn info(&self) -> ClientResult> { Err(ClientErrorKind::Backend("Test error".into()).into()) } fn status(&self, _id: BlockId) -> ClientResult { Err(ClientErrorKind::Backend("Test error".into()).into()) } fn number(&self, _hash: Hash) -> ClientResult>> { Err(ClientErrorKind::Backend("Test error".into()).into()) } fn hash(&self, _number: u64) -> ClientResult> { Err(ClientErrorKind::Backend("Test error".into()).into()) } } impl Storage for DummyStorage { fn import_header( &self, _header: Header, _authorities: Option>, _state: NewBlockState, ) -> ClientResult<()> { Err(ClientErrorKind::Backend("Test error".into()).into()) } fn finalize_header(&self, _block: BlockId) -> ClientResult<()> { Err(ClientErrorKind::Backend("Test error".into()).into()) } fn last_finalized(&self) -> ClientResult { Err(ClientErrorKind::Backend("Test error".into()).into()) } fn header_cht_root(&self, _cht_size: u64, _block: u64) -> ClientResult { Err(ClientErrorKind::Backend("Test error".into()).into()) } fn changes_trie_cht_root(&self, cht_size: u64, block: u64) -> ClientResult { cht::block_to_cht_number(cht_size, block) .and_then(|cht_num| self.changes_tries_cht_roots.get(&cht_num)) .cloned() .ok_or_else(|| ClientErrorKind::Backend( format!("Test error: CHT for block #{} not found", block) ).into()) } fn cache(&self) -> Option<&BlockchainCache> { None } } }