Kill the light client, CHTs and change tries. (#10080)

* Remove light client, change tries and CHTs

* Update tests

* fmt

* Restore changes_root

* Fixed benches

* Cargo fmt

* fmt

* fmt
This commit is contained in:
Arkadiy Paronyan
2021-11-12 14:15:01 +01:00
committed by GitHub
parent 112b7dac47
commit 4cbbf0cf43
141 changed files with 532 additions and 17807 deletions
+3 -106
View File
@@ -20,27 +20,22 @@
use crate::{
blockchain::{well_known_cache_keys, Backend as BlockchainBackend},
light::RemoteBlockchain,
UsageInfo,
};
use parking_lot::RwLock;
use sp_blockchain;
use sp_consensus::BlockOrigin;
use sp_core::{offchain::OffchainStorage, ChangesTrieConfigurationRange};
use sp_core::offchain::OffchainStorage;
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, HashFor, NumberFor},
Justification, Justifications, Storage,
};
use sp_state_machine::{
ChangesTrieState, ChangesTrieStorage as StateChangesTrieStorage, ChangesTrieTransaction,
ChildStorageCollection, IndexOperation, OffchainChangesCollection, StorageCollection,
};
use sp_storage::{ChildInfo, PrefixedStorageKey, StorageData, StorageKey};
use std::{
collections::{HashMap, HashSet},
sync::Arc,
};
use sp_storage::{ChildInfo, StorageData, StorageKey};
use std::collections::{HashMap, HashSet};
pub use sp_state_machine::{Backend as StateBackend, KeyValueStates};
use std::marker::PhantomData;
@@ -191,12 +186,6 @@ pub trait BlockImportOperation<Block: BlockT> {
Ok(())
}
/// Inject changes trie data into the database.
fn update_changes_trie(
&mut self,
update: ChangesTrieTransaction<HashFor<Block>, NumberFor<Block>>,
) -> sp_blockchain::Result<()>;
/// Insert auxiliary keys.
///
/// Values are `None` if should be deleted.
@@ -418,28 +407,6 @@ pub trait StorageProvider<Block: BlockT, B: Backend<Block>> {
child_info: &ChildInfo,
key: &StorageKey,
) -> sp_blockchain::Result<Option<Block::Hash>>;
/// Get longest range within [first; last] that is possible to use in `key_changes`
/// and `key_changes_proof` calls.
/// Range could be shortened from the beginning if some changes tries have been pruned.
/// Returns Ok(None) if changes tries are not supported.
fn max_key_changes_range(
&self,
first: NumberFor<Block>,
last: BlockId<Block>,
) -> sp_blockchain::Result<Option<(NumberFor<Block>, BlockId<Block>)>>;
/// Get pairs of (block, extrinsic) where key has been changed at given blocks range.
/// Works only for runtimes that are supporting changes tries.
///
/// Changes are returned in descending order (i.e. last block comes first).
fn key_changes(
&self,
first: NumberFor<Block>,
last: BlockId<Block>,
storage_key: Option<&PrefixedStorageKey>,
key: &StorageKey,
) -> sp_blockchain::Result<Vec<(NumberFor<Block>, u32)>>;
}
/// Client backend.
@@ -504,9 +471,6 @@ pub trait Backend<Block: BlockT>: AuxStore + Send + Sync {
/// Returns current usage statistics.
fn usage_info(&self) -> Option<UsageInfo>;
/// Returns reference to changes trie storage.
fn changes_trie_storage(&self) -> Option<&dyn PrunableStateChangesTrieStorage<Block>>;
/// Returns a handle to offchain storage.
fn offchain_storage(&self) -> Option<Self::OffchainStorage>;
@@ -561,72 +525,5 @@ pub trait Backend<Block: BlockT>: AuxStore + Send + Sync {
fn get_import_lock(&self) -> &RwLock<()>;
}
/// Changes trie storage that supports pruning.
pub trait PrunableStateChangesTrieStorage<Block: BlockT>:
StateChangesTrieStorage<HashFor<Block>, NumberFor<Block>>
{
/// Get reference to StateChangesTrieStorage.
fn storage(&self) -> &dyn StateChangesTrieStorage<HashFor<Block>, NumberFor<Block>>;
/// Get configuration at given block.
fn configuration_at(
&self,
at: &BlockId<Block>,
) -> sp_blockchain::Result<ChangesTrieConfigurationRange<NumberFor<Block>, Block::Hash>>;
/// Get end block (inclusive) of oldest pruned max-level (or skewed) digest trie blocks range.
/// It is guaranteed that we have no any changes tries before (and including) this block.
/// It is guaranteed that all existing changes tries after this block are not yet pruned (if
/// created).
fn oldest_pruned_digest_range_end(&self) -> NumberFor<Block>;
}
/// Mark for all Backend implementations, that are making use of state data, stored locally.
pub trait LocalBackend<Block: BlockT>: Backend<Block> {}
/// Mark for all Backend implementations, that are fetching required state data from remote nodes.
pub trait RemoteBackend<Block: BlockT>: Backend<Block> {
/// Returns true if the state for given block is available locally.
fn is_local_state_available(&self, block: &BlockId<Block>) -> bool;
/// Returns reference to blockchain backend.
///
/// Returned backend either resolves blockchain data
/// locally, or prepares request to fetch that data from remote node.
fn remote_blockchain(&self) -> Arc<dyn RemoteBlockchain<Block>>;
}
/// Return changes tries state at given block.
pub fn changes_tries_state_at_block<'a, Block: BlockT>(
block: &BlockId<Block>,
maybe_storage: Option<&'a dyn PrunableStateChangesTrieStorage<Block>>,
) -> sp_blockchain::Result<Option<ChangesTrieState<'a, HashFor<Block>, NumberFor<Block>>>> {
let storage = match maybe_storage {
Some(storage) => storage,
None => return Ok(None),
};
let config_range = storage.configuration_at(block)?;
match config_range.config {
Some(config) =>
Ok(Some(ChangesTrieState::new(config, config_range.zero.0, storage.storage()))),
None => Ok(None),
}
}
/// Provide CHT roots. These are stored on a light client and generated dynamically on a full
/// client.
pub trait ProvideChtRoots<Block: BlockT> {
/// Get headers CHT root for given block. Returns None if the block is not a part of any CHT.
fn header_cht_root(
&self,
cht_size: NumberFor<Block>,
block: NumberFor<Block>,
) -> sp_blockchain::Result<Option<Block::Hash>>;
/// Get changes trie CHT root for given block. Returns None if the block is not a part of any
/// CHT.
fn changes_trie_cht_root(
&self,
cht_size: NumberFor<Block>,
block: NumberFor<Block>,
) -> sp_blockchain::Result<Option<Block::Hash>>;
}
-474
View File
@@ -1,474 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Canonical hash trie definitions and helper functions.
//!
//! Each CHT is a trie mapping block numbers to canonical hash.
//! One is generated for every `SIZE` blocks, allowing us to discard those blocks in
//! favor of the trie root. When the "ancient" blocks need to be accessed, we simply
//! request an inclusion proof of a specific block number against the trie with the
//! root hash. A correct proof implies that the claimed block is identical to the one
//! we discarded.
use codec::Encode;
use hash_db;
use sp_trie;
use sp_core::{convert_hash, H256};
use sp_runtime::traits::{AtLeast32Bit, Header as HeaderT, One, Zero};
use sp_state_machine::{
prove_read_on_trie_backend, read_proof_check, read_proof_check_on_proving_backend,
Backend as StateBackend, InMemoryBackend, MemoryDB, StorageProof, TrieBackend,
};
use sp_blockchain::{Error as ClientError, Result as ClientResult};
/// The size of each CHT. This value is passed to every CHT-related function from
/// production code. Other values are passed from tests.
const SIZE: u32 = 2048;
/// Gets default CHT size.
pub fn size<N: From<u32>>() -> N {
SIZE.into()
}
/// Returns Some(cht_number) if CHT is need to be built when the block with given number is
/// canonized.
pub fn is_build_required<N>(cht_size: N, block_num: N) -> Option<N>
where
N: Clone + AtLeast32Bit,
{
let block_cht_num = block_to_cht_number(cht_size.clone(), block_num.clone())?;
let two = N::one() + N::one();
if block_cht_num < two {
return None
}
let cht_start = start_number(cht_size, block_cht_num.clone());
if cht_start != block_num {
return None
}
Some(block_cht_num - two)
}
/// Returns Some(max_cht_number) if CHT has ever been built given maximal canonical block number.
pub fn max_cht_number<N>(cht_size: N, max_canonical_block: N) -> Option<N>
where
N: Clone + AtLeast32Bit,
{
let max_cht_number = block_to_cht_number(cht_size, max_canonical_block)?;
let two = N::one() + N::one();
if max_cht_number < two {
return None
}
Some(max_cht_number - two)
}
/// Compute a CHT root from an iterator of block hashes. Fails if shorter than
/// SIZE items. The items are assumed to proceed sequentially from `start_number(cht_num)`.
/// Discards the trie's nodes.
pub fn compute_root<Header, Hasher, I>(
cht_size: Header::Number,
cht_num: Header::Number,
hashes: I,
) -> ClientResult<Hasher::Out>
where
Header: HeaderT,
Hasher: hash_db::Hasher,
Hasher::Out: Ord,
I: IntoIterator<Item = ClientResult<Option<Header::Hash>>>,
{
use sp_trie::TrieConfiguration;
Ok(sp_trie::trie_types::Layout::<Hasher>::trie_root(build_pairs::<Header, I>(
cht_size, cht_num, hashes,
)?))
}
/// Build CHT-based header proof.
pub fn build_proof<Header, Hasher, BlocksI, HashesI>(
cht_size: Header::Number,
cht_num: Header::Number,
blocks: BlocksI,
hashes: HashesI,
) -> ClientResult<StorageProof>
where
Header: HeaderT,
Hasher: hash_db::Hasher,
Hasher::Out: Ord + codec::Codec,
BlocksI: IntoIterator<Item = Header::Number>,
HashesI: IntoIterator<Item = ClientResult<Option<Header::Hash>>>,
{
let transaction = build_pairs::<Header, _>(cht_size, cht_num, hashes)?
.into_iter()
.map(|(k, v)| (k, Some(v)))
.collect::<Vec<_>>();
let storage = InMemoryBackend::<Hasher>::default().update(vec![(None, transaction)]);
let trie_storage = storage
.as_trie_backend()
.expect("InMemoryState::as_trie_backend always returns Some; qed");
prove_read_on_trie_backend(
trie_storage,
blocks.into_iter().map(|number| encode_cht_key(number)),
)
.map_err(ClientError::from_state)
}
/// Check CHT-based header proof.
pub fn check_proof<Header, Hasher>(
local_root: Header::Hash,
local_number: Header::Number,
remote_hash: Header::Hash,
remote_proof: StorageProof,
) -> ClientResult<()>
where
Header: HeaderT,
Hasher: hash_db::Hasher,
Hasher::Out: Ord + codec::Codec,
{
do_check_proof::<Header, Hasher, _>(
local_root,
local_number,
remote_hash,
move |local_root, local_cht_key| {
read_proof_check::<Hasher, _>(
local_root,
remote_proof,
::std::iter::once(local_cht_key),
)
.map(|mut map| map.remove(local_cht_key).expect("checked proof of local_cht_key; qed"))
.map_err(ClientError::from_state)
},
)
}
/// Check CHT-based header proof on pre-created proving backend.
pub fn check_proof_on_proving_backend<Header, Hasher>(
local_root: Header::Hash,
local_number: Header::Number,
remote_hash: Header::Hash,
proving_backend: &TrieBackend<MemoryDB<Hasher>, Hasher>,
) -> ClientResult<()>
where
Header: HeaderT,
Hasher: hash_db::Hasher,
Hasher::Out: Ord + codec::Codec,
{
do_check_proof::<Header, Hasher, _>(
local_root,
local_number,
remote_hash,
|_, local_cht_key| {
read_proof_check_on_proving_backend::<Hasher>(proving_backend, local_cht_key)
.map_err(ClientError::from_state)
},
)
}
/// Check CHT-based header proof using passed checker function.
fn do_check_proof<Header, Hasher, F>(
local_root: Header::Hash,
local_number: Header::Number,
remote_hash: Header::Hash,
checker: F,
) -> ClientResult<()>
where
Header: HeaderT,
Hasher: hash_db::Hasher,
Hasher::Out: Ord,
F: FnOnce(Hasher::Out, &[u8]) -> ClientResult<Option<Vec<u8>>>,
{
let root: Hasher::Out = convert_hash(&local_root);
let local_cht_key = encode_cht_key(local_number);
let local_cht_value = checker(root, &local_cht_key)?;
let local_cht_value = local_cht_value.ok_or_else(|| ClientError::InvalidCHTProof)?;
let local_hash =
decode_cht_value(&local_cht_value).ok_or_else(|| ClientError::InvalidCHTProof)?;
match &local_hash[..] == remote_hash.as_ref() {
true => Ok(()),
false => Err(ClientError::InvalidCHTProof.into()),
}
}
/// Group ordered blocks by CHT number and call functor with blocks of each group.
pub fn for_each_cht_group<Header, I, F, P>(
cht_size: Header::Number,
blocks: I,
mut functor: F,
mut functor_param: P,
) -> ClientResult<()>
where
Header: HeaderT,
I: IntoIterator<Item = Header::Number>,
F: FnMut(P, Header::Number, Vec<Header::Number>) -> ClientResult<P>,
{
let mut current_cht_num = None;
let mut current_cht_blocks = Vec::new();
for block in blocks {
let new_cht_num = block_to_cht_number(cht_size, block).ok_or_else(|| {
ClientError::Backend(format!("Cannot compute CHT root for the block #{}", block))
})?;
let advance_to_next_cht = current_cht_num.is_some() && current_cht_num != Some(new_cht_num);
if advance_to_next_cht {
let current_cht_num = current_cht_num.expect(
"advance_to_next_cht is true;
it is true only when current_cht_num is Some; qed",
);
assert!(
new_cht_num > current_cht_num,
"for_each_cht_group only supports ordered iterators"
);
functor_param =
functor(functor_param, current_cht_num, std::mem::take(&mut current_cht_blocks))?;
}
current_cht_blocks.push(block);
current_cht_num = Some(new_cht_num);
}
if let Some(current_cht_num) = current_cht_num {
functor(functor_param, current_cht_num, std::mem::take(&mut current_cht_blocks))?;
}
Ok(())
}
/// Build pairs for computing CHT.
fn build_pairs<Header, I>(
cht_size: Header::Number,
cht_num: Header::Number,
hashes: I,
) -> ClientResult<Vec<(Vec<u8>, Vec<u8>)>>
where
Header: HeaderT,
I: IntoIterator<Item = ClientResult<Option<Header::Hash>>>,
{
let start_num = start_number(cht_size, cht_num);
let mut pairs = Vec::new();
let mut hash_index = Header::Number::zero();
for hash in hashes.into_iter() {
let hash =
hash?.ok_or_else(|| ClientError::from(ClientError::MissingHashRequiredForCHT))?;
pairs.push((encode_cht_key(start_num + hash_index).to_vec(), encode_cht_value(hash)));
hash_index += Header::Number::one();
if hash_index == cht_size {
break
}
}
if hash_index == cht_size {
Ok(pairs)
} else {
Err(ClientError::MissingHashRequiredForCHT)
}
}
/// Get the starting block of a given CHT.
/// CHT 0 includes block 1...SIZE,
/// CHT 1 includes block SIZE + 1 ... 2*SIZE
/// More generally: CHT N includes block (1 + N*SIZE)...((N+1)*SIZE).
/// This is because the genesis hash is assumed to be known
/// and including it would be redundant.
pub fn start_number<N: AtLeast32Bit>(cht_size: N, cht_num: N) -> N {
(cht_num * cht_size) + N::one()
}
/// Get the ending block of a given CHT.
pub fn end_number<N: AtLeast32Bit>(cht_size: N, cht_num: N) -> N {
(cht_num + N::one()) * cht_size
}
/// Convert a block number to a CHT number.
/// Returns `None` for `block_num` == 0, `Some` otherwise.
pub fn block_to_cht_number<N: AtLeast32Bit>(cht_size: N, block_num: N) -> Option<N> {
if block_num == N::zero() {
None
} else {
Some((block_num - N::one()) / cht_size)
}
}
/// Convert header number into CHT key.
pub fn encode_cht_key<N: Encode>(number: N) -> Vec<u8> {
number.encode()
}
/// Convert header hash into CHT value.
fn encode_cht_value<Hash: AsRef<[u8]>>(hash: Hash) -> Vec<u8> {
hash.as_ref().to_vec()
}
/// Convert CHT value into block header hash.
pub fn decode_cht_value(value: &[u8]) -> Option<H256> {
match value.len() {
32 => Some(H256::from_slice(&value[0..32])),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use sp_runtime::{generic, traits::BlakeTwo256};
type Header = generic::Header<u64, BlakeTwo256>;
#[test]
fn is_build_required_works() {
assert_eq!(is_build_required(SIZE, 0u32.into()), None);
assert_eq!(is_build_required(SIZE, 1u32.into()), None);
assert_eq!(is_build_required(SIZE, SIZE), None);
assert_eq!(is_build_required(SIZE, SIZE + 1), None);
assert_eq!(is_build_required(SIZE, 2 * SIZE), None);
assert_eq!(is_build_required(SIZE, 2 * SIZE + 1), Some(0));
assert_eq!(is_build_required(SIZE, 2 * SIZE + 2), None);
assert_eq!(is_build_required(SIZE, 3 * SIZE), None);
assert_eq!(is_build_required(SIZE, 3 * SIZE + 1), Some(1));
assert_eq!(is_build_required(SIZE, 3 * SIZE + 2), None);
}
#[test]
fn max_cht_number_works() {
assert_eq!(max_cht_number(SIZE, 0u32.into()), None);
assert_eq!(max_cht_number(SIZE, 1u32.into()), None);
assert_eq!(max_cht_number(SIZE, SIZE), None);
assert_eq!(max_cht_number(SIZE, SIZE + 1), None);
assert_eq!(max_cht_number(SIZE, 2 * SIZE), None);
assert_eq!(max_cht_number(SIZE, 2 * SIZE + 1), Some(0));
assert_eq!(max_cht_number(SIZE, 2 * SIZE + 2), Some(0));
assert_eq!(max_cht_number(SIZE, 3 * SIZE), Some(0));
assert_eq!(max_cht_number(SIZE, 3 * SIZE + 1), Some(1));
assert_eq!(max_cht_number(SIZE, 3 * SIZE + 2), Some(1));
}
#[test]
fn start_number_works() {
assert_eq!(start_number(SIZE, 0u32), 1u32);
assert_eq!(start_number(SIZE, 1u32), SIZE + 1);
assert_eq!(start_number(SIZE, 2u32), SIZE + SIZE + 1);
}
#[test]
fn end_number_works() {
assert_eq!(end_number(SIZE, 0u32), SIZE);
assert_eq!(end_number(SIZE, 1u32), SIZE + SIZE);
assert_eq!(end_number(SIZE, 2u32), SIZE + SIZE + SIZE);
}
#[test]
fn build_pairs_fails_when_no_enough_blocks() {
assert!(build_pairs::<Header, _>(
SIZE as _,
0,
::std::iter::repeat_with(|| Ok(Some(H256::from_low_u64_be(1)))).take(SIZE as usize / 2)
)
.is_err());
}
#[test]
fn build_pairs_fails_when_missing_block() {
assert!(build_pairs::<Header, _>(
SIZE as _,
0,
::std::iter::repeat_with(|| Ok(Some(H256::from_low_u64_be(1))))
.take(SIZE as usize / 2)
.chain(::std::iter::once(Ok(None)))
.chain(
::std::iter::repeat_with(|| Ok(Some(H256::from_low_u64_be(2))))
.take(SIZE as usize / 2 - 1)
)
)
.is_err());
}
#[test]
fn compute_root_works() {
assert!(compute_root::<Header, BlakeTwo256, _>(
SIZE as _,
42,
::std::iter::repeat_with(|| Ok(Some(H256::from_low_u64_be(1)))).take(SIZE as usize)
)
.is_ok());
}
#[test]
#[should_panic]
fn build_proof_panics_when_querying_wrong_block() {
assert!(build_proof::<Header, BlakeTwo256, _, _>(
SIZE as _,
0,
vec![(SIZE * 1000) as u64],
::std::iter::repeat_with(|| Ok(Some(H256::from_low_u64_be(1)))).take(SIZE as usize)
)
.is_err());
}
#[test]
fn build_proof_works() {
assert!(build_proof::<Header, BlakeTwo256, _, _>(
SIZE as _,
0,
vec![(SIZE / 2) as u64],
::std::iter::repeat_with(|| Ok(Some(H256::from_low_u64_be(1)))).take(SIZE as usize)
)
.is_ok());
}
#[test]
#[should_panic]
fn for_each_cht_group_panics() {
let cht_size = SIZE as u64;
let _ = for_each_cht_group::<Header, _, _, _>(
cht_size,
vec![cht_size * 5, cht_size * 2],
|_, _, _| Ok(()),
(),
);
}
#[test]
fn for_each_cht_group_works() {
let cht_size = SIZE as u64;
let _ = for_each_cht_group::<Header, _, _, _>(
cht_size,
vec![
cht_size * 2 + 1,
cht_size * 2 + 2,
cht_size * 2 + 5,
cht_size * 4 + 1,
cht_size * 4 + 7,
cht_size * 6 + 1,
],
|_, cht_num, blocks| {
match cht_num {
2 => assert_eq!(
blocks,
vec![cht_size * 2 + 1, cht_size * 2 + 2, cht_size * 2 + 5]
),
4 => assert_eq!(blocks, vec![cht_size * 4 + 1, cht_size * 4 + 7]),
6 => assert_eq!(blocks, vec![cht_size * 6 + 1]),
_ => unreachable!(),
}
Ok(())
},
(),
);
}
}
+4 -119
View File
@@ -29,8 +29,8 @@ use sp_runtime::{
Justification, Justifications, Storage,
};
use sp_state_machine::{
Backend as StateBackend, ChangesTrieTransaction, ChildStorageCollection, InMemoryBackend,
IndexOperation, StorageCollection,
Backend as StateBackend, ChildStorageCollection, InMemoryBackend, IndexOperation,
StorageCollection,
};
use std::{
collections::{HashMap, HashSet},
@@ -39,10 +39,10 @@ use std::{
};
use crate::{
backend::{self, NewBlockState, ProvideChtRoots},
backend::{self, NewBlockState},
blockchain::{self, well_known_cache_keys::Id as CacheKeyId, BlockStatus, HeaderBackend},
leaves::LeafSet,
light, UsageInfo,
UsageInfo,
};
struct PendingBlock<B: BlockT> {
@@ -109,7 +109,6 @@ struct BlockchainStorage<Block: BlockT> {
finalized_number: NumberFor<Block>,
genesis_hash: Block::Hash,
header_cht_roots: HashMap<NumberFor<Block>, Block::Hash>,
changes_trie_cht_roots: HashMap<NumberFor<Block>, Block::Hash>,
leaves: LeafSet<Block::Hash, NumberFor<Block>>,
aux: HashMap<Vec<u8>, Vec<u8>>,
}
@@ -152,7 +151,6 @@ impl<Block: BlockT> Blockchain<Block> {
finalized_number: Zero::zero(),
genesis_hash: Default::default(),
header_cht_roots: HashMap::new(),
changes_trie_cht_roots: HashMap::new(),
leaves: LeafSet::new(),
aux: HashMap::new(),
}));
@@ -442,10 +440,6 @@ impl<Block: BlockT> blockchain::Backend<Block> for Blockchain<Block> {
Ok(self.storage.read().finalized_hash.clone())
}
fn cache(&self) -> Option<Arc<dyn blockchain::Cache<Block>>> {
None
}
fn leaves(&self) -> sp_blockchain::Result<Vec<Block::Hash>> {
Ok(self.storage.read().leaves.hashes())
}
@@ -466,12 +460,6 @@ impl<Block: BlockT> blockchain::Backend<Block> for Blockchain<Block> {
}
}
impl<Block: BlockT> blockchain::ProvideCache<Block> for Blockchain<Block> {
fn cache(&self) -> Option<Arc<dyn blockchain::Cache<Block>>> {
None
}
}
impl<Block: BlockT> backend::AuxStore for Blockchain<Block> {
fn insert_aux<
'a,
@@ -499,82 +487,6 @@ impl<Block: BlockT> backend::AuxStore for Blockchain<Block> {
}
}
impl<Block: BlockT> light::Storage<Block> for Blockchain<Block>
where
Block::Hash: From<[u8; 32]>,
{
fn import_header(
&self,
header: Block::Header,
_cache: HashMap<CacheKeyId, Vec<u8>>,
state: NewBlockState,
aux_ops: Vec<(Vec<u8>, Option<Vec<u8>>)>,
) -> sp_blockchain::Result<()> {
let hash = header.hash();
self.insert(hash, header, None, None, state)?;
self.write_aux(aux_ops);
Ok(())
}
fn set_head(&self, id: BlockId<Block>) -> sp_blockchain::Result<()> {
Blockchain::set_head(self, id)
}
fn last_finalized(&self) -> sp_blockchain::Result<Block::Hash> {
Ok(self.storage.read().finalized_hash.clone())
}
fn finalize_header(&self, id: BlockId<Block>) -> sp_blockchain::Result<()> {
Blockchain::finalize_header(self, id, None)
}
fn cache(&self) -> Option<Arc<dyn blockchain::Cache<Block>>> {
None
}
fn usage_info(&self) -> Option<UsageInfo> {
None
}
}
impl<Block: BlockT> ProvideChtRoots<Block> for Blockchain<Block> {
fn header_cht_root(
&self,
_cht_size: NumberFor<Block>,
block: NumberFor<Block>,
) -> sp_blockchain::Result<Option<Block::Hash>> {
self.storage
.read()
.header_cht_roots
.get(&block)
.cloned()
.ok_or_else(|| {
sp_blockchain::Error::Backend(format!("Header CHT for block {} not exists", block))
})
.map(Some)
}
fn changes_trie_cht_root(
&self,
_cht_size: NumberFor<Block>,
block: NumberFor<Block>,
) -> sp_blockchain::Result<Option<Block::Hash>> {
self.storage
.read()
.changes_trie_cht_roots
.get(&block)
.cloned()
.ok_or_else(|| {
sp_blockchain::Error::Backend(format!(
"Changes trie CHT for block {} not exists",
block
))
})
.map(Some)
}
}
/// In-memory operation.
pub struct BlockImportOperation<Block: BlockT> {
pending_block: Option<PendingBlock<Block>>,
@@ -650,13 +562,6 @@ where
Ok(())
}
fn update_changes_trie(
&mut self,
_update: ChangesTrieTransaction<HashFor<Block>, NumberFor<Block>>,
) -> sp_blockchain::Result<()> {
Ok(())
}
fn set_genesis_state(
&mut self,
storage: Storage,
@@ -846,10 +751,6 @@ where
None
}
fn changes_trie_storage(&self) -> Option<&dyn backend::PrunableStateChangesTrieStorage<Block>> {
None
}
fn offchain_storage(&self) -> Option<Self::OffchainStorage> {
None
}
@@ -885,22 +786,6 @@ where
impl<Block: BlockT> backend::LocalBackend<Block> for Backend<Block> where Block::Hash: Ord {}
impl<Block: BlockT> backend::RemoteBackend<Block> for Backend<Block>
where
Block::Hash: Ord,
{
fn is_local_state_available(&self, block: &BlockId<Block>) -> bool {
self.blockchain
.expect_block_number_from_id(block)
.map(|num| num.is_zero())
.unwrap_or(false)
}
fn remote_blockchain(&self) -> Arc<dyn light::RemoteBlockchain<Block>> {
unimplemented!()
}
}
/// Check that genesis storage is valid.
pub fn check_genesis_storage(storage: &Storage) -> sp_blockchain::Result<()> {
if storage.top.iter().any(|(k, _)| well_known_keys::is_child_storage_key(k)) {
-3
View File
@@ -21,19 +21,16 @@
pub mod backend;
pub mod call_executor;
pub mod cht;
pub mod client;
pub mod execution_extensions;
pub mod in_mem;
pub mod leaves;
pub mod light;
pub mod notifications;
pub mod proof_provider;
pub use backend::*;
pub use call_executor::*;
pub use client::*;
pub use light::*;
pub use notifications::*;
pub use proof_provider::*;
pub use sp_blockchain as blockchain;
-372
View File
@@ -1,372 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Substrate light client interfaces
use std::{
collections::{BTreeMap, HashMap},
future::Future,
sync::Arc,
};
use crate::{
backend::{AuxStore, NewBlockState},
ProvideChtRoots, UsageInfo,
};
use sp_blockchain::{
well_known_cache_keys, Cache as BlockchainCache, Error as ClientError, HeaderBackend,
HeaderMetadata, Result as ClientResult,
};
use sp_core::{storage::PrefixedStorageKey, ChangesTrieConfigurationRange};
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, Header as HeaderT, NumberFor},
};
use sp_state_machine::StorageProof;
/// Remote call request.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct RemoteCallRequest<Header: HeaderT> {
/// Call at state of given block.
pub block: Header::Hash,
/// Header of block at which call is performed.
pub header: Header,
/// Method to call.
pub method: String,
/// Call data.
pub call_data: Vec<u8>,
/// Number of times to retry request. None means that default RETRY_COUNT is used.
pub retry_count: Option<usize>,
}
/// Remote canonical header request.
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct RemoteHeaderRequest<Header: HeaderT> {
/// The root of CHT this block is included in.
pub cht_root: Header::Hash,
/// Number of the header to query.
pub block: Header::Number,
/// Number of times to retry request. None means that default RETRY_COUNT is used.
pub retry_count: Option<usize>,
}
/// Remote storage read request.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct RemoteReadRequest<Header: HeaderT> {
/// Read at state of given block.
pub block: Header::Hash,
/// Header of block at which read is performed.
pub header: Header,
/// Storage key to read.
pub keys: Vec<Vec<u8>>,
/// Number of times to retry request. None means that default RETRY_COUNT is used.
pub retry_count: Option<usize>,
}
/// Remote storage read child request.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct RemoteReadChildRequest<Header: HeaderT> {
/// Read at state of given block.
pub block: Header::Hash,
/// Header of block at which read is performed.
pub header: Header,
/// Storage key for child.
pub storage_key: PrefixedStorageKey,
/// Child storage key to read.
pub keys: Vec<Vec<u8>>,
/// Number of times to retry request. None means that default RETRY_COUNT is used.
pub retry_count: Option<usize>,
}
/// Remote key changes read request.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RemoteChangesRequest<Header: HeaderT> {
/// All changes trie configurations that are valid within [first_block; last_block].
pub changes_trie_configs: Vec<ChangesTrieConfigurationRange<Header::Number, Header::Hash>>,
/// Query changes from range of blocks, starting (and including) with this hash...
pub first_block: (Header::Number, Header::Hash),
/// ...ending (and including) with this hash. Should come after first_block and
/// be the part of the same fork.
pub last_block: (Header::Number, Header::Hash),
/// Only use digests from blocks up to this hash. Should be last_block OR come
/// after this block and be the part of the same fork.
pub max_block: (Header::Number, Header::Hash),
/// Known changes trie roots for the range of blocks [tries_roots.0..max_block].
/// Proofs for roots of ascendants of tries_roots.0 are provided by the remote node.
pub tries_roots: (Header::Number, Header::Hash, Vec<Header::Hash>),
/// Optional Child Storage key to read.
pub storage_key: Option<PrefixedStorageKey>,
/// Storage key to read.
pub key: Vec<u8>,
/// Number of times to retry request. None means that default RETRY_COUNT is used.
pub retry_count: Option<usize>,
}
/// Key changes read proof.
#[derive(Debug, PartialEq, Eq)]
pub struct ChangesProof<Header: HeaderT> {
/// Max block that has been used in changes query.
pub max_block: Header::Number,
/// All touched nodes of all changes tries.
pub proof: Vec<Vec<u8>>,
/// All changes tries roots that have been touched AND are missing from
/// the requester' node. It is a map of block number => changes trie root.
pub roots: BTreeMap<Header::Number, Header::Hash>,
/// The proofs for all changes tries roots that have been touched AND are
/// missing from the requester' node. It is a map of CHT number => proof.
pub roots_proof: StorageProof,
}
/// Remote block body request
#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)]
pub struct RemoteBodyRequest<Header: HeaderT> {
/// Header of the requested block body
pub header: Header,
/// Number of times to retry request. None means that default RETRY_COUNT is used.
pub retry_count: Option<usize>,
}
/// Light client data fetcher. Implementations of this trait must check if remote data
/// is correct (see FetchedDataChecker) and return already checked data.
pub trait Fetcher<Block: BlockT>: Send + Sync {
/// Remote header future.
type RemoteHeaderResult: Future<Output = Result<Block::Header, ClientError>>
+ Unpin
+ Send
+ 'static;
/// Remote storage read future.
type RemoteReadResult: Future<Output = Result<HashMap<Vec<u8>, Option<Vec<u8>>>, ClientError>>
+ Unpin
+ Send
+ 'static;
/// Remote call result future.
type RemoteCallResult: Future<Output = Result<Vec<u8>, ClientError>> + Unpin + Send + 'static;
/// Remote changes result future.
type RemoteChangesResult: Future<Output = Result<Vec<(NumberFor<Block>, u32)>, ClientError>>
+ Unpin
+ Send
+ 'static;
/// Remote block body result future.
type RemoteBodyResult: Future<Output = Result<Vec<Block::Extrinsic>, ClientError>>
+ Unpin
+ Send
+ 'static;
/// Fetch remote header.
fn remote_header(
&self,
request: RemoteHeaderRequest<Block::Header>,
) -> Self::RemoteHeaderResult;
/// Fetch remote storage value.
fn remote_read(&self, request: RemoteReadRequest<Block::Header>) -> Self::RemoteReadResult;
/// Fetch remote storage child value.
fn remote_read_child(
&self,
request: RemoteReadChildRequest<Block::Header>,
) -> Self::RemoteReadResult;
/// Fetch remote call result.
fn remote_call(&self, request: RemoteCallRequest<Block::Header>) -> Self::RemoteCallResult;
/// Fetch remote changes ((block number, extrinsic index)) where given key has been changed
/// at a given blocks range.
fn remote_changes(
&self,
request: RemoteChangesRequest<Block::Header>,
) -> Self::RemoteChangesResult;
/// Fetch remote block body
fn remote_body(&self, request: RemoteBodyRequest<Block::Header>) -> Self::RemoteBodyResult;
}
/// Light client remote data checker.
///
/// Implementations of this trait should not use any prunable blockchain data
/// except that is passed to its methods.
pub trait FetchChecker<Block: BlockT>: Send + Sync {
/// Check remote header proof.
fn check_header_proof(
&self,
request: &RemoteHeaderRequest<Block::Header>,
header: Option<Block::Header>,
remote_proof: StorageProof,
) -> ClientResult<Block::Header>;
/// Check remote storage read proof.
fn check_read_proof(
&self,
request: &RemoteReadRequest<Block::Header>,
remote_proof: StorageProof,
) -> ClientResult<HashMap<Vec<u8>, Option<Vec<u8>>>>;
/// Check remote storage read proof.
fn check_read_child_proof(
&self,
request: &RemoteReadChildRequest<Block::Header>,
remote_proof: StorageProof,
) -> ClientResult<HashMap<Vec<u8>, Option<Vec<u8>>>>;
/// Check remote method execution proof.
fn check_execution_proof(
&self,
request: &RemoteCallRequest<Block::Header>,
remote_proof: StorageProof,
) -> ClientResult<Vec<u8>>;
/// Check remote changes query proof.
fn check_changes_proof(
&self,
request: &RemoteChangesRequest<Block::Header>,
proof: ChangesProof<Block::Header>,
) -> ClientResult<Vec<(NumberFor<Block>, u32)>>;
/// Check remote body proof.
fn check_body_proof(
&self,
request: &RemoteBodyRequest<Block::Header>,
body: Vec<Block::Extrinsic>,
) -> ClientResult<Vec<Block::Extrinsic>>;
}
/// Light client blockchain storage.
pub trait Storage<Block: BlockT>:
AuxStore
+ HeaderBackend<Block>
+ HeaderMetadata<Block, Error = ClientError>
+ ProvideChtRoots<Block>
{
/// Store new header. Should refuse to revert any finalized blocks.
///
/// Takes new authorities, the leaf state of the new block, and
/// any auxiliary storage updates to place in the same operation.
fn import_header(
&self,
header: Block::Header,
cache: HashMap<well_known_cache_keys::Id, Vec<u8>>,
state: NewBlockState,
aux_ops: Vec<(Vec<u8>, Option<Vec<u8>>)>,
) -> ClientResult<()>;
/// Set an existing block as new best block.
fn set_head(&self, block: BlockId<Block>) -> ClientResult<()>;
/// Mark historic header as finalized.
fn finalize_header(&self, block: BlockId<Block>) -> ClientResult<()>;
/// Get last finalized header.
fn last_finalized(&self) -> ClientResult<Block::Hash>;
/// Get storage cache.
fn cache(&self) -> Option<Arc<dyn BlockchainCache<Block>>>;
/// Get storage usage statistics.
fn usage_info(&self) -> Option<UsageInfo>;
}
/// Remote header.
#[derive(Debug)]
pub enum LocalOrRemote<Data, Request> {
/// When data is available locally, it is returned.
Local(Data),
/// When data is unavailable locally, the request to fetch it from remote node is returned.
Remote(Request),
/// When data is unknown.
Unknown,
}
/// Futures-based blockchain backend that either resolves blockchain data
/// locally, or fetches required data from remote node.
pub trait RemoteBlockchain<Block: BlockT>: Send + Sync {
/// Get block header.
fn header(
&self,
id: BlockId<Block>,
) -> ClientResult<LocalOrRemote<Block::Header, RemoteHeaderRequest<Block::Header>>>;
}
/// Returns future that resolves header either locally, or remotely.
pub fn future_header<Block: BlockT, F: Fetcher<Block>>(
blockchain: &dyn RemoteBlockchain<Block>,
fetcher: &F,
id: BlockId<Block>,
) -> impl Future<Output = Result<Option<Block::Header>, 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 super::*;
use futures::future::Ready;
use parking_lot::Mutex;
use sp_blockchain::Error as ClientError;
use sp_test_primitives::{Block, Extrinsic, Header};
#[derive(Debug, thiserror::Error)]
#[error("Not implemented on test node")]
struct MockError;
impl Into<ClientError> for MockError {
fn into(self) -> ClientError {
ClientError::Application(Box::new(self))
}
}
pub type OkCallFetcher = Mutex<Vec<u8>>;
fn not_implemented_in_tests<T>() -> Ready<Result<T, ClientError>> {
futures::future::ready(Err(MockError.into()))
}
impl Fetcher<Block> for OkCallFetcher {
type RemoteHeaderResult = Ready<Result<Header, ClientError>>;
type RemoteReadResult = Ready<Result<HashMap<Vec<u8>, Option<Vec<u8>>>, ClientError>>;
type RemoteCallResult = Ready<Result<Vec<u8>, ClientError>>;
type RemoteChangesResult = Ready<Result<Vec<(NumberFor<Block>, u32)>, ClientError>>;
type RemoteBodyResult = Ready<Result<Vec<Extrinsic>, ClientError>>;
fn remote_header(&self, _request: RemoteHeaderRequest<Header>) -> Self::RemoteHeaderResult {
not_implemented_in_tests()
}
fn remote_read(&self, _request: RemoteReadRequest<Header>) -> Self::RemoteReadResult {
not_implemented_in_tests()
}
fn remote_read_child(
&self,
_request: RemoteReadChildRequest<Header>,
) -> Self::RemoteReadResult {
not_implemented_in_tests()
}
fn remote_call(&self, _request: RemoteCallRequest<Header>) -> Self::RemoteCallResult {
futures::future::ready(Ok((*self.lock()).clone()))
}
fn remote_changes(
&self,
_request: RemoteChangesRequest<Header>,
) -> Self::RemoteChangesResult {
not_implemented_in_tests()
}
fn remote_body(&self, _request: RemoteBodyRequest<Header>) -> Self::RemoteBodyResult {
not_implemented_in_tests()
}
}
}
+2 -23
View File
@@ -17,10 +17,10 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Proof utilities
use crate::{ChangesProof, CompactProof, StorageProof};
use crate::{CompactProof, StorageProof};
use sp_runtime::{generic::BlockId, traits::Block as BlockT};
use sp_state_machine::{KeyValueStates, KeyValueStorageLevel};
use sp_storage::{ChildInfo, PrefixedStorageKey, StorageKey};
use sp_storage::ChildInfo;
/// Interface for providing block proving utilities.
pub trait ProofProvider<Block: BlockT> {
@@ -50,27 +50,6 @@ pub trait ProofProvider<Block: BlockT> {
method: &str,
call_data: &[u8],
) -> sp_blockchain::Result<(Vec<u8>, StorageProof)>;
/// Reads given header and generates CHT-based header proof.
fn header_proof(
&self,
id: &BlockId<Block>,
) -> sp_blockchain::Result<(Block::Header, StorageProof)>;
/// Get proof for computation of (block, extrinsic) pairs where key has been changed at given
/// blocks range. `min` is the hash of the first block, which changes trie root is known to the
/// requester - when we're using changes tries from ascendants of this block, we should provide
/// proofs for changes tries roots `max` is the hash of the last block known to the requester -
/// we can't use changes tries from descendants of this block.
/// Works only for runtimes that are supporting changes tries.
fn key_changes_proof(
&self,
first: Block::Hash,
last: Block::Hash,
min: Block::Hash,
max: Block::Hash,
storage_key: Option<&PrefixedStorageKey>,
key: &StorageKey,
) -> sp_blockchain::Result<ChangesProof<Block::Header>>;
/// Given a `BlockId` iterate over all storage values starting at `start_keys`.
/// Last `start_keys` element contains last accessed key value.