// Copyright 2017 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 backend. Only stores headers and justifications of blocks. //! Everything else is requested from full nodes on demand. use std::sync::{Arc, Weak}; use futures::{Future, IntoFuture}; use parking_lot::RwLock; use primitives::AuthorityId; use runtime_primitives::{bft::Justification, generic::BlockId}; use runtime_primitives::traits::{Block as BlockT, NumberFor}; use state_machine::{Backend as StateBackend, InMemoryChangesTrieStorage, TrieBackend}; use backend::{Backend as ClientBackend, BlockImportOperation, RemoteBackend, NewBlockState}; use blockchain::HeaderBackend as BlockchainHeaderBackend; use error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult}; use light::blockchain::{Blockchain, Storage as BlockchainStorage}; use light::fetcher::{Fetcher, RemoteReadRequest}; use patricia_trie::NodeCodec; use hashdb::Hasher; use memorydb::MemoryDB; use heapsize::HeapSizeOf; /// Light client backend. pub struct Backend { blockchain: Arc>, } /// Light block (header and justification) import operation. pub struct ImportOperation { header: Option, authorities: Option>, leaf_state: NewBlockState, _phantom: ::std::marker::PhantomData<(S, F)>, } /// On-demand state. pub struct OnDemandState { fetcher: Weak, blockchain: Weak>, block: Block::Hash, cached_header: RwLock>, } impl Backend { /// Create new light backend. pub fn new(blockchain: Arc>) -> Self { Self { blockchain } } /// Get shared blockchain reference. pub fn blockchain(&self) -> &Arc> { &self.blockchain } } impl ClientBackend for Backend where Block: BlockT, S: BlockchainStorage, F: Fetcher, H: Hasher, C: NodeCodec, H::Out: HeapSizeOf, { type BlockImportOperation = ImportOperation; type Blockchain = Blockchain; type State = OnDemandState; type ChangesTrieStorage = InMemoryChangesTrieStorage; fn begin_operation(&self, _block: BlockId) -> ClientResult { Ok(ImportOperation { header: None, authorities: None, leaf_state: NewBlockState::Normal, _phantom: Default::default(), }) } fn commit_operation(&self, operation: Self::BlockImportOperation) -> ClientResult<()> { let header = operation.header.expect("commit is called after set_block_data; set_block_data sets header; qed"); self.blockchain.storage().import_header( header, operation.authorities, operation.leaf_state, ) } fn finalize_block(&self, block: BlockId) -> ClientResult<()> { self.blockchain.storage().finalize_header(block) } fn blockchain(&self) -> &Blockchain { &self.blockchain } fn changes_trie_storage(&self) -> Option<&Self::ChangesTrieStorage> { None } fn state_at(&self, block: BlockId) -> ClientResult { let block_hash = match block { BlockId::Hash(h) => Some(h), BlockId::Number(n) => self.blockchain.hash(n).unwrap_or_default(), }; Ok(OnDemandState { fetcher: self.blockchain.fetcher(), blockchain: Arc::downgrade(&self.blockchain), block: block_hash.ok_or_else(|| ClientErrorKind::UnknownBlock(format!("{}", block)))?, cached_header: RwLock::new(None), }) } fn revert(&self, _n: NumberFor) -> ClientResult> { unimplemented!() } } impl RemoteBackend for Backend where Block: BlockT, S: BlockchainStorage, F: Fetcher, H: Hasher, H::Out: HeapSizeOf, C: NodeCodec, {} impl BlockImportOperation for ImportOperation where Block: BlockT, F: Fetcher, S: BlockchainStorage, H: Hasher, C: NodeCodec, { type State = OnDemandState; fn state(&self) -> ClientResult> { // None means 'locally-stateless' backend Ok(None) } fn set_block_data( &mut self, header: Block::Header, _body: Option>, _justification: Option>, state: NewBlockState, ) -> ClientResult<()> { self.leaf_state = state; self.header = Some(header); Ok(()) } fn update_authorities(&mut self, authorities: Vec) { self.authorities = Some(authorities); } fn update_storage(&mut self, _update: >::Transaction) -> ClientResult<()> { // we're not storing anything locally => ignore changes Ok(()) } fn update_changes_trie(&mut self, _update: MemoryDB) -> ClientResult<()> { // we're not storing anything locally => ignore changes Ok(()) } fn reset_storage, Vec)>>(&mut self, _iter: I) -> ClientResult<()> { // we're not storing anything locally => ignore changes Ok(()) } } impl StateBackend for OnDemandState where Block: BlockT, S: BlockchainStorage, F: Fetcher, H: Hasher, C: NodeCodec, { type Error = ClientError; type Transaction = (); type TrieBackendStorage = MemoryDB; fn storage(&self, key: &[u8]) -> ClientResult>> { let mut header = self.cached_header.read().clone(); if header.is_none() { let cached_header = self.blockchain.upgrade() .ok_or_else(|| ClientErrorKind::UnknownBlock(format!("{}", self.block)).into()) .and_then(|blockchain| blockchain.expect_header(BlockId::Hash(self.block)))?; header = Some(cached_header.clone()); *self.cached_header.write() = Some(cached_header); } self.fetcher.upgrade().ok_or(ClientErrorKind::NotAvailableOnLightClient)? .remote_read(RemoteReadRequest { block: self.block, header: header.expect("if block above guarantees that header is_some(); qed"), key: key.to_vec(), retry_count: None, }) .into_future().wait() } fn for_keys_with_prefix(&self, _prefix: &[u8], _action: A) { // whole state is not available on light node } fn storage_root(&self, _delta: I) -> (H::Out, Self::Transaction) where I: IntoIterator, Option>)> { (H::Out::default(), ()) } fn pairs(&self) -> Vec<(Vec, Vec)> { // whole state is not available on light node Vec::new() } fn try_into_trie_backend(self) -> Option> { None } }