// Copyright 2017-2019 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 blockchain backend. Only stores headers and justifications of recent
//! blocks. CHT roots are stored for headers of ancient blocks.
use std::future::Future;
use std::sync::Arc;
use sr_primitives::{Justification, generic::BlockId};
use sr_primitives::traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero};
use header_metadata::{HeaderMetadata, CachedHeaderMetadata};
pub use client_api::{
backend::{
AuxStore, NewBlockState
},
blockchain::{
Backend as BlockchainBackend, BlockStatus, Cache as BlockchainCache,
HeaderBackend as BlockchainHeaderBackend, Info as BlockchainInfo, ProvideCache,
well_known_cache_keys,
},
error::{
Error as ClientError, Result as ClientResult
},
light::{
RemoteBlockchain, LocalOrRemote, Storage
}
};
use crate::cht;
use crate::light::fetcher::{Fetcher, RemoteHeaderRequest};
/// Light client blockchain.
pub struct Blockchain {
storage: S,
}
impl Blockchain {
/// Create new light blockchain backed with given storage.
pub fn new(storage: S) -> Self {
Self {
storage,
}
}
/// Get storage reference.
pub fn storage(&self) -> &S {
&self.storage
}
}
impl BlockchainHeaderBackend for Blockchain where Block: BlockT, S: Storage {
fn header(&self, id: BlockId) -> ClientResult> {
match RemoteBlockchain::header(self, id)? {
LocalOrRemote::Local(header) => Ok(Some(header)),
LocalOrRemote::Remote(_) => Err(ClientError::NotAvailableOnLightClient),
LocalOrRemote::Unknown => Ok(None),
}
}
fn info(&self) -> BlockchainInfo {
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 HeaderMetadata for Blockchain where Block: BlockT, S: Storage {
type Error = ClientError;
fn header_metadata(&self, hash: Block::Hash) -> Result, Self::Error> {
self.storage.header_metadata(hash)
}
fn insert_header_metadata(&self, hash: Block::Hash, metadata: CachedHeaderMetadata) {
self.storage.insert_header_metadata(hash, metadata)
}
fn remove_header_metadata(&self, hash: Block::Hash) {
self.storage.remove_header_metadata(hash)
}
}
impl BlockchainBackend for Blockchain where Block: BlockT, S: Storage {
fn body(&self, _id: BlockId) -> ClientResult>> {
Err(ClientError::NotAvailableOnLightClient)
}
fn justification(&self, _id: BlockId) -> ClientResult> {
Err(ClientError::NotAvailableOnLightClient)
}
fn last_finalized(&self) -> ClientResult {
self.storage.last_finalized()
}
fn cache(&self) -> Option>> {
self.storage.cache()
}
fn leaves(&self) -> ClientResult> {
Err(ClientError::NotAvailableOnLightClient)
}
fn children(&self, _parent_hash: Block::Hash) -> ClientResult> {
Err(ClientError::NotAvailableOnLightClient)
}
}
impl, Block: BlockT> ProvideCache for Blockchain {
fn cache(&self) -> Option>> {
self.storage.cache()
}
}
impl RemoteBlockchain for Blockchain
where
S: Storage,
{
fn header(&self, id: BlockId) -> ClientResult,
>> {
// first, try to read header from local storage
if let Some(local_header) = self.storage.header(id)? {
return Ok(LocalOrRemote::Local(local_header));
}
// we need to know block number to check if it's a part of CHT
let number = match id {
BlockId::Hash(hash) => match self.storage.number(hash)? {
Some(number) => number,
None => return Ok(LocalOrRemote::Unknown),
},
BlockId::Number(number) => number,
};
// if the header is genesis (never pruned), non-canonical, or from future => return
if number.is_zero() || self.storage.status(BlockId::Number(number))? == BlockStatus::Unknown {
return Ok(LocalOrRemote::Unknown);
}
Ok(LocalOrRemote::Remote(RemoteHeaderRequest {
cht_root: self.storage.header_cht_root(cht::size(), number)?,
block: number,
retry_count: None,
}))
}
}
/// Returns future that resolves header either locally, or remotely.
pub fn future_header>(
blockchain: &dyn RemoteBlockchain,
fetcher: &F,
id: BlockId,
) -> impl Future, ClientError>> {
use futures::future::{ready, Either, FutureExt};
match blockchain.header(id) {
Ok(LocalOrRemote::Remote(request)) => Either::Left(
fetcher
.remote_header(request)
.then(|header| ready(header.map(Some)))
),
Ok(LocalOrRemote::Unknown) => Either::Right(ready(Ok(None))),
Ok(LocalOrRemote::Local(local_header)) => Either::Right(ready(Ok(Some(local_header)))),
Err(err) => Either::Right(ready(Err(err))),
}
}
#[cfg(test)]
pub mod tests {
use std::collections::HashMap;
use parking_lot::Mutex;
use test_client::runtime::{Hash, Block, Header};
use client_api::blockchain::Info;
use super::*;
pub type DummyBlockchain = Blockchain;
pub struct DummyStorage {
pub changes_tries_cht_roots: HashMap,
pub aux_store: Mutex, Vec>>,
}
impl DummyStorage {
pub fn new() -> Self {
DummyStorage {
changes_tries_cht_roots: HashMap::new(),
aux_store: Mutex::new(HashMap::new()),
}
}
}
impl BlockchainHeaderBackend for DummyStorage {
fn header(&self, _id: BlockId) -> ClientResult> {
Err(ClientError::Backend("Test error".into()))
}
fn info(&self) -> Info {
panic!("Test error")
}
fn status(&self, _id: BlockId) -> ClientResult {
Err(ClientError::Backend("Test error".into()))
}
fn number(&self, hash: Hash) -> ClientResult>> {
if hash == Default::default() {
Ok(Some(Default::default()))
} else {
Err(ClientError::Backend("Test error".into()))
}
}
fn hash(&self, number: u64) -> ClientResult > {
if number == 0 {
Ok(Some(Default::default()))
} else {
Err(ClientError::Backend("Test error".into()))
}
}
}
impl HeaderMetadata for DummyStorage {
type Error = ClientError;
fn header_metadata(&self, hash: Hash) -> Result, Self::Error> {
self.header(BlockId::hash(hash))?.map(|header| CachedHeaderMetadata::from(&header))
.ok_or(ClientError::UnknownBlock("header not found".to_owned()))
}
fn insert_header_metadata(&self, _hash: Hash, _metadata: CachedHeaderMetadata) {}
fn remove_header_metadata(&self, _hash: Hash) {}
}
impl AuxStore for DummyStorage {
fn insert_aux<
'a,
'b: 'a,
'c: 'a,
I: IntoIterator- ,
D: IntoIterator
- ,
>(&self, insert: I, _delete: D) -> ClientResult<()> {
for (k, v) in insert.into_iter() {
self.aux_store.lock().insert(k.to_vec(), v.to_vec());
}
Ok(())
}
fn get_aux(&self, key: &[u8]) -> ClientResult
>> {
Ok(self.aux_store.lock().get(key).cloned())
}
}
impl Storage for DummyStorage {
fn import_header(
&self,
_header: Header,
_cache: HashMap>,
_state: NewBlockState,
_aux_ops: Vec<(Vec, Option>)>,
) -> ClientResult<()> {
Ok(())
}
fn set_head(&self, _block: BlockId) -> ClientResult<()> {
Err(ClientError::Backend("Test error".into()))
}
fn finalize_header(&self, _block: BlockId) -> ClientResult<()> {
Err(ClientError::Backend("Test error".into()))
}
fn last_finalized(&self) -> ClientResult {
Err(ClientError::Backend("Test error".into()))
}
fn header_cht_root(&self, _cht_size: u64, _block: u64) -> ClientResult {
Err(ClientError::Backend("Test error".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(|| ClientError::Backend(
format!("Test error: CHT for block #{} not found", block)
).into())
}
fn cache(&self) -> Option>> {
None
}
}
}