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.
@@ -41,7 +41,8 @@ use sp_core::traits::SpawnNamed;
use sp_inherents::InherentData;
use sp_runtime::{
generic::BlockId,
traits::{BlakeTwo256, Block as BlockT, DigestFor, Hash as HashT, Header as HeaderT},
traits::{BlakeTwo256, Block as BlockT, Hash as HashT, Header as HeaderT},
Digest,
};
use std::{marker::PhantomData, pin::Pin, sync::Arc, time};
@@ -261,7 +262,7 @@ where
fn propose(
self,
inherent_data: InherentData,
inherent_digests: DigestFor<Block>,
inherent_digests: Digest,
max_duration: time::Duration,
block_size_limit: Option<usize>,
) -> Self::Proposal {
@@ -310,7 +311,7 @@ where
async fn propose_with(
self,
inherent_data: InherentData,
inherent_digests: DigestFor<Block>,
inherent_digests: Digest,
deadline: time::Instant,
block_size_limit: Option<usize>,
) -> Result<Proposal<Block, backend::TransactionFor<B, Block>, PR::Proof>, sp_blockchain::Error>
@@ -690,13 +691,8 @@ mod tests {
api.execute_block(&block_id, proposal.block).unwrap();
let state = backend.state_at(block_id).unwrap();
let changes_trie_state =
backend::changes_tries_state_at_block(&block_id, backend.changes_trie_storage())
.unwrap();
let storage_changes = api
.into_storage_changes(&state, changes_trie_state.as_ref(), genesis_hash)
.unwrap();
let storage_changes = api.into_storage_changes(&state, genesis_hash).unwrap();
assert_eq!(
proposal.storage_changes.transaction_storage_root,
+6 -9
View File
@@ -35,7 +35,8 @@ use sp_blockchain::{ApplyExtrinsicFailed, Error};
use sp_core::ExecutionContext;
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, DigestFor, Hash, HashFor, Header as HeaderT, NumberFor, One},
traits::{Block as BlockT, Hash, HashFor, Header as HeaderT, NumberFor, One},
Digest,
};
pub use sp_block_builder::BlockBuilder as BlockBuilderApi;
@@ -119,14 +120,14 @@ where
fn new_block_at<R: Into<RecordProof>>(
&self,
parent: &BlockId<Block>,
inherent_digests: DigestFor<Block>,
inherent_digests: Digest,
record_proof: R,
) -> sp_blockchain::Result<BlockBuilder<Block, RA, B>>;
/// Create a new block, built on the head of the chain.
fn new_block(
&self,
inherent_digests: DigestFor<Block>,
inherent_digests: Digest,
) -> sp_blockchain::Result<BlockBuilder<Block, RA, B>>;
}
@@ -159,7 +160,7 @@ where
parent_hash: Block::Hash,
parent_number: NumberFor<Block>,
record_proof: RecordProof,
inherent_digests: DigestFor<Block>,
inherent_digests: Digest,
backend: &'a B,
) -> Result<Self, Error> {
let header = <<Block as BlockT>::Header as HeaderT>::new(
@@ -237,15 +238,11 @@ where
let proof = self.api.extract_proof();
let state = self.backend.state_at(self.block_id)?;
let changes_trie_state = backend::changes_tries_state_at_block(
&self.block_id,
self.backend.changes_trie_storage(),
)?;
let parent_hash = self.parent_hash;
let storage_changes = self
.api
.into_storage_changes(&state, changes_trie_state.as_ref(), parent_hash)
.into_storage_changes(&state, parent_hash)
.map_err(|e| sp_blockchain::Error::StorageChanges(e))?;
Ok(BuiltBlock {
@@ -33,7 +33,7 @@ use sp_api::{ApiExt, ProvideRuntimeApi};
use sp_block_builder::BlockBuilder as BlockBuilderApi;
use sp_blockchain::{
well_known_cache_keys::{self, Id as CacheKeyId},
HeaderBackend, ProvideCache,
HeaderBackend,
};
use sp_consensus::{CanAuthorWith, Error as ConsensusError};
use sp_consensus_aura::{
@@ -45,7 +45,8 @@ use sp_core::{crypto::Pair, ExecutionContext};
use sp_inherents::{CreateInherentDataProviders, InherentDataProvider as _};
use sp_runtime::{
generic::{BlockId, OpaqueDigestItemId},
traits::{Block as BlockT, DigestItemFor, Header},
traits::{Block as BlockT, Header},
DigestItem,
};
use std::{fmt::Debug, hash::Hash, marker::PhantomData, sync::Arc};
@@ -61,9 +62,8 @@ fn check_header<C, B: BlockT, P: Pair>(
hash: B::Hash,
authorities: &[AuthorityId<P>],
check_for_equivocation: CheckForEquivocation,
) -> Result<CheckedHeader<B::Header, (Slot, DigestItemFor<B>)>, Error<B>>
) -> Result<CheckedHeader<B::Header, (Slot, DigestItem)>, Error<B>>
where
DigestItemFor<B>: CompatibleDigestItem<P::Signature>,
P::Signature: Codec,
C: sc_client_api::backend::AuxStore,
P::Public: Encode + Decode + PartialEq + Clone,
@@ -189,14 +189,8 @@ where
#[async_trait::async_trait]
impl<B: BlockT, C, P, CAW, CIDP> Verifier<B> for AuraVerifier<C, P, CAW, CIDP>
where
C: ProvideRuntimeApi<B>
+ Send
+ Sync
+ sc_client_api::backend::AuxStore
+ ProvideCache<B>
+ BlockOf,
C: ProvideRuntimeApi<B> + Send + Sync + sc_client_api::backend::AuxStore + BlockOf,
C::Api: BlockBuilderApi<B> + AuraApi<B, AuthorityId<P>> + ApiExt<B>,
DigestItemFor<B>: CompatibleDigestItem<P::Signature>,
P: Pair + Send + Sync + 'static,
P::Public: Send + Sync + Hash + Eq + Clone + Decode + Encode + Debug + 'static,
P::Signature: Encode + Decode,
@@ -385,7 +379,6 @@ where
C: 'static
+ ProvideRuntimeApi<Block>
+ BlockOf
+ ProvideCache<Block>
+ Send
+ Sync
+ AuxStore
@@ -395,7 +388,6 @@ where
+ Send
+ Sync
+ 'static,
DigestItemFor<Block>: CompatibleDigestItem<P::Signature>,
P: Pair + Send + Sync + 'static,
P::Public: Clone + Eq + Send + Sync + Hash + Debug + Encode + Decode,
P::Signature: Encode + Decode,
+35 -39
View File
@@ -52,7 +52,7 @@ use sc_consensus_slots::{
use sc_telemetry::TelemetryHandle;
use sp_api::ProvideRuntimeApi;
use sp_application_crypto::{AppKey, AppPublic};
use sp_blockchain::{HeaderBackend, ProvideCache, Result as CResult};
use sp_blockchain::{HeaderBackend, Result as CResult};
use sp_consensus::{
BlockOrigin, CanAuthorWith, Environment, Error as ConsensusError, Proposer, SelectChain,
};
@@ -62,7 +62,8 @@ use sp_inherents::CreateInherentDataProviders;
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, DigestItemFor, Header, Member, NumberFor, Zero},
traits::{Block as BlockT, Header, Member, NumberFor, Zero},
DigestItem,
};
mod import_queue;
@@ -178,7 +179,7 @@ where
P::Public: AppPublic + Hash + Member + Encode + Decode,
P::Signature: TryFrom<Vec<u8>> + Hash + Member + Encode + Decode,
B: BlockT,
C: ProvideRuntimeApi<B> + BlockOf + ProvideCache<B> + AuxStore + HeaderBackend<B> + Send + Sync,
C: ProvideRuntimeApi<B> + BlockOf + AuxStore + HeaderBackend<B> + Send + Sync,
C::Api: AuraApi<B, AuthorityId<P>>,
SC: SelectChain<B>,
I: BlockImport<B, Transaction = sp_api::TransactionFor<C, B>> + Send + Sync + 'static,
@@ -267,7 +268,7 @@ pub fn build_aura_worker<P, B, C, PF, I, SO, L, BS, Error>(
) -> impl sc_consensus_slots::SlotWorker<B, <PF::Proposer as Proposer<B>>::Proof>
where
B: BlockT,
C: ProvideRuntimeApi<B> + BlockOf + ProvideCache<B> + AuxStore + HeaderBackend<B> + Send + Sync,
C: ProvideRuntimeApi<B> + BlockOf + AuxStore + HeaderBackend<B> + Send + Sync,
C::Api: AuraApi<B, AuthorityId<P>>,
PF: Environment<B, Error = Error> + Send + Sync + 'static,
PF::Proposer: Proposer<B, Error = Error, Transaction = sp_api::TransactionFor<C, B>>,
@@ -316,7 +317,7 @@ impl<B, C, E, I, P, Error, SO, L, BS> sc_consensus_slots::SimpleSlotWorker<B>
for AuraWorker<C, E, I, P, SO, L, BS>
where
B: BlockT,
C: ProvideRuntimeApi<B> + BlockOf + ProvideCache<B> + HeaderBackend<B> + Sync,
C: ProvideRuntimeApi<B> + BlockOf + HeaderBackend<B> + Sync,
C::Api: AuraApi<B, AuthorityId<P>>,
E: Environment<B, Error = Error> + Send + Sync,
E::Proposer: Proposer<B, Error = Error, Transaction = sp_api::TransactionFor<C, B>>,
@@ -377,12 +378,8 @@ where
})
}
fn pre_digest_data(
&self,
slot: Slot,
_claim: &Self::Claim,
) -> Vec<sp_runtime::DigestItem<B::Hash>> {
vec![<DigestItemFor<B> as CompatibleDigestItem<P::Signature>>::aura_pre_digest(slot)]
fn pre_digest_data(&self, slot: Slot, _claim: &Self::Claim) -> Vec<sp_runtime::DigestItem> {
vec![<DigestItem as CompatibleDigestItem<P::Signature>>::aura_pre_digest(slot)]
}
fn block_import_params(
@@ -426,7 +423,7 @@ where
.map_err(|_| sp_consensus::Error::InvalidSignature(signature, public))?;
let signature_digest_item =
<DigestItemFor<B> as CompatibleDigestItem<P::Signature>>::aura_seal(signature);
<DigestItem as CompatibleDigestItem<P::Signature>>::aura_seal(signature);
let mut import_block = BlockImportParams::new(BlockOrigin::Own, header);
import_block.post_digests.push(signature_digest_item);
@@ -545,7 +542,7 @@ fn authorities<A, B, C>(client: &C, at: &BlockId<B>) -> Result<Vec<A>, Consensus
where
A: Codec + Debug,
B: BlockT,
C: ProvideRuntimeApi<B> + BlockOf + ProvideCache<B>,
C: ProvideRuntimeApi<B> + BlockOf,
C::Api: AuraApi<B, A>,
{
client
@@ -574,7 +571,10 @@ mod tests {
use sp_consensus_aura::sr25519::AuthorityPair;
use sp_inherents::InherentData;
use sp_keyring::sr25519::Keyring;
use sp_runtime::traits::{Block as BlockT, DigestFor, Header as _};
use sp_runtime::{
traits::{Block as BlockT, Header as _},
Digest,
};
use sp_timestamp::InherentDataProvider as TimestampInherentDataProvider;
use std::{
task::Poll,
@@ -611,7 +611,7 @@ mod tests {
fn propose(
self,
_: InherentData,
digests: DigestFor<TestBlock>,
digests: Digest,
_: Duration,
_: Option<usize>,
) -> Self::Proposal {
@@ -661,29 +661,25 @@ mod tests {
_cfg: &ProtocolConfig,
_peer_data: &(),
) -> Self::Verifier {
match client {
PeersClient::Full(client, _) => {
let slot_duration = slot_duration(&*client).expect("slot duration available");
let client = client.as_client();
let slot_duration = slot_duration(&*client).expect("slot duration available");
assert_eq!(slot_duration.slot_duration().as_millis() as u64, SLOT_DURATION);
import_queue::AuraVerifier::new(
client,
Box::new(|_, _| async {
let timestamp = TimestampInherentDataProvider::from_system_time();
let slot = InherentDataProvider::from_timestamp_and_duration(
*timestamp,
Duration::from_secs(6),
);
assert_eq!(slot_duration.slot_duration().as_millis() as u64, SLOT_DURATION);
import_queue::AuraVerifier::new(
client,
Box::new(|_, _| async {
let timestamp = TimestampInherentDataProvider::from_system_time();
let slot = InherentDataProvider::from_timestamp_and_duration(
*timestamp,
Duration::from_secs(6),
);
Ok((timestamp, slot))
}),
AlwaysCanAuthor,
CheckForEquivocation::Yes,
None,
)
},
PeersClient::Light(_, _) => unreachable!("No (yet) tests for light client + Aura"),
}
Ok((timestamp, slot))
}),
AlwaysCanAuthor,
CheckForEquivocation::Yes,
None,
)
}
fn make_block_import(
@@ -724,7 +720,7 @@ mod tests {
for (peer_id, key) in peers {
let mut net = net.lock();
let peer = net.peer(*peer_id);
let client = peer.client().as_full().expect("full clients are created").clone();
let client = peer.client().as_client();
let select_chain = peer.select_chain().expect("full client has a select chain");
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
let keystore = Arc::new(
@@ -823,7 +819,7 @@ mod tests {
let mut net = net.lock();
let peer = net.peer(3);
let client = peer.client().as_full().expect("full clients are created").clone();
let client = peer.client().as_client();
let environ = DummyFactory(client.clone());
let worker = AuraWorker {
@@ -875,7 +871,7 @@ mod tests {
let mut net = net.lock();
let peer = net.peer(3);
let client = peer.client().as_full().expect("full clients are created").clone();
let client = peer.client().as_client();
let environ = DummyFactory(client.clone());
let mut worker = AuraWorker {
+10 -31
View File
@@ -103,9 +103,7 @@ use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_TRACE}
use sp_api::{ApiExt, NumberFor, ProvideRuntimeApi};
use sp_application_crypto::AppKey;
use sp_block_builder::BlockBuilder as BlockBuilderApi;
use sp_blockchain::{
Error as ClientError, HeaderBackend, HeaderMetadata, ProvideCache, Result as ClientResult,
};
use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata, Result as ClientResult};
use sp_consensus::{
BlockOrigin, CacheKeyId, CanAuthorWith, Environment, Error as ConsensusError, Proposer,
SelectChain, SlotData,
@@ -117,7 +115,8 @@ use sp_inherents::{CreateInherentDataProviders, InherentData, InherentDataProvid
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
use sp_runtime::{
generic::{BlockId, OpaqueDigestItemId},
traits::{Block as BlockT, DigestItemFor, Header, Zero},
traits::{Block as BlockT, Header, Zero},
DigestItem,
};
pub use sc_consensus_slots::SlotProportion;
@@ -465,7 +464,6 @@ pub fn start_babe<B, C, SC, E, I, SO, CIDP, BS, CAW, L, Error>(
where
B: BlockT,
C: ProvideRuntimeApi<B>
+ ProvideCache<B>
+ ProvideUncles<B>
+ BlockchainEvents<B>
+ HeaderBackend<B>
@@ -539,7 +537,6 @@ async fn answer_requests<B: BlockT, C>(
epoch_changes: SharedEpochChanges<B, Epoch>,
) where
C: ProvideRuntimeApi<B>
+ ProvideCache<B>
+ ProvideUncles<B>
+ BlockchainEvents<B>
+ HeaderBackend<B>
@@ -677,10 +674,7 @@ impl<B, C, E, I, Error, SO, L, BS> sc_consensus_slots::SimpleSlotWorker<B>
for BabeSlotWorker<B, C, E, I, SO, L, BS>
where
B: BlockT,
C: ProvideRuntimeApi<B>
+ ProvideCache<B>
+ HeaderBackend<B>
+ HeaderMetadata<B, Error = ClientError>,
C: ProvideRuntimeApi<B> + HeaderBackend<B> + HeaderMetadata<B, Error = ClientError>,
C::Api: BabeApi<B>,
E: Environment<B, Error = Error> + Sync,
E::Proposer: Proposer<B, Error = Error, Transaction = sp_api::TransactionFor<C, B>>,
@@ -774,12 +768,8 @@ where
});
}
fn pre_digest_data(
&self,
_slot: Slot,
claim: &Self::Claim,
) -> Vec<sp_runtime::DigestItem<B::Hash>> {
vec![<DigestItemFor<B> as CompatibleDigestItem>::babe_pre_digest(claim.0.clone())]
fn pre_digest_data(&self, _slot: Slot, claim: &Self::Claim) -> Vec<sp_runtime::DigestItem> {
vec![<DigestItem as CompatibleDigestItem>::babe_pre_digest(claim.0.clone())]
}
fn block_import_params(
@@ -820,8 +810,7 @@ where
.clone()
.try_into()
.map_err(|_| sp_consensus::Error::InvalidSignature(signature, public))?;
let digest_item =
<DigestItemFor<B> as CompatibleDigestItem>::babe_seal(signature.into());
let digest_item = <DigestItem as CompatibleDigestItem>::babe_seal(signature.into());
let mut import_block = BlockImportParams::new(BlockOrigin::Own, header);
import_block.post_digests.push(digest_item);
@@ -921,10 +910,7 @@ pub fn find_pre_digest<B: BlockT>(header: &B::Header) -> Result<PreDigest, Error
/// Extract the BABE epoch change digest from the given header, if it exists.
fn find_next_epoch_digest<B: BlockT>(
header: &B::Header,
) -> Result<Option<NextEpochDescriptor>, Error<B>>
where
DigestItemFor<B>: CompatibleDigestItem,
{
) -> Result<Option<NextEpochDescriptor>, Error<B>> {
let mut epoch_digest: Option<_> = None;
for log in header.digest().logs() {
trace!(target: "babe", "Checking log {:?}, looking for epoch change digest.", log);
@@ -943,10 +929,7 @@ where
/// Extract the BABE config change digest from the given header, if it exists.
fn find_next_config_digest<B: BlockT>(
header: &B::Header,
) -> Result<Option<NextConfigDescriptor>, Error<B>>
where
DigestItemFor<B>: CompatibleDigestItem,
{
) -> Result<Option<NextConfigDescriptor>, Error<B>> {
let mut config_digest: Option<_> = None;
for log in header.digest().logs() {
trace!(target: "babe", "Checking log {:?}, looking for epoch change digest.", log);
@@ -1132,8 +1115,7 @@ where
+ ProvideRuntimeApi<Block>
+ Send
+ Sync
+ AuxStore
+ ProvideCache<Block>,
+ AuxStore,
Client::Api: BlockBuilderApi<Block> + BabeApi<Block>,
SelectChain: sp_consensus::SelectChain<Block>,
CAW: CanAuthorWith<Block> + Send + Sync,
@@ -1332,7 +1314,6 @@ where
+ HeaderMetadata<Block, Error = sp_blockchain::Error>
+ AuxStore
+ ProvideRuntimeApi<Block>
+ ProvideCache<Block>
+ Send
+ Sync,
Client::Api: BabeApi<Block> + ApiExt<Block>,
@@ -1399,7 +1380,6 @@ where
+ HeaderMetadata<Block, Error = sp_blockchain::Error>
+ AuxStore
+ ProvideRuntimeApi<Block>
+ ProvideCache<Block>
+ Send
+ Sync,
Client::Api: BabeApi<Block> + ApiExt<Block>,
@@ -1756,7 +1736,6 @@ where
+ Sync
+ 'static,
Client: ProvideRuntimeApi<Block>
+ ProvideCache<Block>
+ HeaderBackend<Block>
+ HeaderMetadata<Block, Error = sp_blockchain::Error>
+ AuxStore
+11 -11
View File
@@ -43,13 +43,13 @@ use sp_consensus_babe::{
use sp_core::crypto::Pair;
use sp_keystore::{vrf::make_transcript as transcript_from_data, SyncCryptoStore};
use sp_runtime::{
generic::DigestItem,
traits::{Block as BlockT, DigestFor},
generic::{Digest, DigestItem},
traits::Block as BlockT,
};
use sp_timestamp::InherentDataProvider as TimestampInherentDataProvider;
use std::{cell::RefCell, task::Poll, time::Duration};
type Item = DigestItem<Hash>;
type Item = DigestItem;
type Error = sp_blockchain::Error;
@@ -108,7 +108,7 @@ impl Environment<TestBlock> for DummyFactory {
impl DummyProposer {
fn propose_with(
&mut self,
pre_digests: DigestFor<TestBlock>,
pre_digests: Digest,
) -> future::Ready<
Result<
Proposal<
@@ -181,7 +181,7 @@ impl Proposer<TestBlock> for DummyProposer {
fn propose(
mut self,
_: InherentData,
pre_digests: DigestFor<TestBlock>,
pre_digests: Digest,
_: Duration,
_: Option<usize>,
) -> Self::Proposal {
@@ -295,7 +295,7 @@ impl TestNetFactory for BabeTestNet {
Option<BoxJustificationImport<Block>>,
Option<PeerData>,
) {
let client = client.as_full().expect("only full clients are tested");
let client = client.as_client();
let config = Config::get_or_compute(&*client).expect("config available");
let (block_import, link) = crate::block_import(config, client.clone(), client.clone())
@@ -320,7 +320,7 @@ impl TestNetFactory for BabeTestNet {
) -> Self::Verifier {
use substrate_test_runtime_client::DefaultTestClientBuilderExt;
let client = client.as_full().expect("only full clients are used in test");
let client = client.as_client();
trace!(target: "babe", "Creating a verifier");
// ensure block import and verifier are linked correctly.
@@ -395,7 +395,7 @@ fn run_one_test(mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + 'static
for (peer_id, seed) in peers {
let mut net = net.lock();
let peer = net.peer(*peer_id);
let client = peer.client().as_full().expect("Only full clients are used in tests").clone();
let client = peer.client().as_client();
let select_chain = peer.select_chain().expect("Full client has select_chain");
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
@@ -679,7 +679,7 @@ fn importing_block_one_sets_genesis_epoch() {
let peer = net.peer(0);
let data = peer.data.as_ref().expect("babe link set up during initialization");
let client = peer.client().as_full().expect("Only full clients are used in tests").clone();
let client = peer.client().as_client();
let mut proposer_factory = DummyFactory {
client: client.clone(),
@@ -721,7 +721,7 @@ fn importing_epoch_change_block_prunes_tree() {
let peer = net.peer(0);
let data = peer.data.as_ref().expect("babe link set up during initialization");
let client = peer.client().as_full().expect("Only full clients are used in tests").clone();
let client = peer.client().as_client();
let mut block_import = data.block_import.lock().take().expect("import set up during init");
let epoch_changes = data.link.epoch_changes.clone();
@@ -836,7 +836,7 @@ fn verify_slots_are_strictly_increasing() {
let peer = net.peer(0);
let data = peer.data.as_ref().expect("babe link set up during initialization");
let client = peer.client().as_full().expect("Only full clients are used in tests").clone();
let client = peer.client().as_client();
let mut block_import = data.block_import.lock().take().expect("import set up during init");
let mut proposer_factory = DummyFactory {
@@ -32,7 +32,7 @@ use sp_consensus_babe::{
};
use sp_consensus_slots::Slot;
use sp_core::{Pair, Public};
use sp_runtime::traits::{DigestItemFor, Header};
use sp_runtime::{traits::Header, DigestItem};
/// BABE verification parameters
pub(super) struct VerificationParams<'a, B: 'a + BlockT> {
@@ -61,10 +61,7 @@ pub(super) struct VerificationParams<'a, B: 'a + BlockT> {
/// with each having different validation logic.
pub(super) fn check_header<B: BlockT + Sized>(
params: VerificationParams<B>,
) -> Result<CheckedHeader<B::Header, VerifiedHeaderInfo<B>>, Error<B>>
where
DigestItemFor<B>: CompatibleDigestItem,
{
) -> Result<CheckedHeader<B::Header, VerifiedHeaderInfo>, Error<B>> {
let VerificationParams { mut header, pre_digest, slot_now, epoch } = params;
let authorities = &epoch.authorities;
@@ -137,9 +134,9 @@ where
Ok(CheckedHeader::Checked(header, info))
}
pub(super) struct VerifiedHeaderInfo<B: BlockT> {
pub(super) pre_digest: DigestItemFor<B>,
pub(super) seal: DigestItemFor<B>,
pub(super) struct VerifiedHeaderInfo {
pub(super) pre_digest: DigestItem,
pub(super) seal: DigestItem,
pub(super) author: AuthorityId,
}
@@ -20,8 +20,8 @@
use serde::{Deserialize, Serialize};
use sp_runtime::{
traits::{Block as BlockT, DigestItemFor, HashFor, Header as HeaderT, NumberFor},
Justification, Justifications,
traits::{Block as BlockT, HashFor, Header as HeaderT, NumberFor},
DigestItem, Justification, Justifications,
};
use std::{any::Any, borrow::Cow, collections::HashMap, sync::Arc};
@@ -122,7 +122,7 @@ pub struct BlockCheckParams<Block: BlockT> {
/// Precomputed storage.
pub enum StorageChanges<Block: BlockT, Transaction> {
/// Changes coming from block execution.
Changes(sp_state_machine::StorageChanges<Transaction, HashFor<Block>, NumberFor<Block>>),
Changes(sp_state_machine::StorageChanges<Transaction, HashFor<Block>>),
/// Whole new state.
Import(ImportedState<Block>),
}
@@ -175,7 +175,7 @@ pub struct BlockImportParams<Block: BlockT, Transaction> {
pub justifications: Option<Justifications>,
/// Digest items that have been added after the runtime for external
/// work, like a consensus signature.
pub post_digests: Vec<DigestItemFor<Block>>,
pub post_digests: Vec<DigestItem>,
/// The body of the block.
pub body: Option<Vec<Block::Extrinsic>>,
/// Indexed transaction body of the block.
@@ -21,7 +21,7 @@ use super::Error;
use sc_consensus::BlockImportParams;
use sp_inherents::InherentData;
use sp_runtime::traits::{Block as BlockT, DigestFor};
use sp_runtime::{traits::Block as BlockT, Digest};
pub mod babe;
@@ -32,11 +32,7 @@ pub trait ConsensusDataProvider<B: BlockT>: Send + Sync {
type Transaction;
/// Attempt to create a consensus digest.
fn create_digest(
&self,
parent: &B::Header,
inherents: &InherentData,
) -> Result<DigestFor<B>, Error>;
fn create_digest(&self, parent: &B::Header, inherents: &InherentData) -> Result<Digest, Error>;
/// set up the neccessary import params.
fn append_block_import(
@@ -49,7 +49,8 @@ use sp_consensus_slots::Slot;
use sp_inherents::{InherentData, InherentDataProvider, InherentIdentifier};
use sp_runtime::{
generic::{BlockId, Digest},
traits::{Block as BlockT, DigestFor, DigestItemFor, Header, Zero},
traits::{Block as BlockT, Header, Zero},
DigestItem,
};
use sp_timestamp::{InherentType, TimestampInherentData, INHERENT_IDENTIFIER};
@@ -193,11 +194,7 @@ where
{
type Transaction = TransactionFor<C, B>;
fn create_digest(
&self,
parent: &B::Header,
inherents: &InherentData,
) -> Result<DigestFor<B>, Error> {
fn create_digest(&self, parent: &B::Header, inherents: &InherentData) -> Result<Digest, Error> {
let slot = inherents
.babe_inherent_data()?
.ok_or_else(|| Error::StringError("No babe inherent data".into()))?;
@@ -207,7 +204,7 @@ where
let logs = if let Some((predigest, _)) =
authorship::claim_slot(slot, &epoch, &self.keystore)
{
vec![<DigestItemFor<B> as CompatibleDigestItem>::babe_pre_digest(predigest)]
vec![<DigestItem as CompatibleDigestItem>::babe_pre_digest(predigest)]
} else {
// well we couldn't claim a slot because this is an existing chain and we're not in the
// authorities. we need to tell BabeBlockImport that the epoch has changed, and we put
@@ -244,13 +241,13 @@ where
});
vec![
DigestItemFor::<B>::PreRuntime(BABE_ENGINE_ID, predigest.encode()),
DigestItemFor::<B>::Consensus(BABE_ENGINE_ID, next_epoch.encode()),
DigestItem::PreRuntime(BABE_ENGINE_ID, predigest.encode()),
DigestItem::Consensus(BABE_ENGINE_ID, next_epoch.encode()),
]
},
ViableEpochDescriptor::UnimportedGenesis(_) => {
// since this is the genesis, secondary predigest works for now.
vec![DigestItemFor::<B>::PreRuntime(BABE_ENGINE_ID, predigest.encode())]
vec![DigestItem::PreRuntime(BABE_ENGINE_ID, predigest.encode())]
},
}
};
+6 -12
View File
@@ -55,7 +55,7 @@ use sc_consensus::{
};
use sp_api::ProvideRuntimeApi;
use sp_block_builder::BlockBuilder as BlockBuilderApi;
use sp_blockchain::{well_known_cache_keys::Id as CacheKeyId, HeaderBackend, ProvideCache};
use sp_blockchain::{well_known_cache_keys::Id as CacheKeyId, HeaderBackend};
use sp_consensus::{
CanAuthorWith, Environment, Error as ConsensusError, Proposer, SelectChain, SyncOracle,
};
@@ -240,7 +240,7 @@ where
B: BlockT,
I: BlockImport<B, Transaction = sp_api::TransactionFor<C, B>> + Send + Sync,
I::Error: Into<ConsensusError>,
C: ProvideRuntimeApi<B> + Send + Sync + HeaderBackend<B> + AuxStore + ProvideCache<B> + BlockOf,
C: ProvideRuntimeApi<B> + Send + Sync + HeaderBackend<B> + AuxStore + BlockOf,
C::Api: BlockBuilderApi<B>,
Algorithm: PowAlgorithm<B>,
CAW: CanAuthorWith<B>,
@@ -319,7 +319,7 @@ where
I: BlockImport<B, Transaction = sp_api::TransactionFor<C, B>> + Send + Sync,
I::Error: Into<ConsensusError>,
S: SelectChain<B>,
C: ProvideRuntimeApi<B> + Send + Sync + HeaderBackend<B> + AuxStore + ProvideCache<B> + BlockOf,
C: ProvideRuntimeApi<B> + Send + Sync + HeaderBackend<B> + AuxStore + BlockOf,
C::Api: BlockBuilderApi<B>,
Algorithm: PowAlgorithm<B> + Send + Sync,
Algorithm::Difficulty: 'static + Send,
@@ -425,10 +425,7 @@ impl<B: BlockT, Algorithm> PowVerifier<B, Algorithm> {
Self { algorithm, _marker: PhantomData }
}
fn check_header(
&self,
mut header: B::Header,
) -> Result<(B::Header, DigestItem<B::Hash>), Error<B>>
fn check_header(&self, mut header: B::Header) -> Result<(B::Header, DigestItem), Error<B>>
where
Algorithm: PowAlgorithm<B>,
{
@@ -630,7 +627,7 @@ where
},
};
let mut inherent_digest = Digest::<Block::Hash>::default();
let mut inherent_digest = Digest::default();
if let Some(pre_runtime) = &pre_runtime {
inherent_digest.push(DigestItem::PreRuntime(POW_ENGINE_ID, pre_runtime.to_vec()));
}
@@ -702,10 +699,7 @@ fn find_pre_digest<B: BlockT>(header: &B::Header) -> Result<Option<Vec<u8>>, Err
}
/// Fetch PoW seal.
fn fetch_seal<B: BlockT>(
digest: Option<&DigestItem<B::Hash>>,
hash: B::Hash,
) -> Result<Vec<u8>, Error<B>> {
fn fetch_seal<B: BlockT>(digest: Option<&DigestItem>, hash: B::Hash) -> Result<Vec<u8>, Error<B>> {
match digest {
Some(DigestItem::Seal(id, seal)) =>
if id == &POW_ENGINE_ID {
+3 -7
View File
@@ -45,7 +45,7 @@ use sp_consensus_slots::Slot;
use sp_inherents::CreateInherentDataProviders;
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, HashFor, Header as HeaderT, NumberFor},
traits::{Block as BlockT, HashFor, Header as HeaderT},
};
use sp_timestamp::Timestamp;
use std::{fmt::Debug, ops::Deref, time::Duration};
@@ -54,7 +54,7 @@ use std::{fmt::Debug, ops::Deref, time::Duration};
///
/// See [`sp_state_machine::StorageChanges`] for more information.
pub type StorageChanges<Transaction, Block> =
sp_state_machine::StorageChanges<Transaction, HashFor<Block>, NumberFor<Block>>;
sp_state_machine::StorageChanges<Transaction, HashFor<Block>>;
/// The result of [`SlotWorker::on_slot`].
#[derive(Debug, Clone)]
@@ -141,11 +141,7 @@ pub trait SimpleSlotWorker<B: BlockT> {
fn notify_slot(&self, _header: &B::Header, _slot: Slot, _epoch_data: &Self::EpochData) {}
/// Return the pre digest data to include in a block authored with the given claim.
fn pre_digest_data(
&self,
slot: Slot,
claim: &Self::Claim,
) -> Vec<sp_runtime::DigestItem<B::Hash>>;
fn pre_digest_data(&self, slot: Slot, claim: &Self::Claim) -> Vec<sp_runtime::DigestItem>;
/// Returns a function which produces a `BlockImportParams`.
fn block_import_params(
File diff suppressed because it is too large Load Diff
-187
View File
@@ -1,187 +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/>.
//! List-cache storage entries.
use codec::{Decode, Encode};
use sp_blockchain::Result as ClientResult;
use sp_runtime::traits::{Block as BlockT, NumberFor};
use crate::cache::{list_storage::Storage, CacheItemT, ComplexBlockId};
/// Single list-based cache entry.
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Entry<Block: BlockT, T> {
/// first block, when this value became actual.
pub valid_from: ComplexBlockId<Block>,
/// Value stored at this entry.
pub value: T,
}
/// Internal representation of the single list-based cache entry. The entry points to the
/// previous entry in the cache, allowing us to traverse back in time in list-style.
#[derive(Debug, Encode, Decode)]
#[cfg_attr(test, derive(Clone, PartialEq))]
pub struct StorageEntry<Block: BlockT, T: CacheItemT> {
/// None if valid from the beginning.
pub prev_valid_from: Option<ComplexBlockId<Block>>,
/// Value stored at this entry.
pub value: T,
}
impl<Block: BlockT, T: CacheItemT> Entry<Block, T> {
/// Returns Some if the entry should be updated with the new value.
pub fn try_update(&self, value: Option<T>) -> Option<StorageEntry<Block, T>> {
match value {
Some(value) => match self.value == value {
true => None,
false =>
Some(StorageEntry { prev_valid_from: Some(self.valid_from.clone()), value }),
},
None => None,
}
}
/// Wrapper that calls search_before to get range where the given block fits.
pub fn search_best_range_before<S: Storage<Block, T>>(
&self,
storage: &S,
block: NumberFor<Block>,
) -> ClientResult<Option<(ComplexBlockId<Block>, Option<ComplexBlockId<Block>>)>> {
Ok(self
.search_best_before(storage, block)?
.map(|(entry, next)| (entry.valid_from, next)))
}
/// Searches the list, ending with THIS entry for the best entry preceding (or at)
/// given block number.
/// If the entry is found, result is the entry and the block id of next entry (if exists).
/// NOTE that this function does not check that the passed block is actually linked to
/// the blocks it found.
pub fn search_best_before<S: Storage<Block, T>>(
&self,
storage: &S,
block: NumberFor<Block>,
) -> ClientResult<Option<(Entry<Block, T>, Option<ComplexBlockId<Block>>)>> {
// we're looking for the best value
let mut next = None;
let mut current = self.valid_from.clone();
if block >= self.valid_from.number {
let value = self.value.clone();
return Ok(Some((Entry { valid_from: current, value }, next)))
}
// else - travel back in time
loop {
let entry = storage.require_entry(&current)?;
if block >= current.number {
return Ok(Some((Entry { valid_from: current, value: entry.value }, next)))
}
next = Some(current);
current = match entry.prev_valid_from {
Some(prev_valid_from) => prev_valid_from,
None => return Ok(None),
};
}
}
}
impl<Block: BlockT, T: CacheItemT> StorageEntry<Block, T> {
/// Converts storage entry into an entry, valid from given block.
pub fn into_entry(self, valid_from: ComplexBlockId<Block>) -> Entry<Block, T> {
Entry { valid_from, value: self.value }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cache::list_storage::tests::{DummyStorage, FaultyStorage};
use substrate_test_runtime_client::runtime::{Block, H256};
fn test_id(number: u64) -> ComplexBlockId<Block> {
ComplexBlockId::new(H256::from_low_u64_be(number), number)
}
#[test]
fn entry_try_update_works() {
// when trying to update with None value
assert_eq!(Entry::<_, u64> { valid_from: test_id(1), value: 42 }.try_update(None), None);
// when trying to update with the same Some value
assert_eq!(Entry { valid_from: test_id(1), value: 1 }.try_update(Some(1)), None);
// when trying to update with different Some value
assert_eq!(
Entry { valid_from: test_id(1), value: 1 }.try_update(Some(2)),
Some(StorageEntry { prev_valid_from: Some(test_id(1)), value: 2 })
);
}
#[test]
fn entry_search_best_before_fails() {
// when storage returns error
assert!(Entry::<_, u64> { valid_from: test_id(100), value: 42 }
.search_best_before(&FaultyStorage, 50)
.is_err());
}
#[test]
fn entry_search_best_before_works() {
// when block is better than our best block
assert_eq!(
Entry::<_, u64> { valid_from: test_id(100), value: 100 }
.search_best_before(&DummyStorage::new(), 150)
.unwrap(),
Some((Entry::<_, u64> { valid_from: test_id(100), value: 100 }, None))
);
// when block is found between two entries
assert_eq!(
Entry::<_, u64> { valid_from: test_id(100), value: 100 }
.search_best_before(
&DummyStorage::new()
.with_entry(
test_id(100),
StorageEntry { prev_valid_from: Some(test_id(50)), value: 100 }
)
.with_entry(
test_id(50),
StorageEntry { prev_valid_from: Some(test_id(30)), value: 50 }
),
75
)
.unwrap(),
Some((Entry::<_, u64> { valid_from: test_id(50), value: 50 }, Some(test_id(100))))
);
// when block is not found
assert_eq!(
Entry::<_, u64> { valid_from: test_id(100), value: 100 }
.search_best_before(
&DummyStorage::new()
.with_entry(
test_id(100),
StorageEntry { prev_valid_from: Some(test_id(50)), value: 100 }
)
.with_entry(test_id(50), StorageEntry { prev_valid_from: None, value: 50 }),
30
)
.unwrap(),
None
);
}
}
-441
View File
@@ -1,441 +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/>.
//! List-cache storage definition and implementation.
use std::sync::Arc;
use crate::utils::{self, meta_keys};
use codec::{Decode, Encode};
use sp_blockchain::{Error as ClientError, Result as ClientResult};
use sp_database::{Database, Transaction};
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, Header as HeaderT, NumberFor},
};
use crate::{
cache::{
list_cache::{CommitOperation, Fork},
list_entry::{Entry, StorageEntry},
CacheItemT, ComplexBlockId,
},
DbHash,
};
/// Single list-cache metadata.
#[derive(Debug)]
#[cfg_attr(test, derive(Clone, PartialEq))]
pub struct Metadata<Block: BlockT> {
/// Block at which best finalized entry is stored.
pub finalized: Option<ComplexBlockId<Block>>,
/// A set of blocks at which best unfinalized entries are stored.
pub unfinalized: Vec<ComplexBlockId<Block>>,
}
/// Readonly list-cache storage trait.
pub trait Storage<Block: BlockT, T: CacheItemT> {
/// Reads hash of the block at given number.
fn read_id(&self, at: NumberFor<Block>) -> ClientResult<Option<Block::Hash>>;
/// Reads header of the block with given hash.
fn read_header(&self, at: &Block::Hash) -> ClientResult<Option<Block::Header>>;
/// Reads cache metadata: best finalized entry (if some) and the list.
fn read_meta(&self) -> ClientResult<Metadata<Block>>;
/// Reads cache entry from the storage.
fn read_entry(
&self,
at: &ComplexBlockId<Block>,
) -> ClientResult<Option<StorageEntry<Block, T>>>;
/// Reads referenced (and thus existing) cache entry from the storage.
fn require_entry(&self, at: &ComplexBlockId<Block>) -> ClientResult<StorageEntry<Block, T>> {
self.read_entry(at).and_then(|entry| {
entry.ok_or_else(|| {
ClientError::from(ClientError::Backend(format!(
"Referenced cache entry at {:?} is not found",
at
)))
})
})
}
}
/// List-cache storage transaction.
pub trait StorageTransaction<Block: BlockT, T: CacheItemT> {
/// Insert storage entry at given block.
fn insert_storage_entry(&mut self, at: &ComplexBlockId<Block>, entry: &StorageEntry<Block, T>);
/// Delete storage entry at given block.
fn remove_storage_entry(&mut self, at: &ComplexBlockId<Block>);
/// Update metadata of the cache.
fn update_meta(
&mut self,
best_finalized_entry: Option<&Entry<Block, T>>,
unfinalized: &[Fork<Block, T>],
operation: &CommitOperation<Block, T>,
);
}
/// A set of columns used by the DbStorage.
#[derive(Debug)]
pub struct DbColumns {
/// Column holding cache meta.
pub meta: u32,
/// Column holding the mapping of { block number => block hash } for blocks of the best chain.
pub key_lookup: u32,
/// Column holding the mapping of { block hash => block header }.
pub header: u32,
/// Column holding cache entries.
pub cache: u32,
}
/// Database-backed list cache storage.
pub struct DbStorage {
name: Vec<u8>,
meta_key: Vec<u8>,
db: Arc<dyn Database<DbHash>>,
columns: DbColumns,
}
impl DbStorage {
/// Create new database-backed list cache storage.
pub fn new(name: Vec<u8>, db: Arc<dyn Database<DbHash>>, columns: DbColumns) -> Self {
let meta_key = meta::key(&name);
DbStorage { name, meta_key, db, columns }
}
/// Get reference to the database.
pub fn db(&self) -> &Arc<dyn Database<DbHash>> {
&self.db
}
/// Get reference to the database columns.
pub fn columns(&self) -> &DbColumns {
&self.columns
}
/// Encode block id for storing as a key in cache column.
/// We append prefix to the actual encoding to allow several caches
/// store entries in the same column.
pub fn encode_block_id<Block: BlockT>(&self, block: &ComplexBlockId<Block>) -> Vec<u8> {
let mut encoded = self.name.clone();
encoded.extend(block.hash.as_ref());
encoded
}
}
impl<Block: BlockT, T: CacheItemT> Storage<Block, T> for DbStorage {
fn read_id(&self, at: NumberFor<Block>) -> ClientResult<Option<Block::Hash>> {
utils::read_header::<Block>(
&*self.db,
self.columns.key_lookup,
self.columns.header,
BlockId::Number(at),
)
.map(|maybe_header| maybe_header.map(|header| header.hash()))
}
fn read_header(&self, at: &Block::Hash) -> ClientResult<Option<Block::Header>> {
utils::read_header::<Block>(
&*self.db,
self.columns.key_lookup,
self.columns.header,
BlockId::Hash(*at),
)
}
fn read_meta(&self) -> ClientResult<Metadata<Block>> {
match self.db.get(self.columns.meta, &self.meta_key) {
Some(meta) => meta::decode(&*meta),
None => Ok(Metadata { finalized: None, unfinalized: Vec::new() }),
}
}
fn read_entry(
&self,
at: &ComplexBlockId<Block>,
) -> ClientResult<Option<StorageEntry<Block, T>>> {
match self.db.get(self.columns.cache, &self.encode_block_id(at)) {
Some(entry) => StorageEntry::<Block, T>::decode(&mut &entry[..])
.map_err(|_| ClientError::Backend("Failed to decode cache entry".into()))
.map(Some),
None => Ok(None),
}
}
}
/// Database-backed list cache storage transaction.
pub struct DbStorageTransaction<'a> {
storage: &'a DbStorage,
tx: &'a mut Transaction<DbHash>,
}
impl<'a> DbStorageTransaction<'a> {
/// Create new database transaction.
pub fn new(storage: &'a DbStorage, tx: &'a mut Transaction<DbHash>) -> Self {
DbStorageTransaction { storage, tx }
}
}
impl<'a, Block: BlockT, T: CacheItemT> StorageTransaction<Block, T> for DbStorageTransaction<'a> {
fn insert_storage_entry(&mut self, at: &ComplexBlockId<Block>, entry: &StorageEntry<Block, T>) {
self.tx.set_from_vec(
self.storage.columns.cache,
&self.storage.encode_block_id(at),
entry.encode(),
);
}
fn remove_storage_entry(&mut self, at: &ComplexBlockId<Block>) {
self.tx.remove(self.storage.columns.cache, &self.storage.encode_block_id(at));
}
fn update_meta(
&mut self,
best_finalized_entry: Option<&Entry<Block, T>>,
unfinalized: &[Fork<Block, T>],
operation: &CommitOperation<Block, T>,
) {
self.tx.set_from_vec(
self.storage.columns.meta,
&self.storage.meta_key,
meta::encode(best_finalized_entry, unfinalized, operation),
);
}
}
/// Metadata related functions.
mod meta {
use super::*;
/// Convert cache name into cache metadata key.
pub fn key(name: &[u8]) -> Vec<u8> {
let mut key_name = meta_keys::CACHE_META_PREFIX.to_vec();
key_name.extend_from_slice(name);
key_name
}
/// Encode cache metadata 'applying' commit operation before encoding.
pub fn encode<Block: BlockT, T: CacheItemT>(
best_finalized_entry: Option<&Entry<Block, T>>,
unfinalized: &[Fork<Block, T>],
op: &CommitOperation<Block, T>,
) -> Vec<u8> {
let mut finalized = best_finalized_entry.as_ref().map(|entry| &entry.valid_from);
let mut unfinalized =
unfinalized.iter().map(|fork| &fork.head().valid_from).collect::<Vec<_>>();
match op {
CommitOperation::AppendNewBlock(_, _) => (),
CommitOperation::AppendNewEntry(index, ref entry) => {
unfinalized[*index] = &entry.valid_from;
},
CommitOperation::AddNewFork(ref entry) => {
unfinalized.push(&entry.valid_from);
},
CommitOperation::BlockFinalized(_, ref finalizing_entry, ref forks) => {
if let Some(finalizing_entry) = finalizing_entry.as_ref() {
finalized = Some(&finalizing_entry.valid_from);
}
for fork_index in forks.iter().rev() {
unfinalized.remove(*fork_index);
}
},
CommitOperation::BlockReverted(ref forks) => {
for (fork_index, updated_fork) in forks.iter().rev() {
match updated_fork {
Some(updated_fork) =>
unfinalized[*fork_index] = &updated_fork.head().valid_from,
None => {
unfinalized.remove(*fork_index);
},
}
}
},
}
(finalized, unfinalized).encode()
}
/// Decode meta information.
pub fn decode<Block: BlockT>(encoded: &[u8]) -> ClientResult<Metadata<Block>> {
let input = &mut &*encoded;
let finalized: Option<ComplexBlockId<Block>> = Decode::decode(input).map_err(|_| {
ClientError::from(ClientError::Backend("Error decoding cache meta".into()))
})?;
let unfinalized: Vec<ComplexBlockId<Block>> = Decode::decode(input).map_err(|_| {
ClientError::from(ClientError::Backend("Error decoding cache meta".into()))
})?;
Ok(Metadata { finalized, unfinalized })
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use std::collections::{HashMap, HashSet};
pub struct FaultyStorage;
impl<Block: BlockT, T: CacheItemT> Storage<Block, T> for FaultyStorage {
fn read_id(&self, _at: NumberFor<Block>) -> ClientResult<Option<Block::Hash>> {
Err(ClientError::Backend("TestError".into()))
}
fn read_header(&self, _at: &Block::Hash) -> ClientResult<Option<Block::Header>> {
Err(ClientError::Backend("TestError".into()))
}
fn read_meta(&self) -> ClientResult<Metadata<Block>> {
Err(ClientError::Backend("TestError".into()))
}
fn read_entry(
&self,
_at: &ComplexBlockId<Block>,
) -> ClientResult<Option<StorageEntry<Block, T>>> {
Err(ClientError::Backend("TestError".into()))
}
}
pub struct DummyStorage<Block: BlockT, T: CacheItemT> {
meta: Metadata<Block>,
ids: HashMap<NumberFor<Block>, Block::Hash>,
headers: HashMap<Block::Hash, Block::Header>,
entries: HashMap<Block::Hash, StorageEntry<Block, T>>,
}
impl<Block: BlockT, T: CacheItemT> DummyStorage<Block, T> {
pub fn new() -> Self {
DummyStorage {
meta: Metadata { finalized: None, unfinalized: Vec::new() },
ids: HashMap::new(),
headers: HashMap::new(),
entries: HashMap::new(),
}
}
pub fn with_meta(
mut self,
finalized: Option<ComplexBlockId<Block>>,
unfinalized: Vec<ComplexBlockId<Block>>,
) -> Self {
self.meta.finalized = finalized;
self.meta.unfinalized = unfinalized;
self
}
pub fn with_id(mut self, at: NumberFor<Block>, id: Block::Hash) -> Self {
self.ids.insert(at, id);
self
}
pub fn with_header(mut self, header: Block::Header) -> Self {
self.headers.insert(header.hash(), header);
self
}
pub fn with_entry(
mut self,
at: ComplexBlockId<Block>,
entry: StorageEntry<Block, T>,
) -> Self {
self.entries.insert(at.hash, entry);
self
}
}
impl<Block: BlockT, T: CacheItemT> Storage<Block, T> for DummyStorage<Block, T> {
fn read_id(&self, at: NumberFor<Block>) -> ClientResult<Option<Block::Hash>> {
Ok(self.ids.get(&at).cloned())
}
fn read_header(&self, at: &Block::Hash) -> ClientResult<Option<Block::Header>> {
Ok(self.headers.get(&at).cloned())
}
fn read_meta(&self) -> ClientResult<Metadata<Block>> {
Ok(self.meta.clone())
}
fn read_entry(
&self,
at: &ComplexBlockId<Block>,
) -> ClientResult<Option<StorageEntry<Block, T>>> {
Ok(self.entries.get(&at.hash).cloned())
}
}
pub struct DummyTransaction<Block: BlockT> {
updated_meta: Option<Metadata<Block>>,
inserted_entries: HashSet<Block::Hash>,
removed_entries: HashSet<Block::Hash>,
}
impl<Block: BlockT> DummyTransaction<Block> {
pub fn new() -> Self {
DummyTransaction {
updated_meta: None,
inserted_entries: HashSet::new(),
removed_entries: HashSet::new(),
}
}
pub fn inserted_entries(&self) -> &HashSet<Block::Hash> {
&self.inserted_entries
}
pub fn removed_entries(&self) -> &HashSet<Block::Hash> {
&self.removed_entries
}
pub fn updated_meta(&self) -> &Option<Metadata<Block>> {
&self.updated_meta
}
}
impl<Block: BlockT, T: CacheItemT> StorageTransaction<Block, T> for DummyTransaction<Block> {
fn insert_storage_entry(
&mut self,
at: &ComplexBlockId<Block>,
_entry: &StorageEntry<Block, T>,
) {
self.inserted_entries.insert(at.hash);
}
fn remove_storage_entry(&mut self, at: &ComplexBlockId<Block>) {
self.removed_entries.insert(at.hash);
}
fn update_meta(
&mut self,
best_finalized_entry: Option<&Entry<Block, T>>,
unfinalized: &[Fork<Block, T>],
operation: &CommitOperation<Block, T>,
) {
self.updated_meta = Some(
meta::decode(&meta::encode(best_finalized_entry, unfinalized, operation)).unwrap(),
);
}
}
}
-413
View File
@@ -1,413 +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/>.
//! DB-backed cache of blockchain data.
use parking_lot::RwLock;
use std::{
collections::{hash_map::Entry, HashMap},
sync::Arc,
};
use crate::{
utils::{self, COLUMN_META},
DbHash,
};
use codec::{Decode, Encode};
use sc_client_api::blockchain::{
well_known_cache_keys::{self, Id as CacheKeyId},
Cache as BlockchainCache,
};
use sp_blockchain::{HeaderMetadataCache, Result as ClientResult};
use sp_database::{Database, Transaction};
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero},
};
use self::list_cache::{ListCache, PruningStrategy};
mod list_cache;
mod list_entry;
mod list_storage;
/// Minimal post-finalization age of finalized blocks before they'll pruned.
const PRUNE_DEPTH: u32 = 1024;
/// The type of entry that is inserted to the cache.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum EntryType {
/// Non-final entry.
NonFinal,
/// Final entry.
Final,
/// Genesis entry (inserted during cache initialization).
Genesis,
}
/// Block identifier that holds both hash and number.
#[derive(Clone, Debug, Encode, Decode, PartialEq)]
pub struct ComplexBlockId<Block: BlockT> {
/// Hash of the block.
pub(crate) hash: Block::Hash,
/// Number of the block.
pub(crate) number: NumberFor<Block>,
}
impl<Block: BlockT> ComplexBlockId<Block> {
/// Create new complex block id.
pub fn new(hash: Block::Hash, number: NumberFor<Block>) -> Self {
ComplexBlockId { hash, number }
}
}
impl<Block: BlockT> ::std::cmp::PartialOrd for ComplexBlockId<Block> {
fn partial_cmp(&self, other: &ComplexBlockId<Block>) -> Option<::std::cmp::Ordering> {
self.number.partial_cmp(&other.number)
}
}
/// All cache items must implement this trait.
pub trait CacheItemT: Clone + Decode + Encode + PartialEq {}
impl<T> CacheItemT for T where T: Clone + Decode + Encode + PartialEq {}
/// Database-backed blockchain data cache.
pub struct DbCache<Block: BlockT> {
cache_at: HashMap<CacheKeyId, ListCache<Block, Vec<u8>, self::list_storage::DbStorage>>,
header_metadata_cache: Arc<HeaderMetadataCache<Block>>,
db: Arc<dyn Database<DbHash>>,
key_lookup_column: u32,
header_column: u32,
cache_column: u32,
genesis_hash: Block::Hash,
best_finalized_block: ComplexBlockId<Block>,
}
impl<Block: BlockT> DbCache<Block> {
/// Create new cache.
pub fn new(
db: Arc<dyn Database<DbHash>>,
header_metadata_cache: Arc<HeaderMetadataCache<Block>>,
key_lookup_column: u32,
header_column: u32,
cache_column: u32,
genesis_hash: Block::Hash,
best_finalized_block: ComplexBlockId<Block>,
) -> Self {
Self {
cache_at: HashMap::new(),
db,
header_metadata_cache,
key_lookup_column,
header_column,
cache_column,
genesis_hash,
best_finalized_block,
}
}
/// Set genesis block hash.
pub fn set_genesis_hash(&mut self, genesis_hash: Block::Hash) {
self.genesis_hash = genesis_hash;
}
/// Begin cache transaction.
pub fn transaction<'a>(
&'a mut self,
tx: &'a mut Transaction<DbHash>,
) -> DbCacheTransaction<'a, Block> {
DbCacheTransaction {
cache: self,
tx,
cache_at_ops: HashMap::new(),
best_finalized_block: None,
}
}
/// Begin cache transaction with given ops.
pub fn transaction_with_ops<'a>(
&'a mut self,
tx: &'a mut Transaction<DbHash>,
ops: DbCacheTransactionOps<Block>,
) -> DbCacheTransaction<'a, Block> {
DbCacheTransaction {
cache: self,
tx,
cache_at_ops: ops.cache_at_ops,
best_finalized_block: ops.best_finalized_block,
}
}
/// Run post-commit cache operations.
pub fn commit(&mut self, ops: DbCacheTransactionOps<Block>) -> ClientResult<()> {
for (name, ops) in ops.cache_at_ops.into_iter() {
self.get_cache(name)?.on_transaction_commit(ops);
}
if let Some(best_finalized_block) = ops.best_finalized_block {
self.best_finalized_block = best_finalized_block;
}
Ok(())
}
/// Creates `ListCache` with the given name or returns a reference to the existing.
pub(crate) fn get_cache(
&mut self,
name: CacheKeyId,
) -> ClientResult<&mut ListCache<Block, Vec<u8>, self::list_storage::DbStorage>> {
get_cache_helper(
&mut self.cache_at,
name,
&self.db,
self.key_lookup_column,
self.header_column,
self.cache_column,
&self.best_finalized_block,
)
}
}
// This helper is needed because otherwise the borrow checker will require to
// clone all parameters outside of the closure.
fn get_cache_helper<'a, Block: BlockT>(
cache_at: &'a mut HashMap<CacheKeyId, ListCache<Block, Vec<u8>, self::list_storage::DbStorage>>,
name: CacheKeyId,
db: &Arc<dyn Database<DbHash>>,
key_lookup: u32,
header: u32,
cache: u32,
best_finalized_block: &ComplexBlockId<Block>,
) -> ClientResult<&'a mut ListCache<Block, Vec<u8>, self::list_storage::DbStorage>> {
match cache_at.entry(name) {
Entry::Occupied(entry) => Ok(entry.into_mut()),
Entry::Vacant(entry) => {
let cache = ListCache::new(
self::list_storage::DbStorage::new(
name.to_vec(),
db.clone(),
self::list_storage::DbColumns { meta: COLUMN_META, key_lookup, header, cache },
),
cache_pruning_strategy(name),
best_finalized_block.clone(),
)?;
Ok(entry.insert(cache))
},
}
}
/// Cache operations that are to be committed after database transaction is committed.
#[derive(Default)]
pub struct DbCacheTransactionOps<Block: BlockT> {
cache_at_ops: HashMap<CacheKeyId, self::list_cache::CommitOperations<Block, Vec<u8>>>,
best_finalized_block: Option<ComplexBlockId<Block>>,
}
impl<Block: BlockT> DbCacheTransactionOps<Block> {
/// Empty transaction ops.
pub fn empty() -> DbCacheTransactionOps<Block> {
DbCacheTransactionOps { cache_at_ops: HashMap::new(), best_finalized_block: None }
}
}
/// Database-backed blockchain data cache transaction valid for single block import.
pub struct DbCacheTransaction<'a, Block: BlockT> {
cache: &'a mut DbCache<Block>,
tx: &'a mut Transaction<DbHash>,
cache_at_ops: HashMap<CacheKeyId, self::list_cache::CommitOperations<Block, Vec<u8>>>,
best_finalized_block: Option<ComplexBlockId<Block>>,
}
impl<'a, Block: BlockT> DbCacheTransaction<'a, Block> {
/// Convert transaction into post-commit operations set.
pub fn into_ops(self) -> DbCacheTransactionOps<Block> {
DbCacheTransactionOps {
cache_at_ops: self.cache_at_ops,
best_finalized_block: self.best_finalized_block,
}
}
/// When new block is inserted into database.
pub fn on_block_insert(
mut self,
parent: ComplexBlockId<Block>,
block: ComplexBlockId<Block>,
data_at: HashMap<CacheKeyId, Vec<u8>>,
entry_type: EntryType,
) -> ClientResult<Self> {
// prepare list of caches that are not update
// (we might still need to do some cache maintenance in this case)
let missed_caches = self
.cache
.cache_at
.keys()
.filter(|cache| !data_at.contains_key(*cache))
.cloned()
.collect::<Vec<_>>();
let mut insert_op = |name: CacheKeyId,
value: Option<Vec<u8>>|
-> Result<(), sp_blockchain::Error> {
let cache = self.cache.get_cache(name)?;
let cache_ops = self.cache_at_ops.entry(name).or_default();
cache.on_block_insert(
&mut self::list_storage::DbStorageTransaction::new(cache.storage(), &mut self.tx),
parent.clone(),
block.clone(),
value,
entry_type,
cache_ops,
)?;
Ok(())
};
data_at.into_iter().try_for_each(|(name, data)| insert_op(name, Some(data)))?;
missed_caches.into_iter().try_for_each(|name| insert_op(name, None))?;
match entry_type {
EntryType::Final | EntryType::Genesis => self.best_finalized_block = Some(block),
EntryType::NonFinal => (),
}
Ok(self)
}
/// When previously inserted block is finalized.
pub fn on_block_finalize(
mut self,
parent: ComplexBlockId<Block>,
block: ComplexBlockId<Block>,
) -> ClientResult<Self> {
for (name, cache) in self.cache.cache_at.iter() {
let cache_ops = self.cache_at_ops.entry(*name).or_default();
cache.on_block_finalize(
&mut self::list_storage::DbStorageTransaction::new(cache.storage(), &mut self.tx),
parent.clone(),
block.clone(),
cache_ops,
)?;
}
self.best_finalized_block = Some(block);
Ok(self)
}
/// When block is reverted.
pub fn on_block_revert(mut self, reverted_block: &ComplexBlockId<Block>) -> ClientResult<Self> {
for (name, cache) in self.cache.cache_at.iter() {
let cache_ops = self.cache_at_ops.entry(*name).or_default();
cache.on_block_revert(
&mut self::list_storage::DbStorageTransaction::new(cache.storage(), &mut self.tx),
reverted_block,
cache_ops,
)?;
}
Ok(self)
}
}
/// Synchronous implementation of database-backed blockchain data cache.
pub struct DbCacheSync<Block: BlockT>(pub RwLock<DbCache<Block>>);
impl<Block: BlockT> BlockchainCache<Block> for DbCacheSync<Block> {
fn initialize(&self, key: &CacheKeyId, data: Vec<u8>) -> ClientResult<()> {
let mut cache = self.0.write();
let genesis_hash = cache.genesis_hash;
let cache_contents = vec![(*key, data)].into_iter().collect();
let db = cache.db.clone();
let mut dbtx = Transaction::new();
let tx = cache.transaction(&mut dbtx);
let tx = tx.on_block_insert(
ComplexBlockId::new(Default::default(), Zero::zero()),
ComplexBlockId::new(genesis_hash, Zero::zero()),
cache_contents,
EntryType::Genesis,
)?;
let tx_ops = tx.into_ops();
db.commit(dbtx)?;
cache.commit(tx_ops)?;
Ok(())
}
fn get_at(
&self,
key: &CacheKeyId,
at: &BlockId<Block>,
) -> ClientResult<
Option<((NumberFor<Block>, Block::Hash), Option<(NumberFor<Block>, Block::Hash)>, Vec<u8>)>,
> {
let mut cache = self.0.write();
let header_metadata_cache = cache.header_metadata_cache.clone();
let cache = cache.get_cache(*key)?;
let storage = cache.storage();
let db = storage.db();
let columns = storage.columns();
let at = match *at {
BlockId::Hash(hash) => match header_metadata_cache.header_metadata(hash) {
Some(metadata) => ComplexBlockId::new(hash, metadata.number),
None => {
let header = utils::require_header::<Block>(
&**db,
columns.key_lookup,
columns.header,
BlockId::Hash(hash.clone()),
)?;
ComplexBlockId::new(hash, *header.number())
},
},
BlockId::Number(number) => {
let hash = utils::require_header::<Block>(
&**db,
columns.key_lookup,
columns.header,
BlockId::Number(number.clone()),
)?
.hash();
ComplexBlockId::new(hash, number)
},
};
cache.value_at_block(&at).map(|block_and_value| {
block_and_value.map(|(begin_block, end_block, value)| {
(
(begin_block.number, begin_block.hash),
end_block.map(|end_block| (end_block.number, end_block.hash)),
value,
)
})
})
}
}
/// Get pruning strategy for given cache.
fn cache_pruning_strategy<N: From<u32>>(cache: CacheKeyId) -> PruningStrategy<N> {
// the cache is mostly used to store data from consensus engines
// this kind of data is only required for non-finalized blocks
// => by default we prune finalized cached entries
match cache {
// we need to keep changes tries configurations forever (or at least until changes tries,
// that were built using this configuration, are pruned) to make it possible to refer
// to old changes tries
well_known_cache_keys::CHANGES_TRIE_CONFIG => PruningStrategy::NeverPrune,
_ => PruningStrategy::ByDepth(PRUNE_DEPTH.into()),
}
}
File diff suppressed because it is too large Load Diff
+8 -265
View File
@@ -28,14 +28,11 @@
#![warn(missing_docs)]
pub mod light;
pub mod offchain;
#[cfg(any(feature = "with-kvdb-rocksdb", test))]
pub mod bench;
mod cache;
mod changes_tries_storage;
mod children;
#[cfg(feature = "with-parity-db")]
mod parity_db;
@@ -56,7 +53,6 @@ use std::{
};
use crate::{
changes_tries_storage::{DbChangesTrieStorage, DbChangesTrieStorageTransaction},
stats::StateUsageStats,
storage_cache::{new_shared_cache, CachingState, SharedCache, SyncingCachingState},
utils::{meta_keys, read_db, read_meta, DatabaseType, Meta},
@@ -64,8 +60,7 @@ use crate::{
use codec::{Decode, Encode};
use hash_db::Prefix;
use sc_client_api::{
backend::{NewBlockState, ProvideChtRoots, PrunableStateChangesTrieStorage},
cht,
backend::NewBlockState,
leaves::{FinalizationDisplaced, LeafSet},
utils::is_descendent_of,
IoInfo, MemoryInfo, MemorySize, UsageInfo,
@@ -79,11 +74,10 @@ use sp_blockchain::{
use sp_core::{
offchain::OffchainOverlayedChange,
storage::{well_known_keys, ChildInfo},
ChangesTrieConfiguration,
};
use sp_database::Transaction;
use sp_runtime::{
generic::{BlockId, DigestItem},
generic::BlockId,
traits::{
Block as BlockT, Hash, HashFor, Header as HeaderT, NumberFor, One, SaturatedConversion,
Zero,
@@ -91,9 +85,8 @@ use sp_runtime::{
Justification, Justifications, Storage,
};
use sp_state_machine::{
backend::Backend as StateBackend, ChangesTrieCacheAction, ChangesTrieTransaction,
ChildStorageCollection, DBValue, IndexOperation, OffchainChangesCollection, StateMachineStats,
StorageCollection, UsageInfo as StateUsageInfo,
backend::Backend as StateBackend, ChildStorageCollection, DBValue, IndexOperation,
OffchainChangesCollection, StateMachineStats, StorageCollection, UsageInfo as StateUsageInfo,
};
use sp_trie::{prefixed_key, MemoryDB, PrefixedMemoryDB};
@@ -104,7 +97,6 @@ pub use sp_database::Database;
#[cfg(any(feature = "with-kvdb-rocksdb", test))]
pub use bench::BenchmarkingState;
const MIN_BLOCKS_TO_KEEP_CHANGES_TRIES_FOR: u32 = 32768;
const CACHE_HEADERS: usize = 8;
/// Default value for storage cache child ratio.
@@ -406,11 +398,9 @@ pub(crate) mod columns {
pub const HEADER: u32 = 4;
pub const BODY: u32 = 5;
pub const JUSTIFICATIONS: u32 = 6;
pub const CHANGES_TRIE: u32 = 7;
pub const AUX: u32 = 8;
/// Offchain workers local storage
pub const OFFCHAIN: u32 = 9;
pub const CACHE: u32 = 10;
/// Transactions
pub const TRANSACTION: u32 = 11;
}
@@ -506,13 +496,6 @@ impl<Block: BlockT> BlockchainDb<Block> {
let mut meta = self.meta.write();
meta.block_gap = gap;
}
// Get block changes trie root, if available.
fn changes_trie_root(&self, block: BlockId<Block>) -> ClientResult<Option<Block::Hash>> {
self.header(block).map(|header| {
header.and_then(|header| header.digest().log(DigestItem::as_changes_trie_root).cloned())
})
}
}
impl<Block: BlockT> sc_client_api::blockchain::HeaderBackend<Block> for BlockchainDb<Block> {
@@ -646,10 +629,6 @@ impl<Block: BlockT> sc_client_api::blockchain::Backend<Block> for BlockchainDb<B
Ok(self.meta.read().finalized_hash.clone())
}
fn cache(&self) -> Option<Arc<dyn sc_client_api::blockchain::Cache<Block>>> {
None
}
fn leaves(&self) -> ClientResult<Vec<Block::Hash>> {
Ok(self.leaves.read().hashes())
}
@@ -702,12 +681,6 @@ impl<Block: BlockT> sc_client_api::blockchain::Backend<Block> for BlockchainDb<B
}
}
impl<Block: BlockT> sc_client_api::blockchain::ProvideCache<Block> for BlockchainDb<Block> {
fn cache(&self) -> Option<Arc<dyn sc_client_api::blockchain::Cache<Block>>> {
None
}
}
impl<Block: BlockT> HeaderMetadata<Block> for BlockchainDb<Block> {
type Error = sp_blockchain::Error;
@@ -745,62 +718,6 @@ impl<Block: BlockT> HeaderMetadata<Block> for BlockchainDb<Block> {
}
}
impl<Block: BlockT> ProvideChtRoots<Block> for BlockchainDb<Block> {
fn header_cht_root(
&self,
cht_size: NumberFor<Block>,
block: NumberFor<Block>,
) -> sp_blockchain::Result<Option<Block::Hash>> {
let cht_number = match cht::block_to_cht_number(cht_size, block) {
Some(number) => number,
None => return Ok(None),
};
let cht_start: NumberFor<Block> = cht::start_number(cht::size(), cht_number);
let mut current_num = cht_start;
let cht_range = ::std::iter::from_fn(|| {
let old_current_num = current_num;
current_num = current_num + One::one();
Some(old_current_num)
});
cht::compute_root::<Block::Header, HashFor<Block>, _>(
cht::size(),
cht_number,
cht_range.map(|num| self.hash(num)),
)
.map(Some)
}
fn changes_trie_cht_root(
&self,
cht_size: NumberFor<Block>,
block: NumberFor<Block>,
) -> sp_blockchain::Result<Option<Block::Hash>> {
let cht_number = match cht::block_to_cht_number(cht_size, block) {
Some(number) => number,
None => return Ok(None),
};
let cht_start: NumberFor<Block> = cht::start_number(cht::size(), cht_number);
let mut current_num = cht_start;
let cht_range = ::std::iter::from_fn(|| {
let old_current_num = current_num;
current_num = current_num + One::one();
Some(old_current_num)
});
cht::compute_root::<Block::Header, HashFor<Block>, _>(
cht::size(),
cht_number,
cht_range.map(|num| self.changes_trie_root(BlockId::Number(num))),
)
.map(Some)
}
}
/// Database transaction
pub struct BlockImportOperation<Block: BlockT> {
old_state: SyncingCachingState<RefTrackingState<Block>, Block>,
@@ -808,9 +725,6 @@ pub struct BlockImportOperation<Block: BlockT> {
storage_updates: StorageCollection,
child_storage_updates: ChildStorageCollection,
offchain_storage_updates: OffchainChangesCollection,
changes_trie_updates: MemoryDB<HashFor<Block>>,
changes_trie_build_cache_update: Option<ChangesTrieCacheAction<Block::Hash, NumberFor<Block>>>,
changes_trie_config_update: Option<Option<ChangesTrieConfiguration>>,
pending_block: Option<PendingBlock<Block>>,
aux_ops: Vec<(Vec<u8>, Option<Vec<u8>>)>,
finalized_blocks: Vec<(BlockId<Block>, Option<Justification>)>,
@@ -858,25 +772,12 @@ impl<Block: BlockT> BlockImportOperation<Block> {
)
});
let mut changes_trie_config = None;
let (root, transaction) = self.old_state.full_storage_root(
storage.top.iter().map(|(k, v)| {
if &k[..] == well_known_keys::CHANGES_TRIE_CONFIG {
changes_trie_config = Some(Decode::decode(&mut &v[..]));
}
(&k[..], Some(&v[..]))
}),
storage.top.iter().map(|(k, v)| (&k[..], Some(&v[..]))),
child_delta,
);
let changes_trie_config = match changes_trie_config {
Some(Ok(c)) => Some(c),
Some(Err(_)) => return Err(sp_blockchain::Error::InvalidState.into()),
None => None,
};
self.db_updates = transaction;
self.changes_trie_config_update = Some(changes_trie_config);
Ok(root)
}
}
@@ -899,11 +800,6 @@ impl<Block: BlockT> sc_client_api::backend::BlockImportOperation<Block>
leaf_state: NewBlockState,
) -> ClientResult<()> {
assert!(self.pending_block.is_none(), "Only one block per operation is allowed");
if let Some(changes_trie_config_update) =
changes_tries_storage::extract_new_configuration(&header)
{
self.changes_trie_config_update = Some(changes_trie_config_update.clone());
}
self.pending_block =
Some(PendingBlock { header, body, indexed_body, justifications, leaf_state });
Ok(())
@@ -930,15 +826,6 @@ impl<Block: BlockT> sc_client_api::backend::BlockImportOperation<Block>
Ok(root)
}
fn update_changes_trie(
&mut self,
update: ChangesTrieTransaction<HashFor<Block>, NumberFor<Block>>,
) -> ClientResult<()> {
self.changes_trie_updates = update.0;
self.changes_trie_build_cache_update = Some(update.1);
Ok(())
}
fn insert_aux<I>(&mut self, ops: I) -> ClientResult<()>
where
I: IntoIterator<Item = (Vec<u8>, Option<Vec<u8>>)>,
@@ -1095,7 +982,6 @@ impl<T: Clone> FrozenForDuration<T> {
pub struct Backend<Block: BlockT> {
storage: Arc<StorageDb<Block>>,
offchain_storage: offchain::LocalStorage,
changes_tries_storage: DbChangesTrieStorage<Block>,
blockchain: BlockchainDb<Block>,
canonicalization_delay: u64,
shared_cache: SharedCache<Block>,
@@ -1155,7 +1041,6 @@ impl<Block: BlockT> Backend<Block> {
) -> ClientResult<Self> {
let is_archive_pruning = config.state_pruning.is_archive();
let blockchain = BlockchainDb::new(db.clone(), config.transaction_storage.clone())?;
let meta = blockchain.meta.clone();
let map_e = |e: sc_state_db::Error<io::Error>| sp_blockchain::Error::from_state_db(e);
let state_db: StateDb<_, _> = StateDb::new(
config.state_pruning.clone(),
@@ -1166,22 +1051,10 @@ impl<Block: BlockT> Backend<Block> {
let storage_db =
StorageDb { db: db.clone(), state_db, prefix_keys: !db.supports_ref_counting() };
let offchain_storage = offchain::LocalStorage::new(db.clone());
let changes_tries_storage = DbChangesTrieStorage::new(
db,
blockchain.header_metadata_cache.clone(),
columns::META,
columns::CHANGES_TRIE,
columns::KEY_LOOKUP,
columns::HEADER,
columns::CACHE,
meta,
if is_archive_pruning { None } else { Some(MIN_BLOCKS_TO_KEEP_CHANGES_TRIES_FOR) },
)?;
let backend = Backend {
storage: Arc::new(storage_db),
offchain_storage,
changes_tries_storage,
blockchain,
canonicalization_delay,
shared_cache: new_shared_cache(
@@ -1318,7 +1191,6 @@ impl<Block: BlockT> Backend<Block> {
header: &Block::Header,
last_finalized: Option<Block::Hash>,
justification: Option<Justification>,
changes_trie_cache_ops: &mut Option<DbChangesTrieStorageTransaction<Block>>,
finalization_displaced: &mut Option<FinalizationDisplaced<Block::Hash, NumberFor<Block>>>,
) -> ClientResult<MetaUpdate<Block>> {
// TODO: ensure best chain contains this block.
@@ -1326,15 +1198,7 @@ impl<Block: BlockT> Backend<Block> {
self.ensure_sequential_finalization(header, last_finalized)?;
let with_state = sc_client_api::Backend::have_state_at(self, &hash, number);
self.note_finalized(
transaction,
false,
header,
*hash,
changes_trie_cache_ops,
finalization_displaced,
with_state,
)?;
self.note_finalized(transaction, header, *hash, finalization_displaced, with_state)?;
if let Some(justification) = justification {
transaction.set_from_vec(
@@ -1400,7 +1264,6 @@ impl<Block: BlockT> Backend<Block> {
(meta.best_number, meta.finalized_hash, meta.finalized_number, meta.block_gap.clone())
};
let mut changes_trie_cache_ops = None;
for (block, justification) in operation.finalized_blocks {
let block_hash = self.blockchain.expect_block_hash_from_id(&block)?;
let block_header = self.blockchain.expect_header(BlockId::Hash(block_hash))?;
@@ -1410,7 +1273,6 @@ impl<Block: BlockT> Backend<Block> {
&block_header,
Some(last_finalized_hash),
justification,
&mut changes_trie_cache_ops,
&mut finalization_displaced_leaves,
)?);
last_finalized_hash = block_hash;
@@ -1475,11 +1337,6 @@ impl<Block: BlockT> Backend<Block> {
);
transaction.set(columns::META, meta_keys::GENESIS_HASH, hash.as_ref());
// for tests, because config is set from within the reset_storage
if operation.changes_trie_config_update.is_none() {
operation.changes_trie_config_update = Some(None);
}
if operation.commit_state {
transaction.set_from_vec(columns::META, meta_keys::FINALIZED_STATE, lookup_key);
} else {
@@ -1578,7 +1435,6 @@ impl<Block: BlockT> Backend<Block> {
let header = &pending_block.header;
let is_best = pending_block.leaf_state.is_best();
let changes_trie_updates = operation.changes_trie_updates;
debug!(target: "db",
"DB Commit {:?} ({}), best={}, state={}, existing={}",
hash, number, is_best, operation.commit_state, existing_header,
@@ -1593,10 +1449,8 @@ impl<Block: BlockT> Backend<Block> {
self.ensure_sequential_finalization(header, Some(last_finalized_hash))?;
self.note_finalized(
&mut transaction,
true,
header,
hash,
&mut changes_trie_cache_ops,
&mut finalization_displaced_leaves,
operation.commit_state,
)?;
@@ -1606,21 +1460,6 @@ impl<Block: BlockT> Backend<Block> {
}
if !existing_header {
let changes_trie_config_update = operation.changes_trie_config_update;
changes_trie_cache_ops = Some(self.changes_tries_storage.commit(
&mut transaction,
changes_trie_updates,
cache::ComplexBlockId::new(
*header.parent_hash(),
if number.is_zero() { Zero::zero() } else { number - One::one() },
),
cache::ComplexBlockId::new(hash, number),
header,
finalized,
changes_trie_config_update,
changes_trie_cache_ops,
)?);
{
let mut leaves = self.blockchain.leaves.write();
leaves.import(hash, number, parent_hash);
@@ -1747,11 +1586,6 @@ impl<Block: BlockT> Backend<Block> {
);
}
if let Some(changes_trie_build_cache_update) = operation.changes_trie_build_cache_update {
self.changes_tries_storage.commit_build_cache(changes_trie_build_cache_update);
}
self.changes_tries_storage.post_commit(changes_trie_cache_ops);
if let Some((enacted, retracted)) = cache_update {
self.shared_cache.write().sync(&enacted, &retracted);
}
@@ -1770,10 +1604,8 @@ impl<Block: BlockT> Backend<Block> {
fn note_finalized(
&self,
transaction: &mut Transaction<DbHash>,
is_inserted: bool,
f_header: &Block::Header,
f_hash: Block::Hash,
changes_trie_cache_ops: &mut Option<DbChangesTrieStorageTransaction<Block>>,
displaced: &mut Option<FinalizationDisplaced<Block::Hash, NumberFor<Block>>>,
with_state: bool,
) -> ClientResult<()> {
@@ -1798,18 +1630,6 @@ impl<Block: BlockT> Backend<Block> {
apply_state_commit(transaction, commit);
}
if !f_num.is_zero() {
let new_changes_trie_cache_ops = self.changes_tries_storage.finalize(
transaction,
*f_header.parent_hash(),
f_hash,
f_num,
if is_inserted { Some(&f_header) } else { None },
changes_trie_cache_ops.take(),
)?;
*changes_trie_cache_ops = Some(new_changes_trie_cache_ops);
}
let new_displaced = self.blockchain.leaves.write().finalize_height(f_num);
self.prune_blocks(transaction, f_num, &new_displaced)?;
match displaced {
@@ -2036,9 +1856,6 @@ impl<Block: BlockT> sc_client_api::backend::Backend<Block> for Backend<Block> {
storage_updates: Default::default(),
child_storage_updates: Default::default(),
offchain_storage_updates: Default::default(),
changes_trie_config_update: None,
changes_trie_updates: MemoryDB::default(),
changes_trie_build_cache_update: None,
aux_ops: Vec::new(),
finalized_blocks: Vec::new(),
set_head: None,
@@ -2089,19 +1906,16 @@ impl<Block: BlockT> sc_client_api::backend::Backend<Block> for Backend<Block> {
let header = self.blockchain.expect_header(block)?;
let mut displaced = None;
let mut changes_trie_cache_ops = None;
let m = self.finalize_block_with_transaction(
&mut transaction,
&hash,
&header,
None,
justification,
&mut changes_trie_cache_ops,
&mut displaced,
)?;
self.storage.db.commit(transaction)?;
self.blockchain.update_meta(m);
self.changes_tries_storage.post_commit(changes_trie_cache_ops);
Ok(())
}
@@ -2148,10 +1962,6 @@ impl<Block: BlockT> sc_client_api::backend::Backend<Block> for Backend<Block> {
Ok(())
}
fn changes_trie_storage(&self) -> Option<&dyn PrunableStateChangesTrieStorage<Block>> {
Some(&self.changes_tries_storage)
}
fn offchain_storage(&self) -> Option<Self::OffchainStorage> {
Some(self.offchain_storage.clone())
}
@@ -2208,7 +2018,6 @@ impl<Block: BlockT> sc_client_api::backend::Backend<Block> for Backend<Block> {
return Ok(c.saturated_into::<NumberFor<Block>>())
}
let mut transaction = Transaction::new();
let removed_number = best_number;
let removed =
self.blockchain.header(BlockId::Number(best_number))?.ok_or_else(|| {
sp_blockchain::Error::UnknownBlock(format!(
@@ -2241,10 +2050,6 @@ impl<Block: BlockT> sc_client_api::backend::Backend<Block> for Backend<Block> {
let key =
utils::number_and_hash_to_lookup_key(best_number.clone(), &best_hash)?;
let changes_trie_cache_ops = self.changes_tries_storage.revert(
&mut transaction,
&cache::ComplexBlockId::new(removed.hash(), removed_number),
)?;
if update_finalized {
transaction.set_from_vec(
columns::META,
@@ -2283,7 +2088,6 @@ impl<Block: BlockT> sc_client_api::backend::Backend<Block> for Backend<Block> {
best_hash,
);
self.storage.db.commit(transaction)?;
self.changes_tries_storage.post_commit(Some(changes_trie_cache_ops));
self.blockchain.update_meta(MetaUpdate {
hash: best_hash,
number: best_number,
@@ -2345,11 +2149,6 @@ impl<Block: BlockT> sc_client_api::backend::Backend<Block> for Backend<Block> {
apply_state_commit(&mut transaction, commit);
}
transaction.remove(columns::KEY_LOOKUP, hash.as_ref());
let changes_trie_cache_ops = self
.changes_tries_storage
.revert(&mut transaction, &cache::ComplexBlockId::new(*hash, hdr.number))?;
self.changes_tries_storage.post_commit(Some(changes_trie_cache_ops));
leaves.revert(hash.clone(), hdr.number);
leaves.prepare_transaction(&mut transaction, columns::META, meta_keys::LEAF_PREFIX);
self.storage.db.commit(transaction)?;
@@ -2461,32 +2260,16 @@ pub(crate) mod tests {
use sp_blockchain::{lowest_common_ancestor, tree_route};
use sp_core::H256;
use sp_runtime::{
generic::DigestItem,
testing::{Block as RawBlock, ExtrinsicWrapper, Header},
traits::{BlakeTwo256, Hash},
ConsensusEngineId,
};
use sp_state_machine::{TrieDBMut, TrieMut};
const CONS0_ENGINE_ID: ConsensusEngineId = *b"CON0";
const CONS1_ENGINE_ID: ConsensusEngineId = *b"CON1";
pub(crate) type Block = RawBlock<ExtrinsicWrapper<u64>>;
pub fn prepare_changes(changes: Vec<(Vec<u8>, Vec<u8>)>) -> (H256, MemoryDB<BlakeTwo256>) {
let mut changes_root = H256::default();
let mut changes_trie_update = MemoryDB::<BlakeTwo256>::default();
{
let mut trie =
TrieDBMut::<BlakeTwo256>::new(&mut changes_trie_update, &mut changes_root);
for (key, value) in changes {
trie.insert(&key, &value).unwrap();
}
}
(changes_root, changes_trie_update)
}
pub fn insert_header(
backend: &Backend<Block>,
number: u64,
@@ -2501,20 +2284,14 @@ pub(crate) mod tests {
backend: &Backend<Block>,
number: u64,
parent_hash: H256,
changes: Option<Vec<(Vec<u8>, Vec<u8>)>>,
_changes: Option<Vec<(Vec<u8>, Vec<u8>)>>,
extrinsics_root: H256,
body: Vec<ExtrinsicWrapper<u64>>,
transaction_index: Option<Vec<IndexOperation>>,
) -> H256 {
use sp_runtime::testing::Digest;
let mut digest = Digest::default();
let mut changes_trie_update = Default::default();
if let Some(changes) = changes {
let (root, update) = prepare_changes(changes);
digest.push(DigestItem::ChangesTrieRoot(root));
changes_trie_update = update;
}
let digest = Digest::default();
let header = Header {
number,
parent_hash,
@@ -2535,8 +2312,6 @@ pub(crate) mod tests {
if let Some(index) = transaction_index {
op.update_transaction_index(index).unwrap();
}
op.update_changes_trie((changes_trie_update, ChangesTrieCacheAction::Clear))
.unwrap();
backend.commit_operation(op).unwrap();
header_hash
@@ -3241,38 +3016,6 @@ pub(crate) mod tests {
}
}
#[test]
fn header_cht_root_works() {
use sc_client_api::ProvideChtRoots;
let backend = Backend::<Block>::new_test(10, 10);
// insert 1 + SIZE + SIZE + 1 blocks so that CHT#0 is created
let mut prev_hash =
insert_header(&backend, 0, Default::default(), None, Default::default());
let cht_size: u64 = cht::size();
for i in 1..1 + cht_size + cht_size + 1 {
prev_hash = insert_header(&backend, i, prev_hash, None, Default::default());
}
let blockchain = backend.blockchain();
let cht_root_1 = blockchain
.header_cht_root(cht_size, cht::start_number(cht_size, 0))
.unwrap()
.unwrap();
let cht_root_2 = blockchain
.header_cht_root(cht_size, cht::start_number(cht_size, 0) + cht_size / 2)
.unwrap()
.unwrap();
let cht_root_3 = blockchain
.header_cht_root(cht_size, cht::end_number(cht_size, 0))
.unwrap()
.unwrap();
assert_eq!(cht_root_1, cht_root_2);
assert_eq!(cht_root_2, cht_root_3);
}
#[test]
fn prune_blocks_on_finalize() {
for storage in &[TransactionStorageMode::BlockBody, TransactionStorageMode::StorageChain] {
File diff suppressed because it is too large Load Diff
+1 -5
View File
@@ -16,7 +16,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
columns, light,
columns,
utils::{DatabaseType, NUM_COLUMNS},
};
/// A `Database` adapter for parity-db.
@@ -61,10 +61,6 @@ pub fn open<H: Clone + AsRef<[u8]>>(
state_col.preimage = true;
state_col.uniform = true;
},
DatabaseType::Light => {
config.columns[light::columns::HEADER as usize].compression =
parity_db::CompressionType::Lz4;
},
}
let db = if create {
+1 -78
View File
@@ -56,10 +56,6 @@ pub mod meta_keys {
pub const FINALIZED_STATE: &[u8; 6] = b"fstate";
/// Block gap.
pub const BLOCK_GAP: &[u8; 3] = b"gap";
/// Meta information prefix for list-based caches.
pub const CACHE_META_PREFIX: &[u8; 5] = b"cache";
/// Meta information for changes tries key.
pub const CHANGES_TRIES_META: &[u8; 5] = b"ctrie";
/// Genesis block hash.
pub const GENESIS_HASH: &[u8; 3] = b"gen";
/// Leaves prefix list key.
@@ -95,8 +91,6 @@ pub type NumberIndexKey = [u8; 4];
pub enum DatabaseType {
/// Full node database.
Full,
/// Light node database.
Light,
}
/// Convert block number into short lookup key (LE representation) for
@@ -124,19 +118,6 @@ where
Ok(lookup_key)
}
/// Convert block lookup key into block number.
/// all block lookup keys start with the block number.
pub fn lookup_key_to_number<N>(key: &[u8]) -> sp_blockchain::Result<N>
where
N: From<u32>,
{
if key.len() < 4 {
return Err(sp_blockchain::Error::Backend("Invalid block key".into()))
}
Ok((key[0] as u32) << 24 | (key[1] as u32) << 16 | (key[2] as u32) << 8 | (key[3] as u32))
.map(Into::into)
}
/// Delete number to hash mapping in DB transaction.
pub fn remove_number_to_key_mapping<N: TryInto<u32>>(
transaction: &mut Transaction<DbHash>,
@@ -147,18 +128,6 @@ pub fn remove_number_to_key_mapping<N: TryInto<u32>>(
Ok(())
}
/// Remove key mappings.
pub fn remove_key_mappings<N: TryInto<u32>, H: AsRef<[u8]>>(
transaction: &mut Transaction<DbHash>,
key_lookup_col: u32,
number: N,
hash: H,
) -> sp_blockchain::Result<()> {
remove_number_to_key_mapping(transaction, key_lookup_col, number)?;
transaction.remove(key_lookup_col, hash.as_ref());
Ok(())
}
/// Place a number mapping into the database. This maps number to current perceived
/// block hash at that position.
pub fn insert_number_to_key_mapping<N: TryInto<u32> + Clone, H: AsRef<[u8]>>(
@@ -357,18 +326,6 @@ fn open_kvdb_rocksdb<Block: BlockT>(
other_col_budget,
);
},
DatabaseType::Light => {
let col_budget = cache_size / (NUM_COLUMNS as usize);
for i in 0..NUM_COLUMNS {
memory_budget.insert(i, col_budget);
}
log::trace!(
target: "db",
"Open RocksDB light database at {:?}, column cache: {} MiB",
path,
col_budget,
);
},
}
db_config.memory_budget = memory_budget;
@@ -424,8 +381,7 @@ fn maybe_migrate_to_type_subdir<Block: BlockT>(
// See if there's a file identifying a rocksdb or paritydb folder in the parent dir and
// the target path ends in a role specific directory
if (basedir.join("db_version").exists() || basedir.join("metadata").exists()) &&
(p.ends_with(DatabaseType::Full.as_str()) ||
p.ends_with(DatabaseType::Light.as_str()))
(p.ends_with(DatabaseType::Full.as_str()))
{
// Try to open the database to check if the current `DatabaseType` matches the type of
// database stored in the target directory and close the database on success.
@@ -501,18 +457,6 @@ pub fn read_header<Block: BlockT>(
}
}
/// Required header from the database.
pub fn require_header<Block: BlockT>(
db: &dyn Database<DbHash>,
col_index: u32,
col: u32,
id: BlockId<Block>,
) -> sp_blockchain::Result<Block::Header> {
read_header(db, col_index, col, id).and_then(|header| {
header.ok_or_else(|| sp_blockchain::Error::UnknownBlock(format!("Require header: {}", id)))
})
}
/// Read meta from the database.
pub fn read_meta<Block>(
db: &dyn Database<DbHash>,
@@ -598,7 +542,6 @@ impl DatabaseType {
pub fn as_str(&self) -> &'static str {
match *self {
DatabaseType::Full => "full",
DatabaseType::Light => "light",
}
}
}
@@ -669,23 +612,12 @@ mod tests {
assert!(old_db_path.join(db_type.as_str()).join(db_check_file).exists());
}
check_dir_for_db_type(
DatabaseType::Light,
DatabaseSource::RocksDb { path: PathBuf::new(), cache_size: 128 },
"db_version",
);
check_dir_for_db_type(
DatabaseType::Full,
DatabaseSource::RocksDb { path: PathBuf::new(), cache_size: 128 },
"db_version",
);
#[cfg(feature = "with-parity-db")]
check_dir_for_db_type(
DatabaseType::Light,
DatabaseSource::ParityDb { path: PathBuf::new() },
"metadata",
);
#[cfg(feature = "with-parity-db")]
check_dir_for_db_type(
DatabaseType::Full,
@@ -709,16 +641,8 @@ mod tests {
assert!(!old_db_path.join("light/db_version").exists());
assert!(!old_db_path.join("full/db_version").exists());
}
let source = DatabaseSource::RocksDb {
path: old_db_path.join(DatabaseType::Light.as_str()),
cache_size: 128,
};
let settings = db_settings(source);
let db_res = open_database::<Block>(&settings, DatabaseType::Light);
assert!(db_res.is_err(), "Opening a light database in full role should fail");
// assert nothing was changed
assert!(old_db_path.join("db_version").exists());
assert!(!old_db_path.join("light/db_version").exists());
assert!(!old_db_path.join("full/db_version").exists());
}
}
@@ -735,7 +659,6 @@ mod tests {
#[test]
fn database_type_as_str_works() {
assert_eq!(DatabaseType::Full.as_str(), "full");
assert_eq!(DatabaseType::Light.as_str(), "light");
}
#[test]
@@ -40,7 +40,7 @@ use tracing_subscriber::layer::SubscriberExt;
use crate::WasmExecutionMethod;
pub type TestExternalities = CoreTestExternalities<BlakeTwo256, u64>;
pub type TestExternalities = CoreTestExternalities<BlakeTwo256>;
type HostFunctions = sp_io::SubstrateHostFunctions;
/// Simple macro that runs a given method as test with the available wasm execution methods.
@@ -19,7 +19,7 @@
use std::{collections::HashMap, marker::PhantomData, sync::Arc};
use log::debug;
use parity_scale_codec::{Decode, Encode};
use parity_scale_codec::Decode;
use sc_client_api::{backend::Backend, utils::is_descendent_of};
use sc_consensus::{
@@ -35,7 +35,7 @@ use sp_core::hashing::twox_128;
use sp_finality_grandpa::{ConsensusLog, GrandpaApi, ScheduledChange, SetId, GRANDPA_ENGINE_ID};
use sp_runtime::{
generic::{BlockId, OpaqueDigestItemId},
traits::{Block as BlockT, DigestFor, Header as HeaderT, NumberFor, Zero},
traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero},
Justification,
};
@@ -89,7 +89,6 @@ impl<BE, Block: BlockT, Client, SC> JustificationImport<Block>
for GrandpaBlockImport<BE, Block, Client, SC>
where
NumberFor<Block>: finality_grandpa::BlockNumberOps,
DigestFor<Block>: Encode,
BE: Backend<Block>,
Client: ClientForGrandpa<Block, BE>,
SC: SelectChain<Block>,
@@ -229,7 +228,6 @@ pub fn find_forced_change<B: BlockT>(
impl<BE, Block: BlockT, Client, SC> GrandpaBlockImport<BE, Block, Client, SC>
where
NumberFor<Block>: finality_grandpa::BlockNumberOps,
DigestFor<Block>: Encode,
BE: Backend<Block>,
Client: ClientForGrandpa<Block, BE>,
Client::Api: GrandpaApi<Block>,
@@ -515,7 +513,6 @@ where
impl<BE, Block: BlockT, Client, SC> BlockImport<Block> for GrandpaBlockImport<BE, Block, Client, SC>
where
NumberFor<Block>: finality_grandpa::BlockNumberOps,
DigestFor<Block>: Encode,
BE: Backend<Block>,
Client: ClientForGrandpa<Block, BE>,
Client::Api: GrandpaApi<Block>,
+2 -3
View File
@@ -58,7 +58,7 @@
use futures::{prelude::*, StreamExt};
use log::{debug, error, info};
use parity_scale_codec::{Decode, Encode};
use parity_scale_codec::Decode;
use parking_lot::RwLock;
use prometheus_endpoint::{PrometheusError, Registry};
use sc_client_api::{
@@ -77,7 +77,7 @@ use sp_core::crypto::Public;
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, DigestFor, NumberFor, Zero},
traits::{Block as BlockT, NumberFor, Zero},
};
pub use finality_grandpa::BlockNumberOps;
@@ -718,7 +718,6 @@ where
SC: SelectChain<Block> + 'static,
VR: VotingRule<Block, C> + Clone + 'static,
NumberFor<Block>: BlockNumberOps,
DigestFor<Block>: Encode,
C: ClientForGrandpa<Block, BE> + 'static,
C::Api: GrandpaApi<Block>,
{
+19 -28
View File
@@ -43,6 +43,7 @@ use sp_finality_grandpa::{
use sp_keyring::Ed25519Keyring;
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
use sp_runtime::{
codec::Encode,
generic::{BlockId, DigestItem},
traits::{Block as BlockT, Header as HeaderT},
Justifications,
@@ -139,26 +140,16 @@ impl TestNetFactory for GrandpaTestNet {
&self,
client: PeersClient,
) -> (BlockImportAdapter<Self::BlockImport>, Option<BoxJustificationImport<Block>>, PeerData) {
match client {
PeersClient::Full(ref client, ref backend) => {
let (import, link) = block_import(
client.clone(),
&self.test_config,
LongestChain::new(backend.clone()),
None,
)
.expect("Could not create block import for fresh peer.");
let justification_import = Box::new(import.clone());
(
BlockImportAdapter::new(import),
Some(justification_import),
Mutex::new(Some(link)),
)
},
PeersClient::Light(..) => {
panic!("Light client is not used in tests.");
},
}
let (client, backend) = (client.as_client(), client.as_backend());
let (import, link) = block_import(
client.clone(),
&self.test_config,
LongestChain::new(backend.clone()),
None,
)
.expect("Could not create block import for fresh peer.");
let justification_import = Box::new(import.clone());
(BlockImportAdapter::new(import), Some(justification_import), Mutex::new(Some(link)))
}
fn peer(&mut self, i: usize) -> &mut GrandpaPeer {
@@ -466,7 +457,7 @@ fn finalize_3_voters_1_full_observer() {
// all peers should have stored the justification for the best finalized block #20
for peer_id in 0..4 {
let client = net.lock().peers[peer_id].client().as_full().unwrap();
let client = net.lock().peers[peer_id].client().as_client();
let justification =
crate::aux_schema::best_justification::<_, Block>(&*client).unwrap().unwrap();
@@ -539,7 +530,7 @@ fn transition_3_voters_twice_1_full_observer() {
net.lock().block_until_sync();
for (i, peer) in net.lock().peers().iter().enumerate() {
let full_client = peer.client().as_full().expect("only full clients are used in test");
let full_client = peer.client().as_client();
assert_eq!(full_client.chain_info().best_number, 1, "Peer #{} failed to sync", i);
let set: AuthoritySet<Hash, BlockNumber> =
@@ -614,7 +605,7 @@ fn transition_3_voters_twice_1_full_observer() {
.take_while(|n| future::ready(n.header.number() < &30))
.for_each(move |_| future::ready(()))
.map(move |()| {
let full_client = client.as_full().expect("only full clients are used in test");
let full_client = client.as_client();
let set: AuthoritySet<Hash, BlockNumber> =
crate::aux_schema::load_authorities(&*full_client).unwrap();
@@ -835,7 +826,7 @@ fn force_change_to_new_set() {
for (i, peer) in net.lock().peers().iter().enumerate() {
assert_eq!(peer.client().info().best_number, 26, "Peer #{} failed to sync", i);
let full_client = peer.client().as_full().expect("only full clients are used in test");
let full_client = peer.client().as_client();
let set: AuthoritySet<Hash, BlockNumber> =
crate::aux_schema::load_authorities(&*full_client).unwrap();
@@ -861,7 +852,7 @@ fn allows_reimporting_change_blocks() {
let client = net.peer(0).client().clone();
let (mut block_import, ..) = net.make_block_import(client.clone());
let full_client = client.as_full().unwrap();
let full_client = client.as_client();
let builder = full_client
.new_block_at(&BlockId::Number(0), Default::default(), false)
.unwrap();
@@ -908,7 +899,7 @@ fn test_bad_justification() {
let client = net.peer(0).client().clone();
let (mut block_import, ..) = net.make_block_import(client.clone());
let full_client = client.as_full().expect("only full clients are used in test");
let full_client = client.as_client();
let builder = full_client
.new_block_at(&BlockId::Number(0), Default::default(), false)
.unwrap();
@@ -1148,7 +1139,7 @@ fn voter_persists_its_votes() {
.await;
let block_30_hash =
net.lock().peer(0).client().as_full().unwrap().hash(30).unwrap().unwrap();
net.lock().peer(0).client().as_client().hash(30).unwrap().unwrap();
// we restart alice's voter
abort.abort();
@@ -1581,7 +1572,7 @@ fn imports_justification_for_regular_blocks_on_import() {
let client = net.peer(0).client().clone();
let (mut block_import, ..) = net.make_block_import(client.clone());
let full_client = client.as_full().expect("only full clients are used in test");
let full_client = client.as_client();
let builder = full_client
.new_block_at(&BlockId::Number(0), Default::default(), false)
.unwrap();
-27
View File
@@ -1,27 +0,0 @@
[package]
description = "components for a light client"
name = "sc-light"
version = "4.0.0-dev"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
homepage = "https://substrate.io"
repository = "https://github.com/paritytech/substrate/"
documentation = "https://docs.rs/sc-light"
readme = "README.md"
[dependencies]
parking_lot = "0.11.1"
hash-db = "0.15.2"
sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" }
sp-externalities = { version = "0.10.0-dev", path = "../../primitives/externalities" }
sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" }
sp-core = { version = "4.0.0-dev", path = "../../primitives/core" }
sp-state-machine = { version = "0.10.0-dev", path = "../../primitives/state-machine" }
sc-client-api = { version = "4.0.0-dev", path = "../api" }
sp-api = { version = "4.0.0-dev", path = "../../primitives/api" }
codec = { package = "parity-scale-codec", version = "2.0.0" }
sc-executor = { version = "0.10.0-dev", path = "../executor" }
[features]
default = []
-3
View File
@@ -1,3 +0,0 @@
Light client components.
License: GPL-3.0-or-later WITH Classpath-exception-2.0
-578
View File
@@ -1,578 +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/>.
//! Light client backend. Only stores headers and justifications of blocks.
//! Everything else is requested from full nodes on demand.
use parking_lot::RwLock;
use std::{
collections::{HashMap, HashSet},
sync::Arc,
};
use codec::{Decode, Encode};
use super::blockchain::Blockchain;
use hash_db::Hasher;
use sc_client_api::{
backend::{
AuxStore, Backend as ClientBackend, BlockImportOperation, NewBlockState,
PrunableStateChangesTrieStorage, RemoteBackend,
},
blockchain::{well_known_cache_keys, HeaderBackend as BlockchainHeaderBackend},
in_mem::check_genesis_storage,
light::Storage as BlockchainStorage,
UsageInfo,
};
use sp_blockchain::{Error as ClientError, Result as ClientResult};
use sp_core::{
offchain::storage::InMemOffchainStorage,
storage::{well_known_keys, ChildInfo},
ChangesTrieConfiguration,
};
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, HashFor, Header, NumberFor, Zero},
Justification, Justifications, Storage,
};
use sp_state_machine::{
Backend as StateBackend, ChangesTrieTransaction, ChildStorageCollection, InMemoryBackend,
IndexOperation, StorageCollection, TrieBackend,
};
const IN_MEMORY_EXPECT_PROOF: &str =
"InMemory state backend has Void error type and always succeeds; qed";
/// Light client backend.
pub struct Backend<S, H: Hasher> {
blockchain: Arc<Blockchain<S>>,
genesis_state: RwLock<Option<InMemoryBackend<H>>>,
import_lock: RwLock<()>,
}
/// Light block (header and justification) import operation.
pub struct ImportOperation<Block: BlockT, S> {
header: Option<Block::Header>,
cache: HashMap<well_known_cache_keys::Id, Vec<u8>>,
leaf_state: NewBlockState,
aux_ops: Vec<(Vec<u8>, Option<Vec<u8>>)>,
finalized_blocks: Vec<BlockId<Block>>,
set_head: Option<BlockId<Block>>,
storage_update: Option<InMemoryBackend<HashFor<Block>>>,
changes_trie_config_update: Option<Option<ChangesTrieConfiguration>>,
_phantom: std::marker::PhantomData<S>,
}
/// Either in-memory genesis state, or locally-unavailable state.
pub enum GenesisOrUnavailableState<H: Hasher> {
/// Genesis state - storage values are stored in-memory.
Genesis(InMemoryBackend<H>),
/// We know that state exists, but all calls will fail with error, because it
/// isn't locally available.
Unavailable,
}
impl<S, H: Hasher> Backend<S, H> {
/// Create new light backend.
pub fn new(blockchain: Arc<Blockchain<S>>) -> Self {
Self { blockchain, genesis_state: RwLock::new(None), import_lock: Default::default() }
}
/// Get shared blockchain reference.
pub fn blockchain(&self) -> &Arc<Blockchain<S>> {
&self.blockchain
}
}
impl<S: AuxStore, H: Hasher> AuxStore for Backend<S, H> {
fn insert_aux<
'a,
'b: 'a,
'c: 'a,
I: IntoIterator<Item = &'a (&'c [u8], &'c [u8])>,
D: IntoIterator<Item = &'a &'b [u8]>,
>(
&self,
insert: I,
delete: D,
) -> ClientResult<()> {
self.blockchain.storage().insert_aux(insert, delete)
}
fn get_aux(&self, key: &[u8]) -> ClientResult<Option<Vec<u8>>> {
self.blockchain.storage().get_aux(key)
}
}
impl<S, Block> ClientBackend<Block> for Backend<S, HashFor<Block>>
where
Block: BlockT,
S: BlockchainStorage<Block>,
Block::Hash: Ord,
{
type BlockImportOperation = ImportOperation<Block, S>;
type Blockchain = Blockchain<S>;
type State = GenesisOrUnavailableState<HashFor<Block>>;
type OffchainStorage = InMemOffchainStorage;
fn begin_operation(&self) -> ClientResult<Self::BlockImportOperation> {
Ok(ImportOperation {
header: None,
cache: Default::default(),
leaf_state: NewBlockState::Normal,
aux_ops: Vec::new(),
finalized_blocks: Vec::new(),
set_head: None,
storage_update: None,
changes_trie_config_update: None,
_phantom: Default::default(),
})
}
fn begin_state_operation(
&self,
_operation: &mut Self::BlockImportOperation,
_block: BlockId<Block>,
) -> ClientResult<()> {
Ok(())
}
fn commit_operation(&self, mut operation: Self::BlockImportOperation) -> ClientResult<()> {
if !operation.finalized_blocks.is_empty() {
for block in operation.finalized_blocks {
self.blockchain.storage().finalize_header(block)?;
}
}
if let Some(header) = operation.header {
let is_genesis_import = header.number().is_zero();
if let Some(new_config) = operation.changes_trie_config_update {
operation
.cache
.insert(well_known_cache_keys::CHANGES_TRIE_CONFIG, new_config.encode());
}
self.blockchain.storage().import_header(
header,
operation.cache,
operation.leaf_state,
operation.aux_ops,
)?;
// when importing genesis block => remember its state
if is_genesis_import {
*self.genesis_state.write() = operation.storage_update.take();
}
} else {
for (key, maybe_val) in operation.aux_ops {
match maybe_val {
Some(val) => self
.blockchain
.storage()
.insert_aux(&[(&key[..], &val[..])], std::iter::empty())?,
None =>
self.blockchain.storage().insert_aux(std::iter::empty(), &[&key[..]])?,
}
}
}
if let Some(set_head) = operation.set_head {
self.blockchain.storage().set_head(set_head)?;
}
Ok(())
}
fn finalize_block(
&self,
block: BlockId<Block>,
_justification: Option<Justification>,
) -> ClientResult<()> {
self.blockchain.storage().finalize_header(block)
}
fn append_justification(
&self,
_block: BlockId<Block>,
_justification: Justification,
) -> ClientResult<()> {
Ok(())
}
fn blockchain(&self) -> &Blockchain<S> {
&self.blockchain
}
fn usage_info(&self) -> Option<UsageInfo> {
self.blockchain.storage().usage_info()
}
fn changes_trie_storage(&self) -> Option<&dyn PrunableStateChangesTrieStorage<Block>> {
None
}
fn offchain_storage(&self) -> Option<Self::OffchainStorage> {
None
}
fn state_at(&self, block: BlockId<Block>) -> ClientResult<Self::State> {
let block_number = self.blockchain.expect_block_number_from_id(&block)?;
// special case for genesis block
if block_number.is_zero() {
if let Some(genesis_state) = self.genesis_state.read().clone() {
return Ok(GenesisOrUnavailableState::Genesis(genesis_state))
}
}
// else return unavailable state. We do not return error here, because error
// would mean that we do not know this state at all. But we know that it exists
Ok(GenesisOrUnavailableState::Unavailable)
}
fn revert(
&self,
_n: NumberFor<Block>,
_revert_finalized: bool,
) -> ClientResult<(NumberFor<Block>, HashSet<Block::Hash>)> {
Err(ClientError::NotAvailableOnLightClient)
}
fn remove_leaf_block(&self, _hash: &Block::Hash) -> ClientResult<()> {
Err(ClientError::NotAvailableOnLightClient)
}
fn get_import_lock(&self) -> &RwLock<()> {
&self.import_lock
}
}
impl<S, Block> RemoteBackend<Block> for Backend<S, HashFor<Block>>
where
Block: BlockT,
S: BlockchainStorage<Block> + 'static,
Block::Hash: Ord,
{
fn is_local_state_available(&self, block: &BlockId<Block>) -> bool {
self.genesis_state.read().is_some() &&
self.blockchain
.expect_block_number_from_id(block)
.map(|num| num.is_zero())
.unwrap_or(false)
}
fn remote_blockchain(&self) -> Arc<dyn super::blockchain::RemoteBlockchain<Block>> {
self.blockchain.clone()
}
}
impl<S, Block> BlockImportOperation<Block> for ImportOperation<Block, S>
where
Block: BlockT,
S: BlockchainStorage<Block>,
Block::Hash: Ord,
{
type State = GenesisOrUnavailableState<HashFor<Block>>;
fn state(&self) -> ClientResult<Option<&Self::State>> {
// None means 'locally-stateless' backend
Ok(None)
}
fn set_block_data(
&mut self,
header: Block::Header,
_body: Option<Vec<Block::Extrinsic>>,
_indexed_body: Option<Vec<Vec<u8>>>,
_justifications: Option<Justifications>,
state: NewBlockState,
) -> ClientResult<()> {
self.leaf_state = state;
self.header = Some(header);
Ok(())
}
fn update_cache(&mut self, cache: HashMap<well_known_cache_keys::Id, Vec<u8>>) {
self.cache = cache;
}
fn update_db_storage(
&mut self,
_update: <Self::State as StateBackend<HashFor<Block>>>::Transaction,
) -> ClientResult<()> {
// we're not storing anything locally => ignore changes
Ok(())
}
fn update_changes_trie(
&mut self,
_update: ChangesTrieTransaction<HashFor<Block>, NumberFor<Block>>,
) -> ClientResult<()> {
// we're not storing anything locally => ignore changes
Ok(())
}
fn set_genesis_state(&mut self, input: Storage, commit: bool) -> ClientResult<Block::Hash> {
check_genesis_storage(&input)?;
// changes trie configuration
let changes_trie_config = input
.top
.iter()
.find(|(k, _)| &k[..] == well_known_keys::CHANGES_TRIE_CONFIG)
.map(|(_, v)| {
Decode::decode(&mut &v[..])
.expect("changes trie configuration is encoded properly at genesis")
});
self.changes_trie_config_update = Some(changes_trie_config);
// this is only called when genesis block is imported => shouldn't be performance bottleneck
let mut storage: HashMap<Option<ChildInfo>, _> = HashMap::new();
storage.insert(None, input.top);
// create a list of children keys to re-compute roots for
let child_delta = input
.children_default
.iter()
.map(|(_storage_key, storage_child)| (&storage_child.child_info, std::iter::empty()));
// make sure to persist the child storage
for (_child_key, storage_child) in input.children_default.clone() {
storage.insert(Some(storage_child.child_info), storage_child.data);
}
let storage_update = InMemoryBackend::from(storage);
let (storage_root, _) = storage_update.full_storage_root(std::iter::empty(), child_delta);
if commit {
self.storage_update = Some(storage_update);
}
Ok(storage_root)
}
fn reset_storage(&mut self, _input: Storage) -> ClientResult<Block::Hash> {
Err(ClientError::NotAvailableOnLightClient)
}
fn insert_aux<I>(&mut self, ops: I) -> ClientResult<()>
where
I: IntoIterator<Item = (Vec<u8>, Option<Vec<u8>>)>,
{
self.aux_ops.append(&mut ops.into_iter().collect());
Ok(())
}
fn update_storage(
&mut self,
_update: StorageCollection,
_child_update: ChildStorageCollection,
) -> ClientResult<()> {
// we're not storing anything locally => ignore changes
Ok(())
}
fn mark_finalized(
&mut self,
block: BlockId<Block>,
_justifications: Option<Justification>,
) -> ClientResult<()> {
self.finalized_blocks.push(block);
Ok(())
}
fn mark_head(&mut self, block: BlockId<Block>) -> ClientResult<()> {
self.set_head = Some(block);
Ok(())
}
fn update_transaction_index(
&mut self,
_index: Vec<IndexOperation>,
) -> sp_blockchain::Result<()> {
// noop for the light client
Ok(())
}
}
impl<H: Hasher> std::fmt::Debug for GenesisOrUnavailableState<H> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
GenesisOrUnavailableState::Genesis(ref state) => state.fmt(f),
GenesisOrUnavailableState::Unavailable => write!(f, "Unavailable"),
}
}
}
impl<H: Hasher> StateBackend<H> for GenesisOrUnavailableState<H>
where
H::Out: Ord + codec::Codec,
{
type Error = ClientError;
type Transaction = <InMemoryBackend<H> as StateBackend<H>>::Transaction;
type TrieBackendStorage = <InMemoryBackend<H> as StateBackend<H>>::TrieBackendStorage;
fn storage(&self, key: &[u8]) -> ClientResult<Option<Vec<u8>>> {
match *self {
GenesisOrUnavailableState::Genesis(ref state) =>
Ok(state.storage(key).expect(IN_MEMORY_EXPECT_PROOF)),
GenesisOrUnavailableState::Unavailable => Err(ClientError::NotAvailableOnLightClient),
}
}
fn child_storage(&self, child_info: &ChildInfo, key: &[u8]) -> ClientResult<Option<Vec<u8>>> {
match *self {
GenesisOrUnavailableState::Genesis(ref state) =>
Ok(state.child_storage(child_info, key).expect(IN_MEMORY_EXPECT_PROOF)),
GenesisOrUnavailableState::Unavailable => Err(ClientError::NotAvailableOnLightClient),
}
}
fn next_storage_key(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
match *self {
GenesisOrUnavailableState::Genesis(ref state) =>
Ok(state.next_storage_key(key).expect(IN_MEMORY_EXPECT_PROOF)),
GenesisOrUnavailableState::Unavailable => Err(ClientError::NotAvailableOnLightClient),
}
}
fn next_child_storage_key(
&self,
child_info: &ChildInfo,
key: &[u8],
) -> Result<Option<Vec<u8>>, Self::Error> {
match *self {
GenesisOrUnavailableState::Genesis(ref state) =>
Ok(state.next_child_storage_key(child_info, key).expect(IN_MEMORY_EXPECT_PROOF)),
GenesisOrUnavailableState::Unavailable => Err(ClientError::NotAvailableOnLightClient),
}
}
fn for_keys_with_prefix<A: FnMut(&[u8])>(&self, prefix: &[u8], action: A) {
match *self {
GenesisOrUnavailableState::Genesis(ref state) =>
state.for_keys_with_prefix(prefix, action),
GenesisOrUnavailableState::Unavailable => (),
}
}
fn for_key_values_with_prefix<A: FnMut(&[u8], &[u8])>(&self, prefix: &[u8], action: A) {
match *self {
GenesisOrUnavailableState::Genesis(ref state) =>
state.for_key_values_with_prefix(prefix, action),
GenesisOrUnavailableState::Unavailable => (),
}
}
fn apply_to_key_values_while<A: FnMut(Vec<u8>, Vec<u8>) -> bool>(
&self,
child_info: Option<&ChildInfo>,
prefix: Option<&[u8]>,
start_at: Option<&[u8]>,
action: A,
allow_missing: bool,
) -> ClientResult<bool> {
match *self {
GenesisOrUnavailableState::Genesis(ref state) => Ok(state
.apply_to_key_values_while(child_info, prefix, start_at, action, allow_missing)
.expect(IN_MEMORY_EXPECT_PROOF)),
GenesisOrUnavailableState::Unavailable => Err(ClientError::NotAvailableOnLightClient),
}
}
fn apply_to_keys_while<A: FnMut(&[u8]) -> bool>(
&self,
child_info: Option<&ChildInfo>,
prefix: Option<&[u8]>,
action: A,
) {
match *self {
GenesisOrUnavailableState::Genesis(ref state) =>
state.apply_to_keys_while(child_info, prefix, action),
GenesisOrUnavailableState::Unavailable => (),
}
}
fn for_child_keys_with_prefix<A: FnMut(&[u8])>(
&self,
child_info: &ChildInfo,
prefix: &[u8],
action: A,
) {
match *self {
GenesisOrUnavailableState::Genesis(ref state) =>
state.for_child_keys_with_prefix(child_info, prefix, action),
GenesisOrUnavailableState::Unavailable => (),
}
}
fn storage_root<'a>(
&self,
delta: impl Iterator<Item = (&'a [u8], Option<&'a [u8]>)>,
) -> (H::Out, Self::Transaction)
where
H::Out: Ord,
{
match *self {
GenesisOrUnavailableState::Genesis(ref state) => state.storage_root(delta),
GenesisOrUnavailableState::Unavailable => Default::default(),
}
}
fn child_storage_root<'a>(
&self,
child_info: &ChildInfo,
delta: impl Iterator<Item = (&'a [u8], Option<&'a [u8]>)>,
) -> (H::Out, bool, Self::Transaction)
where
H::Out: Ord,
{
match *self {
GenesisOrUnavailableState::Genesis(ref state) => {
let (root, is_equal, _) = state.child_storage_root(child_info, delta);
(root, is_equal, Default::default())
},
GenesisOrUnavailableState::Unavailable => (H::Out::default(), true, Default::default()),
}
}
fn pairs(&self) -> Vec<(Vec<u8>, Vec<u8>)> {
match *self {
GenesisOrUnavailableState::Genesis(ref state) => state.pairs(),
GenesisOrUnavailableState::Unavailable => Vec::new(),
}
}
fn keys(&self, prefix: &[u8]) -> Vec<Vec<u8>> {
match *self {
GenesisOrUnavailableState::Genesis(ref state) => state.keys(prefix),
GenesisOrUnavailableState::Unavailable => Vec::new(),
}
}
fn register_overlay_stats(&self, _stats: &sp_state_machine::StateMachineStats) {}
fn usage_info(&self) -> sp_state_machine::UsageInfo {
sp_state_machine::UsageInfo::empty()
}
fn as_trie_backend(&self) -> Option<&TrieBackend<Self::TrieBackendStorage, H>> {
match self {
GenesisOrUnavailableState::Genesis(ref state) => state.as_trie_backend(),
GenesisOrUnavailableState::Unavailable => None,
}
}
}
-219
View File
@@ -1,219 +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/>.
//! Light client blockchain backend. Only stores headers and justifications of recent
//! blocks. CHT roots are stored for headers of ancient blocks.
use std::sync::Arc;
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero},
Justifications,
};
use sc_client_api::light::RemoteHeaderRequest;
pub use sc_client_api::{
backend::{AuxStore, NewBlockState, ProvideChtRoots},
blockchain::{
well_known_cache_keys, Backend as BlockchainBackend, BlockStatus, Cache as BlockchainCache,
HeaderBackend as BlockchainHeaderBackend, Info as BlockchainInfo, ProvideCache,
},
cht,
light::{LocalOrRemote, RemoteBlockchain, Storage},
};
use sp_blockchain::{
CachedHeaderMetadata, Error as ClientError, HeaderMetadata, Result as ClientResult,
};
/// Light client blockchain.
pub struct Blockchain<S> {
storage: S,
}
impl<S> Blockchain<S> {
/// 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<S, Block> BlockchainHeaderBackend<Block> for Blockchain<S>
where
Block: BlockT,
S: Storage<Block>,
{
fn header(&self, id: BlockId<Block>) -> ClientResult<Option<Block::Header>> {
match RemoteBlockchain::header(self, id)? {
LocalOrRemote::Local(header) => Ok(Some(header)),
LocalOrRemote::Remote(_) => Err(ClientError::NotAvailableOnLightClient),
LocalOrRemote::Unknown => Ok(None),
}
}
fn info(&self) -> BlockchainInfo<Block> {
self.storage.info()
}
fn status(&self, id: BlockId<Block>) -> ClientResult<BlockStatus> {
self.storage.status(id)
}
fn number(&self, hash: Block::Hash) -> ClientResult<Option<NumberFor<Block>>> {
self.storage.number(hash)
}
fn hash(
&self,
number: <<Block as BlockT>::Header as HeaderT>::Number,
) -> ClientResult<Option<Block::Hash>> {
self.storage.hash(number)
}
}
impl<S, Block> HeaderMetadata<Block> for Blockchain<S>
where
Block: BlockT,
S: Storage<Block>,
{
type Error = ClientError;
fn header_metadata(
&self,
hash: Block::Hash,
) -> Result<CachedHeaderMetadata<Block>, Self::Error> {
self.storage.header_metadata(hash)
}
fn insert_header_metadata(&self, hash: Block::Hash, metadata: CachedHeaderMetadata<Block>) {
self.storage.insert_header_metadata(hash, metadata)
}
fn remove_header_metadata(&self, hash: Block::Hash) {
self.storage.remove_header_metadata(hash)
}
}
impl<S, Block> BlockchainBackend<Block> for Blockchain<S>
where
Block: BlockT,
S: Storage<Block>,
{
fn body(&self, _id: BlockId<Block>) -> ClientResult<Option<Vec<Block::Extrinsic>>> {
Err(ClientError::NotAvailableOnLightClient)
}
fn justifications(&self, _id: BlockId<Block>) -> ClientResult<Option<Justifications>> {
Err(ClientError::NotAvailableOnLightClient)
}
fn last_finalized(&self) -> ClientResult<Block::Hash> {
self.storage.last_finalized()
}
fn cache(&self) -> Option<Arc<dyn BlockchainCache<Block>>> {
self.storage.cache()
}
fn leaves(&self) -> ClientResult<Vec<Block::Hash>> {
Err(ClientError::NotAvailableOnLightClient)
}
fn children(&self, _parent_hash: Block::Hash) -> ClientResult<Vec<Block::Hash>> {
Err(ClientError::NotAvailableOnLightClient)
}
fn indexed_transaction(&self, _hash: &Block::Hash) -> ClientResult<Option<Vec<u8>>> {
Err(ClientError::NotAvailableOnLightClient)
}
fn block_indexed_body(
&self,
_id: BlockId<Block>,
) -> sp_blockchain::Result<Option<Vec<Vec<u8>>>> {
Err(ClientError::NotAvailableOnLightClient)
}
}
impl<S: Storage<Block>, Block: BlockT> ProvideCache<Block> for Blockchain<S> {
fn cache(&self) -> Option<Arc<dyn BlockchainCache<Block>>> {
self.storage.cache()
}
}
impl<S, Block: BlockT> RemoteBlockchain<Block> for Blockchain<S>
where
S: Storage<Block>,
{
fn header(
&self,
id: BlockId<Block>,
) -> ClientResult<LocalOrRemote<Block::Header, RemoteHeaderRequest<Block::Header>>> {
// 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: match self.storage.header_cht_root(cht::size(), number)? {
Some(cht_root) => cht_root,
None => return Ok(LocalOrRemote::Unknown),
},
block: number,
retry_count: None,
}))
}
}
impl<S: Storage<Block>, Block: BlockT> ProvideChtRoots<Block> for Blockchain<S> {
fn header_cht_root(
&self,
cht_size: NumberFor<Block>,
block: NumberFor<Block>,
) -> sp_blockchain::Result<Option<Block::Hash>> {
self.storage().header_cht_root(cht_size, block)
}
fn changes_trie_cht_root(
&self,
cht_size: NumberFor<Block>,
block: NumberFor<Block>,
) -> sp_blockchain::Result<Option<Block::Hash>> {
self.storage().changes_trie_cht_root(cht_size, block)
}
}
-206
View File
@@ -1,206 +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/>.
//! Methods that light client could use to execute runtime calls.
use std::{cell::RefCell, panic::UnwindSafe, result, sync::Arc};
use codec::{Decode, Encode};
use hash_db::Hasher;
use sp_core::{
convert_hash,
traits::{CodeExecutor, SpawnNamed},
NativeOrEncoded,
};
use sp_externalities::Extensions;
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, Header as HeaderT},
};
use sp_state_machine::{
create_proof_check_backend, execution_proof_check_on_trie_backend, ExecutionManager,
ExecutionStrategy, OverlayedChanges, StorageProof,
};
use sp_api::{ProofRecorder, StorageTransactionCache};
use sp_blockchain::{Error as ClientError, Result as ClientResult};
use sc_client_api::{
backend::RemoteBackend, call_executor::CallExecutor, light::RemoteCallRequest,
};
use sc_executor::RuntimeVersion;
/// Call executor that is able to execute calls only on genesis state.
///
/// Trying to execute call on non-genesis state leads to error.
pub struct GenesisCallExecutor<B, L> {
backend: Arc<B>,
local: L,
}
impl<B, L> GenesisCallExecutor<B, L> {
/// Create new genesis call executor.
pub fn new(backend: Arc<B>, local: L) -> Self {
Self { backend, local }
}
}
impl<B, L: Clone> Clone for GenesisCallExecutor<B, L> {
fn clone(&self) -> Self {
GenesisCallExecutor { backend: self.backend.clone(), local: self.local.clone() }
}
}
impl<Block, B, Local> CallExecutor<Block> for GenesisCallExecutor<B, Local>
where
Block: BlockT,
B: RemoteBackend<Block>,
Local: CallExecutor<Block>,
{
type Error = ClientError;
type Backend = B;
fn call(
&self,
id: &BlockId<Block>,
method: &str,
call_data: &[u8],
strategy: ExecutionStrategy,
extensions: Option<Extensions>,
) -> ClientResult<Vec<u8>> {
if self.backend.is_local_state_available(id) {
self.local.call(id, method, call_data, strategy, extensions)
} else {
Err(ClientError::NotAvailableOnLightClient)
}
}
fn contextual_call<
EM: Fn(
Result<NativeOrEncoded<R>, Self::Error>,
Result<NativeOrEncoded<R>, Self::Error>,
) -> Result<NativeOrEncoded<R>, Self::Error>,
R: Encode + Decode + PartialEq,
NC: FnOnce() -> result::Result<R, sp_api::ApiError> + UnwindSafe,
>(
&self,
at: &BlockId<Block>,
method: &str,
call_data: &[u8],
changes: &RefCell<OverlayedChanges>,
_: Option<&RefCell<StorageTransactionCache<Block, B::State>>>,
_manager: ExecutionManager<EM>,
native_call: Option<NC>,
recorder: &Option<ProofRecorder<Block>>,
extensions: Option<Extensions>,
) -> ClientResult<NativeOrEncoded<R>>
where
ExecutionManager<EM>: Clone,
{
// there's no actual way/need to specify native/wasm execution strategy on light node
// => we can safely ignore passed values
if self.backend.is_local_state_available(at) {
CallExecutor::contextual_call::<
fn(
Result<NativeOrEncoded<R>, Local::Error>,
Result<NativeOrEncoded<R>, Local::Error>,
) -> Result<NativeOrEncoded<R>, Local::Error>,
_,
NC,
>(
&self.local,
at,
method,
call_data,
changes,
None,
ExecutionManager::NativeWhenPossible,
native_call,
recorder,
extensions,
)
} else {
Err(ClientError::NotAvailableOnLightClient)
}
}
fn prove_execution(
&self,
at: &BlockId<Block>,
method: &str,
call_data: &[u8],
) -> ClientResult<(Vec<u8>, StorageProof)> {
if self.backend.is_local_state_available(at) {
self.local.prove_execution(at, method, call_data)
} else {
Err(ClientError::NotAvailableOnLightClient)
}
}
fn runtime_version(&self, id: &BlockId<Block>) -> ClientResult<RuntimeVersion> {
if self.backend.is_local_state_available(id) {
self.local.runtime_version(id)
} else {
Err(ClientError::NotAvailableOnLightClient)
}
}
}
/// Check remote contextual execution proof using given backend.
///
/// Proof should include the method execution proof.
pub fn check_execution_proof<Header, E, H>(
executor: &E,
spawn_handle: Box<dyn SpawnNamed>,
request: &RemoteCallRequest<Header>,
remote_proof: StorageProof,
) -> ClientResult<Vec<u8>>
where
Header: HeaderT<Hash = H::Out>,
E: CodeExecutor + Clone + 'static,
H: Hasher,
H::Out: Ord + codec::Codec + 'static,
{
let local_state_root = request.header.state_root();
let root: H::Out = convert_hash(&local_state_root);
// prepare execution environment
let mut changes = OverlayedChanges::default();
let trie_backend = create_proof_check_backend(root, remote_proof)?;
// TODO: Remove when solved: https://github.com/paritytech/substrate/issues/5047
let backend_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(&trie_backend);
let runtime_code = backend_runtime_code
.runtime_code()
.map_err(|_e| ClientError::RuntimeCodeMissing)?;
// execute method
execution_proof_check_on_trie_backend::<H, Header::Number, _, _>(
&trie_backend,
&mut changes,
executor,
spawn_handle,
&request.method,
&request.call_data,
&runtime_code,
)
.map_err(Into::into)
}
-41
View File
@@ -1,41 +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/>.
//! Light client components.
use sp_runtime::traits::{Block as BlockT, HashFor};
use std::sync::Arc;
pub mod backend;
pub mod blockchain;
pub mod call_executor;
pub use backend::*;
pub use blockchain::*;
pub use call_executor::*;
use sc_client_api::light::Storage as BlockchainStorage;
/// Create an instance of light client backend.
pub fn new_light_backend<B, S>(blockchain: Arc<Blockchain<S>>) -> Arc<Backend<S, HashFor<B>>>
where
B: BlockT,
S: BlockchainStorage<B>,
{
Arc::new(Backend::new(blockchain))
}
+8 -42
View File
@@ -20,14 +20,14 @@ use crate::{
bitswap::Bitswap,
config::ProtocolId,
discovery::{DiscoveryBehaviour, DiscoveryConfig, DiscoveryOut},
light_client_requests, peer_info,
peer_info,
protocol::{message::Roles, CustomMessageOutcome, NotificationsSink, Protocol},
request_responses, DhtEvent, ObservedRole,
};
use bytes::Bytes;
use codec::Encode;
use futures::{channel::oneshot, stream::StreamExt};
use futures::channel::oneshot;
use libp2p::{
core::{Multiaddr, PeerId, PublicKey},
identify::IdentifyInfo,
@@ -76,10 +76,6 @@ pub struct Behaviour<B: BlockT> {
#[behaviour(ignore)]
events: VecDeque<BehaviourOut<B>>,
/// Light client request handling.
#[behaviour(ignore)]
light_client_request_sender: light_client_requests::sender::LightClientRequestSender<B>,
/// Protocol name used to send out block requests via
/// [`request_responses::RequestResponsesBehaviour`].
#[behaviour(ignore)]
@@ -198,7 +194,6 @@ impl<B: BlockT> Behaviour<B> {
substrate: Protocol<B>,
user_agent: String,
local_public_key: PublicKey,
light_client_request_sender: light_client_requests::sender::LightClientRequestSender<B>,
disco_config: DiscoveryConfig,
block_request_protocol_config: request_responses::ProtocolConfig,
state_request_protocol_config: request_responses::ProtocolConfig,
@@ -233,7 +228,6 @@ impl<B: BlockT> Behaviour<B> {
request_response_protocols.into_iter(),
peerset,
)?,
light_client_request_sender,
events: VecDeque::new(),
block_request_protocol_name,
state_request_protocol_name,
@@ -316,14 +310,6 @@ impl<B: BlockT> Behaviour<B> {
pub fn put_value(&mut self, key: record::Key, value: Vec<u8>) {
self.discovery.put_value(key, value);
}
/// Issue a light client request.
pub fn light_client_request(
&mut self,
r: light_client_requests::sender::Request<B>,
) -> Result<(), light_client_requests::sender::SendRequestError> {
self.light_client_request_sender.request(r)
}
}
fn reported_roles_to_observed_role(roles: Roles) -> ObservedRole {
@@ -436,17 +422,11 @@ impl<B: BlockT> NetworkBehaviourEventProcess<CustomMessageOutcome<B>> for Behavi
CustomMessageOutcome::NotificationsReceived { remote, messages } => {
self.events.push_back(BehaviourOut::NotificationsReceived { remote, messages });
},
CustomMessageOutcome::PeerNewBest(peer_id, number) => {
self.light_client_request_sender.update_best_block(&peer_id, number);
},
CustomMessageOutcome::SyncConnected(peer_id) => {
self.light_client_request_sender.inject_connected(peer_id);
self.events.push_back(BehaviourOut::SyncConnected(peer_id))
},
CustomMessageOutcome::SyncDisconnected(peer_id) => {
self.light_client_request_sender.inject_disconnected(peer_id);
self.events.push_back(BehaviourOut::SyncDisconnected(peer_id))
},
CustomMessageOutcome::PeerNewBest(_peer_id, _number) => {},
CustomMessageOutcome::SyncConnected(peer_id) =>
self.events.push_back(BehaviourOut::SyncConnected(peer_id)),
CustomMessageOutcome::SyncDisconnected(peer_id) =>
self.events.push_back(BehaviourOut::SyncDisconnected(peer_id)),
CustomMessageOutcome::None => {},
}
}
@@ -534,23 +514,9 @@ impl<B: BlockT> NetworkBehaviourEventProcess<DiscoveryOut> for Behaviour<B> {
impl<B: BlockT> Behaviour<B> {
fn poll<TEv>(
&mut self,
cx: &mut Context,
_cx: &mut Context,
_: &mut impl PollParameters,
) -> Poll<NetworkBehaviourAction<TEv, BehaviourOut<B>>> {
use light_client_requests::sender::OutEvent;
while let Poll::Ready(Some(event)) = self.light_client_request_sender.poll_next_unpin(cx) {
match event {
OutEvent::SendRequest { target, request, pending_response, protocol_name } =>
self.request_responses.send_request(
&target,
&protocol_name,
request,
pending_response,
IfDisconnected::ImmediateError,
),
}
}
if let Some(event) = self.events.pop_front() {
return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event))
}
-6
View File
@@ -23,7 +23,6 @@
pub use crate::{
chain::Client,
on_demand_layer::{AlwaysBadChecker, OnDemand},
request_responses::{
IncomingRequest, OutgoingResponse, ProtocolConfig as RequestResponseConfig,
},
@@ -83,11 +82,6 @@ pub struct Params<B: BlockT, H: ExHashT> {
/// Client that contains the blockchain.
pub chain: Arc<dyn Client<B>>,
/// The `OnDemand` object acts as a "receiver" for block data requests from the client.
/// If `Some`, the network worker will process these requests and answer them.
/// Normally used only for light clients.
pub on_demand: Option<Arc<OnDemand<B>>>,
/// Pool of transactions.
///
/// The network worker will fetch transactions from this object in order to propagate them on
-1
View File
@@ -247,7 +247,6 @@
mod behaviour;
mod chain;
mod discovery;
mod on_demand_layer;
mod peer_info;
mod protocol;
mod request_responses;
@@ -20,8 +20,6 @@
/// For incoming light client requests.
pub mod handler;
/// For outgoing light client requests.
pub mod sender;
use crate::{config::ProtocolId, request_responses::ProtocolConfig};
@@ -47,269 +45,3 @@ pub fn generate_protocol_config(protocol_id: &ProtocolId) -> ProtocolConfig {
inbound_queue: None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{config::ProtocolId, request_responses::IncomingRequest};
use assert_matches::assert_matches;
use futures::{
channel::oneshot,
executor::{block_on, LocalPool},
prelude::*,
task::Spawn,
};
use libp2p::PeerId;
use sc_client_api::{
light::{
self, ChangesProof, RemoteBodyRequest, RemoteCallRequest, RemoteChangesRequest,
RemoteHeaderRequest, RemoteReadRequest,
},
FetchChecker, RemoteReadChildRequest, StorageProof,
};
use sp_blockchain::Error as ClientError;
use sp_core::storage::ChildInfo;
use sp_runtime::{
generic::Header,
traits::{BlakeTwo256, Block as BlockT, NumberFor},
};
use std::{collections::HashMap, sync::Arc};
pub struct DummyFetchChecker<B> {
pub ok: bool,
pub _mark: std::marker::PhantomData<B>,
}
impl<B: BlockT> FetchChecker<B> for DummyFetchChecker<B> {
fn check_header_proof(
&self,
_request: &RemoteHeaderRequest<B::Header>,
header: Option<B::Header>,
_remote_proof: StorageProof,
) -> Result<B::Header, ClientError> {
match self.ok {
true if header.is_some() => Ok(header.unwrap()),
_ => Err(ClientError::Backend("Test error".into())),
}
}
fn check_read_proof(
&self,
request: &RemoteReadRequest<B::Header>,
_: StorageProof,
) -> Result<HashMap<Vec<u8>, Option<Vec<u8>>>, ClientError> {
match self.ok {
true => Ok(request.keys.iter().cloned().map(|k| (k, Some(vec![42]))).collect()),
false => Err(ClientError::Backend("Test error".into())),
}
}
fn check_read_child_proof(
&self,
request: &RemoteReadChildRequest<B::Header>,
_: StorageProof,
) -> Result<HashMap<Vec<u8>, Option<Vec<u8>>>, ClientError> {
match self.ok {
true => Ok(request.keys.iter().cloned().map(|k| (k, Some(vec![42]))).collect()),
false => Err(ClientError::Backend("Test error".into())),
}
}
fn check_execution_proof(
&self,
_: &RemoteCallRequest<B::Header>,
_: StorageProof,
) -> Result<Vec<u8>, ClientError> {
match self.ok {
true => Ok(vec![42]),
false => Err(ClientError::Backend("Test error".into())),
}
}
fn check_changes_proof(
&self,
_: &RemoteChangesRequest<B::Header>,
_: ChangesProof<B::Header>,
) -> Result<Vec<(NumberFor<B>, u32)>, ClientError> {
match self.ok {
true => Ok(vec![(100u32.into(), 2)]),
false => Err(ClientError::Backend("Test error".into())),
}
}
fn check_body_proof(
&self,
_: &RemoteBodyRequest<B::Header>,
body: Vec<B::Extrinsic>,
) -> Result<Vec<B::Extrinsic>, ClientError> {
match self.ok {
true => Ok(body),
false => Err(ClientError::Backend("Test error".into())),
}
}
}
pub fn protocol_id() -> ProtocolId {
ProtocolId::from("test")
}
pub fn peerset() -> (sc_peerset::Peerset, sc_peerset::PeersetHandle) {
let cfg = sc_peerset::SetConfig {
in_peers: 128,
out_peers: 128,
bootnodes: Default::default(),
reserved_only: false,
reserved_nodes: Default::default(),
};
sc_peerset::Peerset::from_config(sc_peerset::PeersetConfig { sets: vec![cfg] })
}
pub fn dummy_header() -> sp_test_primitives::Header {
sp_test_primitives::Header {
parent_hash: Default::default(),
number: 0,
state_root: Default::default(),
extrinsics_root: Default::default(),
digest: Default::default(),
}
}
type Block =
sp_runtime::generic::Block<Header<u64, BlakeTwo256>, substrate_test_runtime::Extrinsic>;
fn send_receive(request: sender::Request<Block>, pool: &LocalPool) {
let client = Arc::new(substrate_test_runtime_client::new());
let (handler, protocol_config) =
handler::LightClientRequestHandler::new(&protocol_id(), client);
pool.spawner().spawn_obj(handler.run().boxed().into()).unwrap();
let (_peer_set, peer_set_handle) = peerset();
let mut sender = sender::LightClientRequestSender::<Block>::new(
&protocol_id(),
Arc::new(crate::light_client_requests::tests::DummyFetchChecker {
ok: true,
_mark: std::marker::PhantomData,
}),
peer_set_handle,
);
sender.inject_connected(PeerId::random());
sender.request(request).unwrap();
let sender::OutEvent::SendRequest { pending_response, request, .. } =
block_on(sender.next()).unwrap();
let (tx, rx) = oneshot::channel();
block_on(protocol_config.inbound_queue.unwrap().send(IncomingRequest {
peer: PeerId::random(),
payload: request,
pending_response: tx,
}))
.unwrap();
pool.spawner()
.spawn_obj(
async move {
pending_response.send(Ok(rx.await.unwrap().result.unwrap())).unwrap();
}
.boxed()
.into(),
)
.unwrap();
pool.spawner()
.spawn_obj(sender.for_each(|_| future::ready(())).boxed().into())
.unwrap();
}
#[test]
fn send_receive_call() {
let chan = oneshot::channel();
let request = light::RemoteCallRequest {
block: Default::default(),
header: dummy_header(),
method: "test".into(),
call_data: vec![],
retry_count: None,
};
let mut pool = LocalPool::new();
send_receive(sender::Request::Call { request, sender: chan.0 }, &pool);
assert_eq!(vec![42], pool.run_until(chan.1).unwrap().unwrap());
// ^--- from `DummyFetchChecker::check_execution_proof`
}
#[test]
fn send_receive_read() {
let chan = oneshot::channel();
let request = light::RemoteReadRequest {
header: dummy_header(),
block: Default::default(),
keys: vec![b":key".to_vec()],
retry_count: None,
};
let mut pool = LocalPool::new();
send_receive(sender::Request::Read { request, sender: chan.0 }, &pool);
assert_eq!(
Some(vec![42]),
pool.run_until(chan.1).unwrap().unwrap().remove(&b":key"[..]).unwrap()
);
// ^--- from `DummyFetchChecker::check_read_proof`
}
#[test]
fn send_receive_read_child() {
let chan = oneshot::channel();
let child_info = ChildInfo::new_default(&b":child_storage:default:sub"[..]);
let request = light::RemoteReadChildRequest {
header: dummy_header(),
block: Default::default(),
storage_key: child_info.prefixed_storage_key(),
keys: vec![b":key".to_vec()],
retry_count: None,
};
let mut pool = LocalPool::new();
send_receive(sender::Request::ReadChild { request, sender: chan.0 }, &pool);
assert_eq!(
Some(vec![42]),
pool.run_until(chan.1).unwrap().unwrap().remove(&b":key"[..]).unwrap()
);
// ^--- from `DummyFetchChecker::check_read_child_proof`
}
#[test]
fn send_receive_header() {
sp_tracing::try_init_simple();
let chan = oneshot::channel();
let request = light::RemoteHeaderRequest {
cht_root: Default::default(),
block: 1,
retry_count: None,
};
let mut pool = LocalPool::new();
send_receive(sender::Request::Header { request, sender: chan.0 }, &pool);
// The remote does not know block 1:
assert_matches!(pool.run_until(chan.1).unwrap(), Err(ClientError::RemoteFetchFailed));
}
#[test]
fn send_receive_changes() {
let chan = oneshot::channel();
let request = light::RemoteChangesRequest {
changes_trie_configs: vec![sp_core::ChangesTrieConfigurationRange {
zero: (0, Default::default()),
end: None,
config: Some(sp_core::ChangesTrieConfiguration::new(4, 2)),
}],
first_block: (1, Default::default()),
last_block: (100, Default::default()),
max_block: (100, Default::default()),
tries_roots: (1, Default::default(), Vec::new()),
key: Vec::new(),
storage_key: None,
retry_count: None,
};
let mut pool = LocalPool::new();
send_receive(sender::Request::Changes { request, sender: chan.0 }, &pool);
assert_eq!(vec![(100, 2)], pool.run_until(chan.1).unwrap().unwrap());
// ^--- from `DummyFetchChecker::check_changes_proof`
}
}
@@ -32,17 +32,14 @@ use codec::{self, Decode, Encode};
use futures::{channel::mpsc, prelude::*};
use log::{debug, trace};
use prost::Message;
use sc_client_api::{light, StorageProof};
use sc_client_api::StorageProof;
use sc_peerset::ReputationChange;
use sp_core::{
hexdisplay::HexDisplay,
storage::{ChildInfo, ChildType, PrefixedStorageKey, StorageKey},
storage::{ChildInfo, ChildType, PrefixedStorageKey},
};
use sp_runtime::{
generic::BlockId,
traits::{Block, Zero},
};
use std::{collections::BTreeMap, sync::Arc};
use sp_runtime::{generic::BlockId, traits::Block};
use std::sync::Arc;
const LOG_TARGET: &str = "light-client-request-handler";
@@ -137,12 +134,12 @@ impl<B: Block> LightClientRequestHandler<B> {
self.on_remote_call_request(&peer, r)?,
Some(schema::v1::light::request::Request::RemoteReadRequest(r)) =>
self.on_remote_read_request(&peer, r)?,
Some(schema::v1::light::request::Request::RemoteHeaderRequest(r)) =>
self.on_remote_header_request(&peer, r)?,
Some(schema::v1::light::request::Request::RemoteHeaderRequest(_r)) =>
return Err(HandleRequestError::BadRequest("Not supported.")),
Some(schema::v1::light::request::Request::RemoteReadChildRequest(r)) =>
self.on_remote_read_child_request(&peer, r)?,
Some(schema::v1::light::request::Request::RemoteChangesRequest(r)) =>
self.on_remote_changes_request(&peer, r)?,
Some(schema::v1::light::request::Request::RemoteChangesRequest(_r)) =>
return Err(HandleRequestError::BadRequest("Not supported.")),
None =>
return Err(HandleRequestError::BadRequest("Remote request without request data.")),
};
@@ -285,106 +282,6 @@ impl<B: Block> LightClientRequestHandler<B> {
Ok(schema::v1::light::Response { response: Some(response) })
}
fn on_remote_header_request(
&mut self,
peer: &PeerId,
request: &schema::v1::light::RemoteHeaderRequest,
) -> Result<schema::v1::light::Response, HandleRequestError> {
trace!("Remote header proof request from {} ({:?}).", peer, request.block);
let block = Decode::decode(&mut request.block.as_ref())?;
let (header, proof) = match self.client.header_proof(&BlockId::Number(block)) {
Ok((header, proof)) => (header.encode(), proof),
Err(error) => {
trace!(
"Remote header proof request from {} ({:?}) failed with: {}.",
peer,
request.block,
error
);
(Default::default(), StorageProof::empty())
},
};
let response = {
let r = schema::v1::light::RemoteHeaderResponse { header, proof: proof.encode() };
schema::v1::light::response::Response::RemoteHeaderResponse(r)
};
Ok(schema::v1::light::Response { response: Some(response) })
}
fn on_remote_changes_request(
&mut self,
peer: &PeerId,
request: &schema::v1::light::RemoteChangesRequest,
) -> Result<schema::v1::light::Response, HandleRequestError> {
trace!(
"Remote changes proof request from {} for key {} ({:?}..{:?}).",
peer,
if !request.storage_key.is_empty() {
format!(
"{} : {}",
HexDisplay::from(&request.storage_key),
HexDisplay::from(&request.key)
)
} else {
HexDisplay::from(&request.key).to_string()
},
request.first,
request.last,
);
let first = Decode::decode(&mut request.first.as_ref())?;
let last = Decode::decode(&mut request.last.as_ref())?;
let min = Decode::decode(&mut request.min.as_ref())?;
let max = Decode::decode(&mut request.max.as_ref())?;
let key = StorageKey(request.key.clone());
let storage_key = if request.storage_key.is_empty() {
None
} else {
Some(PrefixedStorageKey::new_ref(&request.storage_key))
};
let proof =
match self.client.key_changes_proof(first, last, min, max, storage_key, &key) {
Ok(proof) => proof,
Err(error) => {
trace!(
"Remote changes proof request from {} for key {} ({:?}..{:?}) failed with: {}.",
peer,
format!("{} : {}", HexDisplay::from(&request.storage_key), HexDisplay::from(&key.0)),
request.first,
request.last,
error,
);
light::ChangesProof::<B::Header> {
max_block: Zero::zero(),
proof: Vec::new(),
roots: BTreeMap::new(),
roots_proof: StorageProof::empty(),
}
},
};
let response = {
let r = schema::v1::light::RemoteChangesResponse {
max: proof.max_block.encode(),
proof: proof.proof,
roots: proof
.roots
.into_iter()
.map(|(k, v)| schema::v1::light::Pair { fst: k.encode(), snd: v.encode() })
.collect(),
roots_proof: proof.roots_proof.encode(),
};
schema::v1::light::response::Response::RemoteChangesResponse(r)
};
Ok(schema::v1::light::Response { response: Some(response) })
}
}
#[derive(derive_more::Display, derive_more::From)]
File diff suppressed because it is too large Load Diff
@@ -1,241 +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/>.
//! On-demand requests service.
use crate::light_client_requests;
use futures::{channel::oneshot, prelude::*};
use parking_lot::Mutex;
use sc_client_api::{
ChangesProof, FetchChecker, Fetcher, RemoteBodyRequest, RemoteCallRequest,
RemoteChangesRequest, RemoteHeaderRequest, RemoteReadChildRequest, RemoteReadRequest,
StorageProof,
};
use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender};
use sp_blockchain::Error as ClientError;
use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor};
use std::{
collections::HashMap,
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
/// Implements the `Fetcher` trait of the client. Makes it possible for the light client to perform
/// network requests for some state.
///
/// This implementation stores all the requests in a queue. The network, in parallel, is then
/// responsible for pulling elements out of that queue and fulfilling them.
pub struct OnDemand<B: BlockT> {
/// Objects that checks whether what has been retrieved is correct.
checker: Arc<dyn FetchChecker<B>>,
/// Queue of requests. Set to `Some` at initialization, then extracted by the network.
///
/// Note that a better alternative would be to use a MPMC queue here, and add a `poll` method
/// from the `OnDemand`. However there exists no popular implementation of MPMC channels in
/// asynchronous Rust at the moment
requests_queue:
Mutex<Option<TracingUnboundedReceiver<light_client_requests::sender::Request<B>>>>,
/// Sending side of `requests_queue`.
requests_send: TracingUnboundedSender<light_client_requests::sender::Request<B>>,
}
#[derive(Debug, thiserror::Error)]
#[error("AlwaysBadChecker")]
struct ErrorAlwaysBadChecker;
impl Into<ClientError> for ErrorAlwaysBadChecker {
fn into(self) -> ClientError {
ClientError::Application(Box::new(self))
}
}
/// Dummy implementation of `FetchChecker` that always assumes that responses are bad.
///
/// Considering that it is the responsibility of the client to build the fetcher, it can use this
/// implementation if it knows that it will never perform any request.
#[derive(Default, Clone)]
pub struct AlwaysBadChecker;
impl<Block: BlockT> FetchChecker<Block> for AlwaysBadChecker {
fn check_header_proof(
&self,
_request: &RemoteHeaderRequest<Block::Header>,
_remote_header: Option<Block::Header>,
_remote_proof: StorageProof,
) -> Result<Block::Header, ClientError> {
Err(ErrorAlwaysBadChecker.into())
}
fn check_read_proof(
&self,
_request: &RemoteReadRequest<Block::Header>,
_remote_proof: StorageProof,
) -> Result<HashMap<Vec<u8>, Option<Vec<u8>>>, ClientError> {
Err(ErrorAlwaysBadChecker.into())
}
fn check_read_child_proof(
&self,
_request: &RemoteReadChildRequest<Block::Header>,
_remote_proof: StorageProof,
) -> Result<HashMap<Vec<u8>, Option<Vec<u8>>>, ClientError> {
Err(ErrorAlwaysBadChecker.into())
}
fn check_execution_proof(
&self,
_request: &RemoteCallRequest<Block::Header>,
_remote_proof: StorageProof,
) -> Result<Vec<u8>, ClientError> {
Err(ErrorAlwaysBadChecker.into())
}
fn check_changes_proof(
&self,
_request: &RemoteChangesRequest<Block::Header>,
_remote_proof: ChangesProof<Block::Header>,
) -> Result<Vec<(NumberFor<Block>, u32)>, ClientError> {
Err(ErrorAlwaysBadChecker.into())
}
fn check_body_proof(
&self,
_request: &RemoteBodyRequest<Block::Header>,
_body: Vec<Block::Extrinsic>,
) -> Result<Vec<Block::Extrinsic>, ClientError> {
Err(ErrorAlwaysBadChecker.into())
}
}
impl<B: BlockT> OnDemand<B>
where
B::Header: HeaderT,
{
/// Creates new on-demand service.
pub fn new(checker: Arc<dyn FetchChecker<B>>) -> Self {
let (requests_send, requests_queue) = tracing_unbounded("mpsc_ondemand");
let requests_queue = Mutex::new(Some(requests_queue));
Self { checker, requests_queue, requests_send }
}
/// Get checker reference.
pub fn checker(&self) -> &Arc<dyn FetchChecker<B>> {
&self.checker
}
/// Extracts the queue of requests.
///
/// Whenever one of the methods of the `Fetcher` trait is called, an element is pushed on this
/// channel.
///
/// If this function returns `None`, that means that the receiver has already been extracted in
/// the past, and therefore that something already handles the requests.
pub(crate) fn extract_receiver(
&self,
) -> Option<TracingUnboundedReceiver<light_client_requests::sender::Request<B>>> {
self.requests_queue.lock().take()
}
}
impl<B> Fetcher<B> for OnDemand<B>
where
B: BlockT,
B::Header: HeaderT,
{
type RemoteHeaderResult = RemoteResponse<B::Header>;
type RemoteReadResult = RemoteResponse<HashMap<Vec<u8>, Option<Vec<u8>>>>;
type RemoteCallResult = RemoteResponse<Vec<u8>>;
type RemoteChangesResult = RemoteResponse<Vec<(NumberFor<B>, u32)>>;
type RemoteBodyResult = RemoteResponse<Vec<B::Extrinsic>>;
fn remote_header(&self, request: RemoteHeaderRequest<B::Header>) -> Self::RemoteHeaderResult {
let (sender, receiver) = oneshot::channel();
let _ = self
.requests_send
.unbounded_send(light_client_requests::sender::Request::Header { request, sender });
RemoteResponse { receiver }
}
fn remote_read(&self, request: RemoteReadRequest<B::Header>) -> Self::RemoteReadResult {
let (sender, receiver) = oneshot::channel();
let _ = self
.requests_send
.unbounded_send(light_client_requests::sender::Request::Read { request, sender });
RemoteResponse { receiver }
}
fn remote_read_child(
&self,
request: RemoteReadChildRequest<B::Header>,
) -> Self::RemoteReadResult {
let (sender, receiver) = oneshot::channel();
let _ = self
.requests_send
.unbounded_send(light_client_requests::sender::Request::ReadChild { request, sender });
RemoteResponse { receiver }
}
fn remote_call(&self, request: RemoteCallRequest<B::Header>) -> Self::RemoteCallResult {
let (sender, receiver) = oneshot::channel();
let _ = self
.requests_send
.unbounded_send(light_client_requests::sender::Request::Call { request, sender });
RemoteResponse { receiver }
}
fn remote_changes(
&self,
request: RemoteChangesRequest<B::Header>,
) -> Self::RemoteChangesResult {
let (sender, receiver) = oneshot::channel();
let _ = self
.requests_send
.unbounded_send(light_client_requests::sender::Request::Changes { request, sender });
RemoteResponse { receiver }
}
fn remote_body(&self, request: RemoteBodyRequest<B::Header>) -> Self::RemoteBodyResult {
let (sender, receiver) = oneshot::channel();
let _ = self
.requests_send
.unbounded_send(light_client_requests::sender::Request::Body { request, sender });
RemoteResponse { receiver }
}
}
/// Future for an on-demand remote call response.
pub struct RemoteResponse<T> {
receiver: oneshot::Receiver<Result<T, ClientError>>,
}
impl<T> Future for RemoteResponse<T> {
type Output = Result<T, ClientError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
match self.receiver.poll_unpin(cx) {
Poll::Ready(Ok(res)) => Poll::Ready(res),
Poll::Ready(Err(_)) => Poll::Ready(Err(ClientError::RemoteFetchCancelled)),
Poll::Pending => Poll::Pending,
}
}
}
-37
View File
@@ -33,11 +33,9 @@ use crate::{
config::{parse_str_addr, Params, TransportConfig},
discovery::DiscoveryConfig,
error::Error,
light_client_requests,
network_state::{
NetworkState, NotConnectedPeer as NetworkStateNotConnectedPeer, Peer as NetworkStatePeer,
},
on_demand_layer::AlwaysBadChecker,
protocol::{
self,
event::Event,
@@ -238,12 +236,6 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkWorker<B, H> {
}
})?;
let checker = params
.on_demand
.as_ref()
.map(|od| od.checker().clone())
.unwrap_or_else(|| Arc::new(AlwaysBadChecker));
let num_connected = Arc::new(AtomicUsize::new(0));
let is_major_syncing = Arc::new(AtomicBool::new(false));
@@ -255,14 +247,6 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkWorker<B, H> {
params.network_config.client_version, params.network_config.node_name
);
let light_client_request_sender = {
light_client_requests::sender::LightClientRequestSender::new(
&params.protocol_id,
checker,
peerset_handle.clone(),
)
};
let discovery_config = {
let mut config = DiscoveryConfig::new(local_public.clone());
config.with_permanent_addresses(known_addresses);
@@ -347,7 +331,6 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkWorker<B, H> {
protocol,
user_agent,
local_public,
light_client_request_sender,
discovery_config,
params.block_request_protocol_config,
params.state_request_protocol_config,
@@ -447,7 +430,6 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkWorker<B, H> {
service,
import_queue: params.import_queue,
from_service,
light_client_rqs: params.on_demand.and_then(|od| od.extract_receiver()),
event_streams: out_events::OutChannels::new(params.metrics_registry.as_ref())?,
peers_notifications_sinks,
tx_handler_controller,
@@ -1464,8 +1446,6 @@ pub struct NetworkWorker<B: BlockT + 'static, H: ExHashT> {
import_queue: Box<dyn ImportQueue<B>>,
/// Messages from the [`NetworkService`] that must be processed.
from_service: TracingUnboundedReceiver<ServiceToWorkerMsg<B, H>>,
/// Receiver for queries from the light client that must be processed.
light_client_rqs: Option<TracingUnboundedReceiver<light_client_requests::sender::Request<B>>>,
/// Senders for events that happen on the network.
event_streams: out_events::OutChannels,
/// Prometheus network metrics.
@@ -1489,23 +1469,6 @@ impl<B: BlockT + 'static, H: ExHashT> Future for NetworkWorker<B, H> {
this.import_queue
.poll_actions(cx, &mut NetworkLink { protocol: &mut this.network_service });
// Check for new incoming light client requests.
if let Some(light_client_rqs) = this.light_client_rqs.as_mut() {
while let Poll::Ready(Some(rq)) = light_client_rqs.poll_next_unpin(cx) {
let result = this.network_service.behaviour_mut().light_client_request(rq);
match result {
Ok(()) => {},
Err(light_client_requests::sender::SendRequestError::TooManyRequests) => {
warn!("Couldn't start light client request: too many pending requests");
},
}
if let Some(metrics) = this.metrics.as_ref() {
metrics.issued_light_requests.inc();
}
}
}
// At the time of writing of this comment, due to a high volume of messages, the network
// worker sometimes takes a long time to process the loop below. When that happens, the
// rest of the polling is frozen. In order to avoid negative side-effects caused by this
@@ -116,7 +116,6 @@ fn build_test_full_node(
}),
network_config: config,
chain: client.clone(),
on_demand: None,
transaction_pool: Arc::new(crate::config::EmptyTransactionPool),
protocol_id,
import_queue,
+27 -158
View File
@@ -48,13 +48,13 @@ use sc_consensus::{
};
pub use sc_network::config::EmptyTransactionPool;
use sc_network::{
block_request_handler::{self, BlockRequestHandler},
block_request_handler::BlockRequestHandler,
config::{
MultiaddrWithPeerId, NetworkConfiguration, NonDefaultSetConfig, NonReservedPeerMode,
ProtocolConfig, ProtocolId, Role, SyncMode, TransportConfig,
},
light_client_requests::{self, handler::LightClientRequestHandler},
state_request_handler::{self, StateRequestHandler},
light_client_requests::handler::LightClientRequestHandler,
state_request_handler::StateRequestHandler,
warp_request_handler, Multiaddr, NetworkService, NetworkWorker,
};
use sc_service::client::Client;
@@ -133,25 +133,20 @@ pub type PeersFullClient = Client<
Block,
substrate_test_runtime_client::runtime::RuntimeApi,
>;
pub type PeersLightClient = Client<
substrate_test_runtime_client::LightBackend,
substrate_test_runtime_client::LightExecutor,
Block,
substrate_test_runtime_client::runtime::RuntimeApi,
>;
#[derive(Clone)]
pub enum PeersClient {
Full(Arc<PeersFullClient>, Arc<substrate_test_runtime_client::Backend>),
Light(Arc<PeersLightClient>, Arc<substrate_test_runtime_client::LightBackend>),
pub struct PeersClient {
client: Arc<PeersFullClient>,
backend: Arc<substrate_test_runtime_client::Backend>,
}
impl PeersClient {
pub fn as_full(&self) -> Option<Arc<PeersFullClient>> {
match *self {
PeersClient::Full(ref client, _) => Some(client.clone()),
_ => None,
}
pub fn as_client(&self) -> Arc<PeersFullClient> {
self.client.clone()
}
pub fn as_backend(&self) -> Arc<substrate_test_runtime_client::Backend> {
self.backend.clone()
}
pub fn as_block_import(&self) -> BlockImportAdapter<Self> {
@@ -159,27 +154,18 @@ impl PeersClient {
}
pub fn get_aux(&self, key: &[u8]) -> ClientResult<Option<Vec<u8>>> {
match *self {
PeersClient::Full(ref client, _) => client.get_aux(key),
PeersClient::Light(ref client, _) => client.get_aux(key),
}
self.client.get_aux(key)
}
pub fn info(&self) -> BlockchainInfo<Block> {
match *self {
PeersClient::Full(ref client, _) => client.chain_info(),
PeersClient::Light(ref client, _) => client.chain_info(),
}
self.client.info()
}
pub fn header(
&self,
block: &BlockId<Block>,
) -> ClientResult<Option<<Block as BlockT>::Header>> {
match *self {
PeersClient::Full(ref client, _) => client.header(block),
PeersClient::Light(ref client, _) => client.header(block),
}
self.client.header(block)
}
pub fn has_state_at(&self, block: &BlockId<Block>) -> bool {
@@ -187,33 +173,19 @@ impl PeersClient {
Some(header) => header,
None => return false,
};
match self {
PeersClient::Full(_client, backend) =>
backend.have_state_at(&header.hash(), *header.number()),
PeersClient::Light(_client, backend) =>
backend.have_state_at(&header.hash(), *header.number()),
}
self.backend.have_state_at(&header.hash(), *header.number())
}
pub fn justifications(&self, block: &BlockId<Block>) -> ClientResult<Option<Justifications>> {
match *self {
PeersClient::Full(ref client, _) => client.justifications(block),
PeersClient::Light(ref client, _) => client.justifications(block),
}
self.client.justifications(block)
}
pub fn finality_notification_stream(&self) -> FinalityNotifications<Block> {
match *self {
PeersClient::Full(ref client, _) => client.finality_notification_stream(),
PeersClient::Light(ref client, _) => client.finality_notification_stream(),
}
self.client.finality_notification_stream()
}
pub fn import_notification_stream(&self) -> ImportNotifications<Block> {
match *self {
PeersClient::Full(ref client, _) => client.import_notification_stream(),
PeersClient::Light(ref client, _) => client.import_notification_stream(),
}
self.client.import_notification_stream()
}
pub fn finalize_block(
@@ -222,12 +194,7 @@ impl PeersClient {
justification: Option<Justification>,
notify: bool,
) -> ClientResult<()> {
match *self {
PeersClient::Full(ref client, ref _backend) =>
client.finalize_block(id, justification, notify),
PeersClient::Light(ref client, ref _backend) =>
client.finalize_block(id, justification, notify),
}
self.client.finalize_block(id, justification, notify)
}
}
@@ -240,10 +207,7 @@ impl BlockImport<Block> for PeersClient {
&mut self,
block: BlockCheckParams<Block>,
) -> Result<ImportResult, Self::Error> {
match self {
PeersClient::Full(client, _) => client.check_block(block).await,
PeersClient::Light(client, _) => client.check_block(block).await,
}
self.client.check_block(block).await
}
async fn import_block(
@@ -251,12 +215,7 @@ impl BlockImport<Block> for PeersClient {
block: BlockImportParams<Block, ()>,
cache: HashMap<well_known_cache_keys::Id, Vec<u8>>,
) -> Result<ImportResult, Self::Error> {
match self {
PeersClient::Full(client, _) =>
client.import_block(block.clear_storage_changes_and_mutate(), cache).await,
PeersClient::Light(client, _) =>
client.import_block(block.clear_storage_changes_and_mutate(), cache).await,
}
self.client.import_block(block.clear_storage_changes_and_mutate(), cache).await
}
}
@@ -370,8 +329,7 @@ where
BlockBuilder<Block, PeersFullClient, substrate_test_runtime_client::Backend>,
) -> Block,
{
let full_client =
self.client.as_full().expect("blocks could only be generated by full clients");
let full_client = self.client.as_client();
let mut at = full_client.header(&at).unwrap().unwrap().hash();
for _ in 0..count {
let builder =
@@ -779,11 +737,11 @@ where
let (c, longest_chain) = test_client_builder.build_with_longest_chain();
let client = Arc::new(c);
let (block_import, justification_import, data) =
self.make_block_import(PeersClient::Full(client.clone(), backend.clone()));
let (block_import, justification_import, data) = self
.make_block_import(PeersClient { client: client.clone(), backend: backend.clone() });
let verifier = self.make_verifier(
PeersClient::Full(client.clone(), backend.clone()),
PeersClient { client: client.clone(), backend: backend.clone() },
&Default::default(),
&data,
);
@@ -868,7 +826,6 @@ where
}),
network_config,
chain: client.clone(),
on_demand: None,
transaction_pool: Arc::new(EmptyTransactionPool),
protocol_id,
import_queue,
@@ -899,7 +856,7 @@ where
peers.push(Peer {
data,
client: PeersClient::Full(client.clone(), backend.clone()),
client: PeersClient { client: client.clone(), backend: backend.clone() },
select_chain: Some(longest_chain),
backend: Some(backend),
imported_blocks_stream,
@@ -912,94 +869,6 @@ where
});
}
/// Add a light peer.
fn add_light_peer(&mut self) {
let (c, backend) = substrate_test_runtime_client::new_light();
let client = Arc::new(c);
let (block_import, justification_import, data) =
self.make_block_import(PeersClient::Light(client.clone(), backend.clone()));
let verifier = self.make_verifier(
PeersClient::Light(client.clone(), backend.clone()),
&Default::default(),
&data,
);
let verifier = VerifierAdapter::new(verifier);
let import_queue = Box::new(BasicQueue::new(
verifier.clone(),
Box::new(block_import.clone()),
justification_import,
&sp_core::testing::TaskExecutor::new(),
None,
));
let listen_addr = build_multiaddr![Memory(rand::random::<u64>())];
let mut network_config =
NetworkConfiguration::new("test-node", "test-client", Default::default(), None);
network_config.transport = TransportConfig::MemoryOnly;
network_config.listen_addresses = vec![listen_addr.clone()];
network_config.allow_non_globals_in_dht = true;
let protocol_id = ProtocolId::from("test-protocol-name");
let block_request_protocol_config =
block_request_handler::generate_protocol_config(&protocol_id);
let state_request_protocol_config =
state_request_handler::generate_protocol_config(&protocol_id);
let light_client_request_protocol_config =
light_client_requests::generate_protocol_config(&protocol_id);
let network = NetworkWorker::new(sc_network::config::Params {
role: Role::Light,
executor: None,
transactions_handler_executor: Box::new(|task| {
async_std::task::spawn(task);
}),
network_config,
chain: client.clone(),
on_demand: None,
transaction_pool: Arc::new(EmptyTransactionPool),
protocol_id,
import_queue,
block_announce_validator: Box::new(DefaultBlockAnnounceValidator),
metrics_registry: None,
block_request_protocol_config,
state_request_protocol_config,
light_client_request_protocol_config,
warp_sync: None,
})
.unwrap();
self.mut_peers(|peers| {
for peer in peers.iter_mut() {
peer.network.add_known_address(
network.service().local_peer_id().clone(),
listen_addr.clone(),
);
}
let imported_blocks_stream = Box::pin(client.import_notification_stream().fuse());
let finality_notification_stream =
Box::pin(client.finality_notification_stream().fuse());
peers.push(Peer {
data,
verifier,
select_chain: None,
backend: None,
block_import,
client: PeersClient::Light(client, backend),
imported_blocks_stream,
finality_notification_stream,
network,
listen_addr,
});
});
}
/// Used to spawn background tasks, e.g. the block request protocol handler.
fn spawn_task(&self, f: BoxFuture<'static, ()>) {
async_std::task::spawn(f);
+3 -102
View File
@@ -20,7 +20,6 @@ use super::*;
use futures::{executor::block_on, Future};
use sp_consensus::{block_validation::Validation, BlockOrigin};
use sp_runtime::Justifications;
use std::time::Duration;
use substrate_test_runtime::Header;
fn test_ancestor_search_when_common_is(n: usize) {
@@ -391,35 +390,6 @@ fn own_blocks_are_announced() {
(net.peers()[2].blockchain_canon_equals(peer0));
}
#[test]
fn blocks_are_not_announced_by_light_nodes() {
sp_tracing::try_init_simple();
let mut net = TestNet::new(0);
// full peer0 is connected to light peer
// light peer1 is connected to full peer2
net.add_full_peer();
net.add_light_peer();
// Sync between 0 and 1.
net.peer(0).push_blocks(1, false);
assert_eq!(net.peer(0).client.info().best_number, 1);
net.block_until_sync();
assert_eq!(net.peer(1).client.info().best_number, 1);
// Add another node and remove node 0.
net.add_full_peer();
net.peers.remove(0);
// Poll for a few seconds and make sure 1 and 2 (now 0 and 1) don't sync together.
let mut delay = futures_timer::Delay::new(Duration::from_secs(5));
block_on(futures::future::poll_fn::<(), _>(|cx| {
net.poll(cx);
Pin::new(&mut delay).poll(cx)
}));
assert_eq!(net.peer(1).client.info().best_number, 0);
}
#[test]
fn can_sync_small_non_best_forks() {
sp_tracing::try_init_simple();
@@ -483,72 +453,6 @@ fn can_sync_small_non_best_forks() {
}));
}
#[test]
fn can_not_sync_from_light_peer() {
sp_tracing::try_init_simple();
// given the network with 1 full nodes (#0) and 1 light node (#1)
let mut net = TestNet::new(1);
net.add_light_peer();
// generate some blocks on #0
net.peer(0).push_blocks(1, false);
// and let the light client sync from this node
net.block_until_sync();
// ensure #0 && #1 have the same best block
let full0_info = net.peer(0).client.info();
let light_info = net.peer(1).client.info();
assert_eq!(full0_info.best_number, 1);
assert_eq!(light_info.best_number, 1);
assert_eq!(light_info.best_hash, full0_info.best_hash);
// add new full client (#2) && remove #0
net.add_full_peer();
net.peers.remove(0);
// ensure that the #2 (now #1) fails to sync block #1 even after 5 seconds
let mut test_finished = futures_timer::Delay::new(Duration::from_secs(5));
block_on(futures::future::poll_fn::<(), _>(|cx| {
net.poll(cx);
Pin::new(&mut test_finished).poll(cx)
}));
}
#[test]
fn light_peer_imports_header_from_announce() {
sp_tracing::try_init_simple();
fn import_with_announce(net: &mut TestNet, hash: H256) {
net.peer(0).announce_block(hash, None);
block_on(futures::future::poll_fn::<(), _>(|cx| {
net.poll(cx);
if net.peer(1).client().header(&BlockId::Hash(hash)).unwrap().is_some() {
Poll::Ready(())
} else {
Poll::Pending
}
}));
}
// given the network with 1 full nodes (#0) and 1 light node (#1)
let mut net = TestNet::new(1);
net.add_light_peer();
// let them connect to each other
net.block_until_sync();
// check that NEW block is imported from announce message
let new_hash = net.peer(0).push_blocks(1, false);
import_with_announce(&mut net, new_hash);
// check that KNOWN STALE block is imported from announce message
let known_stale_hash = net.peer(0).push_blocks_at(BlockId::Number(0), 1, true);
import_with_announce(&mut net, known_stale_hash);
}
#[test]
fn can_sync_explicit_forks() {
sp_tracing::try_init_simple();
@@ -1210,16 +1114,14 @@ fn syncs_indexed_blocks() {
assert!(net
.peer(0)
.client()
.as_full()
.unwrap()
.as_client()
.indexed_transaction(&indexed_key)
.unwrap()
.is_some());
assert!(net
.peer(1)
.client()
.as_full()
.unwrap()
.as_client()
.indexed_transaction(&indexed_key)
.unwrap()
.is_none());
@@ -1228,8 +1130,7 @@ fn syncs_indexed_blocks() {
assert!(net
.peer(1)
.client()
.as_full()
.unwrap()
.as_client()
.indexed_transaction(&indexed_key)
.unwrap()
.is_some());
@@ -1,114 +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/>.
//! Blockchain API backend for light nodes.
use futures::{future::ready, FutureExt, TryFutureExt};
use jsonrpc_pubsub::manager::SubscriptionManager;
use std::sync::Arc;
use sc_client_api::light::{Fetcher, RemoteBlockchain, RemoteBodyRequest};
use sp_runtime::{
generic::{BlockId, SignedBlock},
traits::Block as BlockT,
};
use super::{client_err, error::FutureResult, ChainBackend};
use sc_client_api::BlockchainEvents;
use sp_blockchain::HeaderBackend;
/// Blockchain API backend for light nodes. Reads all the data from local
/// database, if available, or fetches it from remote node otherwise.
pub struct LightChain<Block: BlockT, Client, F> {
/// Substrate client.
client: Arc<Client>,
/// Current subscriptions.
subscriptions: SubscriptionManager,
/// Remote blockchain reference
remote_blockchain: Arc<dyn RemoteBlockchain<Block>>,
/// Remote fetcher reference.
fetcher: Arc<F>,
}
impl<Block: BlockT, Client, F: Fetcher<Block>> LightChain<Block, Client, F> {
/// Create new Chain API RPC handler.
pub fn new(
client: Arc<Client>,
subscriptions: SubscriptionManager,
remote_blockchain: Arc<dyn RemoteBlockchain<Block>>,
fetcher: Arc<F>,
) -> Self {
Self { client, subscriptions, remote_blockchain, fetcher }
}
}
impl<Block, Client, F> ChainBackend<Client, Block> for LightChain<Block, Client, F>
where
Block: BlockT + 'static,
Block::Header: Unpin,
Client: BlockchainEvents<Block> + HeaderBackend<Block> + Send + Sync + 'static,
F: Fetcher<Block> + Send + Sync + 'static,
{
fn client(&self) -> &Arc<Client> {
&self.client
}
fn subscriptions(&self) -> &SubscriptionManager {
&self.subscriptions
}
fn header(&self, hash: Option<Block::Hash>) -> FutureResult<Option<Block::Header>> {
let hash = self.unwrap_or_best(hash);
let fetcher = self.fetcher.clone();
let maybe_header = sc_client_api::light::future_header(
&*self.remote_blockchain,
&*fetcher,
BlockId::Hash(hash),
);
maybe_header.then(move |result| ready(result.map_err(client_err))).boxed()
}
fn block(&self, hash: Option<Block::Hash>) -> FutureResult<Option<SignedBlock<Block>>> {
let fetcher = self.fetcher.clone();
self.header(hash)
.and_then(move |header| async move {
match header {
Some(header) => {
let body = fetcher
.remote_body(RemoteBodyRequest {
header: header.clone(),
retry_count: Default::default(),
})
.await;
body.map(|body| {
Some(SignedBlock {
block: Block::new(header, body),
justifications: None,
})
})
.map_err(client_err)
},
None => Ok(None),
}
})
.boxed()
}
}
+1 -28
View File
@@ -19,7 +19,6 @@
//! Substrate blockchain API.
mod chain_full;
mod chain_light;
#[cfg(test)]
mod tests;
@@ -33,10 +32,7 @@ use rpc::{
use std::sync::Arc;
use jsonrpc_pubsub::{manager::SubscriptionManager, typed::Subscriber, SubscriptionId};
use sc_client_api::{
light::{Fetcher, RemoteBlockchain},
BlockchainEvents,
};
use sc_client_api::BlockchainEvents;
use sp_rpc::{list::ListOrValue, number::NumberOrHex};
use sp_runtime::{
generic::{BlockId, SignedBlock},
@@ -204,29 +200,6 @@ where
Chain { backend: Box::new(self::chain_full::FullChain::new(client, subscriptions)) }
}
/// Create new state API that works on light node.
pub fn new_light<Block: BlockT, Client, F: Fetcher<Block>>(
client: Arc<Client>,
subscriptions: SubscriptionManager,
remote_blockchain: Arc<dyn RemoteBlockchain<Block>>,
fetcher: Arc<F>,
) -> Chain<Block, Client>
where
Block: BlockT + 'static,
Block::Header: Unpin,
Client: BlockBackend<Block> + HeaderBackend<Block> + BlockchainEvents<Block> + 'static,
F: Send + Sync + 'static,
{
Chain {
backend: Box::new(self::chain_light::LightChain::new(
client,
subscriptions,
remote_blockchain,
fetcher,
)),
}
}
/// Chain API with subscriptions support.
pub struct Chain<Block: BlockT, Client> {
backend: Box<dyn ChainBackend<Client, Block>>,
+8 -117
View File
@@ -26,11 +26,7 @@ use futures::{
use jsonrpc_pubsub::{manager::SubscriptionManager, typed::Subscriber, SubscriptionId};
use log::warn;
use rpc::Result as RpcResult;
use std::{
collections::{BTreeMap, HashMap},
ops::Range,
sync::Arc,
};
use std::{collections::HashMap, sync::Arc};
use sc_rpc_api::state::ReadProof;
use sp_blockchain::{
@@ -43,10 +39,7 @@ use sp_core::{
},
Bytes,
};
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, CheckedSub, NumberFor, SaturatedConversion},
};
use sp_runtime::{generic::BlockId, traits::Block as BlockT};
use sp_version::RuntimeVersion;
use sp_api::{CallApiAt, Metadata, ProvideRuntimeApi};
@@ -66,14 +59,6 @@ use std::marker::PhantomData;
struct QueryStorageRange<Block: BlockT> {
/// Hashes of all the blocks in the range.
pub hashes: Vec<Block::Hash>,
/// Number of the first block in the range.
pub first_number: NumberFor<Block>,
/// Blocks subrange ([begin; end) indices within `hashes`) where we should read keys at
/// each state to get changes.
pub unfiltered_range: Range<usize>,
/// Blocks subrange ([begin; end) indices within `hashes`) where we could pre-filter
/// blocks-with-changes by using changes tries.
pub filtered_range: Option<Range<usize>>,
}
/// State API backend for full nodes.
@@ -107,10 +92,8 @@ where
Ok(hash.unwrap_or_else(|| self.client.info().best_hash))
}
/// Splits the `query_storage` block range into 'filtered' and 'unfiltered' subranges.
/// Blocks that contain changes within filtered subrange could be filtered using changes tries.
/// Blocks that contain changes within unfiltered subrange must be filtered manually.
fn split_query_storage_range(
/// Validates block range.
fn query_storage_range(
&self,
from: Block::Hash,
to: Option<Block::Hash>,
@@ -156,23 +139,7 @@ where
hashes
};
// check if we can filter blocks-with-changes from some (sub)range using changes tries
let changes_trie_range = self
.client
.max_key_changes_range(from_number, BlockId::Hash(to_meta.hash))
.map_err(client_err)?;
let filtered_range_begin = changes_trie_range.and_then(|(begin, _)| {
// avoids a corner case where begin < from_number (happens when querying genesis)
begin.checked_sub(&from_number).map(|x| x.saturated_into::<usize>())
});
let (unfiltered_range, filtered_range) = split_range(hashes.len(), filtered_range_begin);
Ok(QueryStorageRange {
hashes,
first_number: from_number,
unfiltered_range,
filtered_range,
})
Ok(QueryStorageRange { hashes })
}
/// Iterates through range.unfiltered_range and check each block for changes of keys' values.
@@ -183,8 +150,8 @@ where
last_values: &mut HashMap<StorageKey, Option<StorageData>>,
changes: &mut Vec<StorageChangeSet<Block::Hash>>,
) -> Result<()> {
for block in range.unfiltered_range.start..range.unfiltered_range.end {
let block_hash = range.hashes[block].clone();
for block_hash in &range.hashes {
let block_hash = block_hash.clone();
let mut block_changes =
StorageChangeSet { block: block_hash.clone(), changes: Vec::new() };
let id = BlockId::hash(block_hash);
@@ -207,57 +174,6 @@ where
}
Ok(())
}
/// Iterates through all blocks that are changing keys within range.filtered_range and collects
/// these changes.
fn query_storage_filtered(
&self,
range: &QueryStorageRange<Block>,
keys: &[StorageKey],
last_values: &HashMap<StorageKey, Option<StorageData>>,
changes: &mut Vec<StorageChangeSet<Block::Hash>>,
) -> Result<()> {
let (begin, end) = match range.filtered_range {
Some(ref filtered_range) => (
range.first_number + filtered_range.start.saturated_into(),
BlockId::Hash(range.hashes[filtered_range.end - 1].clone()),
),
None => return Ok(()),
};
let mut changes_map: BTreeMap<NumberFor<Block>, StorageChangeSet<Block::Hash>> =
BTreeMap::new();
for key in keys {
let mut last_block = None;
let mut last_value = last_values.get(key).cloned().unwrap_or_default();
let key_changes = self.client.key_changes(begin, end, None, key).map_err(client_err)?;
for (block, _) in key_changes.into_iter().rev() {
if last_block == Some(block) {
continue
}
let block_hash =
range.hashes[(block - range.first_number).saturated_into::<usize>()].clone();
let id = BlockId::Hash(block_hash);
let value_at_block = self.client.storage(&id, key).map_err(client_err)?;
if last_value == value_at_block {
continue
}
changes_map
.entry(block)
.or_insert_with(|| StorageChangeSet { block: block_hash, changes: Vec::new() })
.changes
.push((key.clone(), value_at_block.clone()));
last_block = Some(block);
last_value = value_at_block;
}
}
if let Some(additional_capacity) = changes_map.len().checked_sub(changes.len()) {
changes.reserve(additional_capacity);
}
changes.extend(changes_map.into_iter().map(|(_, cs)| cs));
Ok(())
}
}
impl<BE, Block, Client> StateBackend<Block, Client> for FullState<BE, Block, Client>
@@ -430,11 +346,10 @@ where
keys: Vec<StorageKey>,
) -> FutureResult<Vec<StorageChangeSet<Block::Hash>>> {
let call_fn = move || {
let range = self.split_query_storage_range(from, to)?;
let range = self.query_storage_range(from, to)?;
let mut changes = Vec::new();
let mut last_values = HashMap::new();
self.query_storage_unfiltered(&range, &keys, &mut last_values, &mut changes)?;
self.query_storage_filtered(&range, &keys, &last_values, &mut changes)?;
Ok(changes)
};
@@ -768,30 +683,6 @@ where
}
}
/// Splits passed range into two subranges where:
/// - first range has at least one element in it;
/// - second range (optionally) starts at given `middle` element.
pub(crate) fn split_range(
size: usize,
middle: Option<usize>,
) -> (Range<usize>, Option<Range<usize>>) {
// check if we can filter blocks-with-changes from some (sub)range using changes tries
let range2_begin = match middle {
// some of required changes tries are pruned => use available tries
Some(middle) if middle != 0 => Some(middle),
// all required changes tries are available, but we still want values at first block
// => do 'unfiltered' read for the first block and 'filtered' for the rest
Some(_) if size > 1 => Some(1),
// range contains single element => do not use changes tries
Some(_) => None,
// changes tries are not available => do 'unfiltered' read for the whole range
None => None,
};
let range1 = 0..range2_begin.unwrap_or(size);
let range2 = range2_begin.map(|begin| begin..size);
(range1, range2)
}
fn invalid_block_range<B: BlockT>(
from: &CachedHeaderMetadata<B>,
to: &CachedHeaderMetadata<B>,
+5 -29
View File
@@ -17,16 +17,15 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use self::error::Error;
use super::{state_full::split_range, *};
use super::*;
use crate::testing::TaskExecutor;
use assert_matches::assert_matches;
use futures::{executor, StreamExt};
use sc_block_builder::BlockBuilderProvider;
use sc_rpc_api::DenyUnsafe;
use sp_consensus::BlockOrigin;
use sp_core::{hash::H256, storage::ChildInfo, ChangesTrieConfiguration};
use sp_core::{hash::H256, storage::ChildInfo};
use sp_io::hashing::blake2_256;
use sp_runtime::generic::BlockId;
use std::sync::Arc;
use substrate_test_runtime_client::{prelude::*, runtime};
@@ -336,7 +335,7 @@ fn should_send_initial_storage_changes_and_notifications() {
#[test]
fn should_query_storage() {
fn run_tests(mut client: Arc<TestClient>, has_changes_trie_config: bool) {
fn run_tests(mut client: Arc<TestClient>) {
let (api, _child) = new_full(
client.clone(),
SubscriptionManager::new(Arc::new(TaskExecutor)),
@@ -369,13 +368,6 @@ fn should_query_storage() {
let block2_hash = add_block(1);
let genesis_hash = client.genesis_hash();
if has_changes_trie_config {
assert_eq!(
client.max_key_changes_range(1, BlockId::Hash(block1_hash)).unwrap(),
Some((0, BlockId::Hash(block1_hash))),
);
}
let mut expected = vec![
StorageChangeSet {
block: genesis_hash,
@@ -519,24 +511,8 @@ fn should_query_storage() {
);
}
run_tests(Arc::new(substrate_test_runtime_client::new()), false);
run_tests(
Arc::new(
TestClientBuilder::new()
.changes_trie_config(Some(ChangesTrieConfiguration::new(4, 2)))
.build(),
),
true,
);
}
#[test]
fn should_split_ranges() {
assert_eq!(split_range(1, None), (0..1, None));
assert_eq!(split_range(100, None), (0..100, None));
assert_eq!(split_range(1, Some(0)), (0..1, None));
assert_eq!(split_range(100, Some(50)), (0..50, Some(50..100)));
assert_eq!(split_range(100, Some(99)), (0..99, Some(99..100)));
run_tests(Arc::new(substrate_test_runtime_client::new()));
run_tests(Arc::new(TestClientBuilder::new().build()));
}
#[test]
+8 -19
View File
@@ -30,9 +30,8 @@ use log::info;
use prometheus_endpoint::Registry;
use sc_chain_spec::get_extension;
use sc_client_api::{
execution_extensions::ExecutionExtensions, light::RemoteBlockchain,
proof_provider::ProofProvider, BadBlocks, BlockBackend, BlockchainEvents, ExecutorProvider,
ForkBlocks, StorageProvider, UsageProvider,
execution_extensions::ExecutionExtensions, proof_provider::ProofProvider, BadBlocks,
BlockBackend, BlockchainEvents, ExecutorProvider, ForkBlocks, StorageProvider, UsageProvider,
};
use sc_client_db::{Backend, DatabaseSettings};
use sc_consensus::import_queue::ImportQueue;
@@ -40,7 +39,7 @@ use sc_executor::RuntimeVersionOf;
use sc_keystore::LocalKeystore;
use sc_network::{
block_request_handler::{self, BlockRequestHandler},
config::{OnDemand, Role, SyncMode},
config::{Role, SyncMode},
light_client_requests::{self, handler::LightClientRequestHandler},
state_request_handler::{self, StateRequestHandler},
warp_request_handler::{self, RequestHandler as WarpSyncRequestHandler, WarpSyncProvider},
@@ -381,23 +380,19 @@ where
pub struct SpawnTasksParams<'a, TBl: BlockT, TCl, TExPool, TRpc, Backend> {
/// The service configuration.
pub config: Configuration,
/// A shared client returned by `new_full_parts`/`new_light_parts`.
/// A shared client returned by `new_full_parts`.
pub client: Arc<TCl>,
/// A shared backend returned by `new_full_parts`/`new_light_parts`.
/// A shared backend returned by `new_full_parts`.
pub backend: Arc<Backend>,
/// A task manager returned by `new_full_parts`/`new_light_parts`.
/// A task manager returned by `new_full_parts`.
pub task_manager: &'a mut TaskManager,
/// A shared keystore returned by `new_full_parts`/`new_light_parts`.
/// A shared keystore returned by `new_full_parts`.
pub keystore: SyncCryptoStorePtr,
/// An optional, shared data fetcher for light clients.
pub on_demand: Option<Arc<OnDemand<TBl>>>,
/// A shared transaction pool.
pub transaction_pool: Arc<TExPool>,
/// A RPC extension builder. Use `NoopRpcExtensionBuilder` if you just want to pass in the
/// extensions directly.
pub rpc_extensions_builder: Box<dyn RpcExtensionBuilder<Output = TRpc> + Send>,
/// An optional, shared remote blockchain instance. Used for light clients.
pub remote_blockchain: Option<Arc<dyn RemoteBlockchain<TBl>>>,
/// A shared network instance.
pub network: Arc<NetworkService<TBl, <TBl as BlockT>::Hash>>,
/// A Sender for RPC requests.
@@ -475,12 +470,10 @@ where
mut config,
task_manager,
client,
on_demand: _,
backend,
keystore,
transaction_pool,
rpc_extensions_builder,
remote_blockchain: _,
network,
system_rpc_tx,
telemetry,
@@ -725,7 +718,7 @@ where
pub struct BuildNetworkParams<'a, TBl: BlockT, TExPool, TImpQu, TCl> {
/// The service configuration.
pub config: &'a Configuration,
/// A shared client returned by `new_full_parts`/`new_light_parts`.
/// A shared client returned by `new_full_parts`.
pub client: Arc<TCl>,
/// A shared transaction pool.
pub transaction_pool: Arc<TExPool>,
@@ -733,8 +726,6 @@ pub struct BuildNetworkParams<'a, TBl: BlockT, TExPool, TImpQu, TCl> {
pub spawn_handle: SpawnTaskHandle,
/// An import queue.
pub import_queue: TImpQu,
/// An optional, shared data fetcher for light clients.
pub on_demand: Option<Arc<OnDemand<TBl>>>,
/// A block announce validator builder.
pub block_announce_validator_builder:
Option<Box<dyn FnOnce(Arc<TCl>) -> Box<dyn BlockAnnounceValidator<TBl> + Send> + Send>>,
@@ -773,7 +764,6 @@ where
transaction_pool,
spawn_handle,
import_queue,
on_demand,
block_announce_validator_builder,
warp_sync,
} = params;
@@ -869,7 +859,6 @@ where
},
network_config: config.network.clone(),
chain: client.clone(),
on_demand,
transaction_pool: transaction_pool_adapter as _,
import_queue: Box::new(import_queue),
protocol_id,
@@ -26,10 +26,7 @@ use sp_core::{
NativeOrEncoded, NeverNativeValue,
};
use sp_externalities::Extensions;
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, NumberFor},
};
use sp_runtime::{generic::BlockId, traits::Block as BlockT};
use sp_state_machine::{
self, backend::Backend as _, ExecutionManager, ExecutionStrategy, Ext, OverlayedChanges,
StateMachine, StorageProof,
@@ -153,8 +150,6 @@ where
extensions: Option<Extensions>,
) -> sp_blockchain::Result<Vec<u8>> {
let mut changes = OverlayedChanges::default();
let changes_trie =
backend::changes_tries_state_at_block(at, self.backend.changes_trie_storage())?;
let state = self.backend.state_at(*at)?;
let state_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(&state);
let runtime_code =
@@ -168,7 +163,6 @@ where
let return_data = StateMachine::new(
&state,
changes_trie,
&mut changes,
&self.executor,
method,
@@ -208,8 +202,6 @@ where
where
ExecutionManager<EM>: Clone,
{
let changes_trie_state =
backend::changes_tries_state_at_block(at, self.backend.changes_trie_storage())?;
let mut storage_transaction_cache = storage_transaction_cache.map(|c| c.borrow_mut());
let state = self.backend.state_at(*at)?;
@@ -243,7 +235,6 @@ where
let mut state_machine = StateMachine::new(
&backend,
changes_trie_state,
changes,
&self.executor,
method,
@@ -262,7 +253,6 @@ where
None => {
let mut state_machine = StateMachine::new(
&state,
changes_trie_state,
changes,
&self.executor,
method,
@@ -286,11 +276,9 @@ where
fn runtime_version(&self, id: &BlockId<Block>) -> sp_blockchain::Result<RuntimeVersion> {
let mut overlay = OverlayedChanges::default();
let changes_trie_state =
backend::changes_tries_state_at_block(id, self.backend.changes_trie_storage())?;
let state = self.backend.state_at(*id)?;
let mut cache = StorageTransactionCache::<Block, B::State>::default();
let mut ext = Ext::new(&mut overlay, &mut cache, &state, changes_trie_state, None);
let mut ext = Ext::new(&mut overlay, &mut cache, &state, None);
let state_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(&state);
let runtime_code =
state_runtime_code.runtime_code().map_err(sp_blockchain::Error::RuntimeCode)?;
@@ -317,7 +305,7 @@ where
state_runtime_code.runtime_code().map_err(sp_blockchain::Error::RuntimeCode)?;
let runtime_code = self.check_override(runtime_code, at)?;
sp_state_machine::prove_execution_on_trie_backend::<_, _, NumberFor<Block>, _, _>(
sp_state_machine::prove_execution_on_trie_backend(
&trie_backend,
&mut Default::default(),
&self.executor,
+16 -385
View File
@@ -23,7 +23,6 @@ use super::{
genesis,
};
use codec::{Decode, Encode};
use hash_db::Prefix;
use log::{info, trace, warn};
use parking_lot::{Mutex, RwLock};
use prometheus_endpoint::Registry;
@@ -31,18 +30,15 @@ use rand::Rng;
use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider, RecordProof};
use sc_client_api::{
backend::{
self, apply_aux, changes_tries_state_at_block, BlockImportOperation, ClientImportOperation,
Finalizer, ImportSummary, LockImportRun, NewBlockState, PrunableStateChangesTrieStorage,
StorageProvider,
self, apply_aux, BlockImportOperation, ClientImportOperation, Finalizer, ImportSummary,
LockImportRun, NewBlockState, StorageProvider,
},
cht,
client::{
BadBlocks, BlockBackend, BlockImportNotification, BlockOf, BlockchainEvents, ClientInfo,
FinalityNotification, FinalityNotifications, ForkBlocks, ImportNotifications,
ProvideUncles,
},
execution_extensions::ExecutionExtensions,
light::ChangesProof,
notifications::{StorageEventStream, StorageNotifications},
CallExecutor, ExecutorProvider, KeyIterator, ProofProvider, UsageProvider,
};
@@ -56,39 +52,36 @@ use sp_api::{
ProvideRuntimeApi,
};
use sp_blockchain::{
self as blockchain, well_known_cache_keys::Id as CacheKeyId, Backend as ChainBackend, Cache,
CachedHeaderMetadata, Error, HeaderBackend as ChainHeaderBackend, HeaderMetadata, ProvideCache,
self as blockchain, well_known_cache_keys::Id as CacheKeyId, Backend as ChainBackend,
CachedHeaderMetadata, Error, HeaderBackend as ChainHeaderBackend, HeaderMetadata,
};
use sp_consensus::{BlockOrigin, BlockStatus, Error as ConsensusError};
use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedSender};
use sp_core::{
convert_hash,
storage::{
well_known_keys, ChildInfo, ChildType, PrefixedStorageKey, StorageChild, StorageData,
StorageKey,
},
ChangesTrieConfiguration, NativeOrEncoded,
NativeOrEncoded,
};
#[cfg(feature = "test-helpers")]
use sp_keystore::SyncCryptoStorePtr;
use sp_runtime::{
generic::{BlockId, DigestItem, SignedBlock},
generic::{BlockId, SignedBlock},
traits::{
Block as BlockT, DigestFor, HashFor, Header as HeaderT, NumberFor, One,
SaturatedConversion, Zero,
Block as BlockT, HashFor, Header as HeaderT, NumberFor, One, SaturatedConversion, Zero,
},
BuildStorage, Justification, Justifications,
BuildStorage, Digest, Justification, Justifications,
};
use sp_state_machine::{
key_changes, key_changes_proof, prove_child_read, prove_range_read_with_child_with_size,
prove_read, read_range_proof_check_with_child_on_proving_backend, Backend as StateBackend,
ChangesTrieAnchorBlockId, ChangesTrieConfigurationRange, ChangesTrieRootsStorage,
ChangesTrieStorage, DBValue, KeyValueStates, KeyValueStorageLevel, MAX_NESTED_TRIE_DEPTH,
prove_child_read, prove_range_read_with_child_with_size, prove_read,
read_range_proof_check_with_child_on_proving_backend, Backend as StateBackend, KeyValueStates,
KeyValueStorageLevel, MAX_NESTED_TRIE_DEPTH,
};
use sp_trie::{CompactProof, StorageProof};
use std::{
collections::{BTreeMap, HashMap, HashSet},
collections::{HashMap, HashSet},
marker::PhantomData,
panic::UnwindSafe,
path::PathBuf,
@@ -413,250 +406,6 @@ where
self.executor.runtime_version(id)
}
/// Reads given header and generates CHT-based header proof for CHT of given size.
pub fn header_proof_with_cht_size(
&self,
id: &BlockId<Block>,
cht_size: NumberFor<Block>,
) -> sp_blockchain::Result<(Block::Header, StorageProof)> {
let proof_error = || {
sp_blockchain::Error::Backend(format!("Failed to generate header proof for {:?}", id))
};
let header = self.backend.blockchain().expect_header(*id)?;
let block_num = *header.number();
let cht_num = cht::block_to_cht_number(cht_size, block_num).ok_or_else(proof_error)?;
let cht_start = cht::start_number(cht_size, cht_num);
let mut current_num = cht_start;
let cht_range = ::std::iter::from_fn(|| {
let old_current_num = current_num;
current_num = current_num + One::one();
Some(old_current_num)
});
let headers = cht_range.map(|num| self.block_hash(num));
let proof = cht::build_proof::<Block::Header, HashFor<Block>, _, _>(
cht_size,
cht_num,
std::iter::once(block_num),
headers,
)?;
Ok((header, proof))
}
/// Does the same work as `key_changes_proof`, but assumes that CHTs are of passed size.
pub fn key_changes_proof_with_cht_size(
&self,
first: Block::Hash,
last: Block::Hash,
min: Block::Hash,
max: Block::Hash,
storage_key: Option<&PrefixedStorageKey>,
key: &StorageKey,
cht_size: NumberFor<Block>,
) -> sp_blockchain::Result<ChangesProof<Block::Header>> {
struct AccessedRootsRecorder<'a, Block: BlockT> {
storage: &'a dyn ChangesTrieStorage<HashFor<Block>, NumberFor<Block>>,
min: NumberFor<Block>,
required_roots_proofs: Mutex<BTreeMap<NumberFor<Block>, Block::Hash>>,
}
impl<'a, Block: BlockT> ChangesTrieRootsStorage<HashFor<Block>, NumberFor<Block>>
for AccessedRootsRecorder<'a, Block>
{
fn build_anchor(
&self,
hash: Block::Hash,
) -> Result<ChangesTrieAnchorBlockId<Block::Hash, NumberFor<Block>>, String> {
self.storage.build_anchor(hash)
}
fn root(
&self,
anchor: &ChangesTrieAnchorBlockId<Block::Hash, NumberFor<Block>>,
block: NumberFor<Block>,
) -> Result<Option<Block::Hash>, String> {
let root = self.storage.root(anchor, block)?;
if block < self.min {
if let Some(ref root) = root {
self.required_roots_proofs.lock().insert(block, root.clone());
}
}
Ok(root)
}
}
impl<'a, Block: BlockT> ChangesTrieStorage<HashFor<Block>, NumberFor<Block>>
for AccessedRootsRecorder<'a, Block>
{
fn as_roots_storage(
&self,
) -> &dyn sp_state_machine::ChangesTrieRootsStorage<HashFor<Block>, NumberFor<Block>> {
self
}
fn with_cached_changed_keys(
&self,
root: &Block::Hash,
functor: &mut dyn FnMut(&HashMap<Option<PrefixedStorageKey>, HashSet<Vec<u8>>>),
) -> bool {
self.storage.with_cached_changed_keys(root, functor)
}
fn get(&self, key: &Block::Hash, prefix: Prefix) -> Result<Option<DBValue>, String> {
self.storage.get(key, prefix)
}
}
let first_number =
self.backend.blockchain().expect_block_number_from_id(&BlockId::Hash(first))?;
let (storage, configs) = self.require_changes_trie(first_number, last, true)?;
let min_number =
self.backend.blockchain().expect_block_number_from_id(&BlockId::Hash(min))?;
let recording_storage = AccessedRootsRecorder::<Block> {
storage: storage.storage(),
min: min_number,
required_roots_proofs: Mutex::new(BTreeMap::new()),
};
let max_number = std::cmp::min(
self.backend.blockchain().info().best_number,
self.backend.blockchain().expect_block_number_from_id(&BlockId::Hash(max))?,
);
// fetch key changes proof
let mut proof = Vec::new();
for (config_zero, config_end, config) in configs {
let last_number =
self.backend.blockchain().expect_block_number_from_id(&BlockId::Hash(last))?;
let config_range = ChangesTrieConfigurationRange {
config: &config,
zero: config_zero,
end: config_end.map(|(config_end_number, _)| config_end_number),
};
let proof_range = key_changes_proof::<HashFor<Block>, _>(
config_range,
&recording_storage,
first_number,
&ChangesTrieAnchorBlockId { hash: convert_hash(&last), number: last_number },
max_number,
storage_key,
&key.0,
)
.map_err(|err| sp_blockchain::Error::ChangesTrieAccessFailed(err))?;
proof.extend(proof_range);
}
// now gather proofs for all changes tries roots that were touched during key_changes_proof
// execution AND are unknown (i.e. replaced with CHT) to the requester
let roots = recording_storage.required_roots_proofs.into_inner();
let roots_proof = self.changes_trie_roots_proof(cht_size, roots.keys().cloned())?;
Ok(ChangesProof {
max_block: max_number,
proof,
roots: roots.into_iter().map(|(n, h)| (n, convert_hash(&h))).collect(),
roots_proof,
})
}
/// Generate CHT-based proof for roots of changes tries at given blocks.
fn changes_trie_roots_proof<I: IntoIterator<Item = NumberFor<Block>>>(
&self,
cht_size: NumberFor<Block>,
blocks: I,
) -> sp_blockchain::Result<StorageProof> {
// most probably we have touched several changes tries that are parts of the single CHT
// => GroupBy changes tries by CHT number and then gather proof for the whole group at once
let mut proofs = Vec::new();
cht::for_each_cht_group::<Block::Header, _, _, _>(
cht_size,
blocks,
|_, cht_num, cht_blocks| {
let cht_proof =
self.changes_trie_roots_proof_at_cht(cht_size, cht_num, cht_blocks)?;
proofs.push(cht_proof);
Ok(())
},
(),
)?;
Ok(StorageProof::merge(proofs))
}
/// Generates CHT-based proof for roots of changes tries at given blocks
/// (that are part of single CHT).
fn changes_trie_roots_proof_at_cht(
&self,
cht_size: NumberFor<Block>,
cht_num: NumberFor<Block>,
blocks: Vec<NumberFor<Block>>,
) -> sp_blockchain::Result<StorageProof> {
let cht_start = cht::start_number(cht_size, cht_num);
let mut current_num = cht_start;
let cht_range = ::std::iter::from_fn(|| {
let old_current_num = current_num;
current_num = current_num + One::one();
Some(old_current_num)
});
let roots = cht_range.map(|num| {
self.header(&BlockId::Number(num)).map(|block| {
block
.and_then(|block| block.digest().log(DigestItem::as_changes_trie_root).cloned())
})
});
let proof = cht::build_proof::<Block::Header, HashFor<Block>, _, _>(
cht_size, cht_num, blocks, roots,
)?;
Ok(proof)
}
/// Returns changes trie storage and all configurations that have been active
/// in the range [first; last].
///
/// Configurations are returned in descending order (and obviously never overlap).
/// If fail_if_disabled is false, returns maximal consequent configurations ranges,
/// starting from last and stopping on either first, or when CT have been disabled.
/// If fail_if_disabled is true, fails when there's a subrange where CT have been disabled
/// inside first..last blocks range.
fn require_changes_trie(
&self,
first: NumberFor<Block>,
last: Block::Hash,
fail_if_disabled: bool,
) -> sp_blockchain::Result<(
&dyn PrunableStateChangesTrieStorage<Block>,
Vec<(NumberFor<Block>, Option<(NumberFor<Block>, Block::Hash)>, ChangesTrieConfiguration)>,
)> {
let storage = self
.backend
.changes_trie_storage()
.ok_or_else(|| sp_blockchain::Error::ChangesTriesNotSupported)?;
let mut configs = Vec::with_capacity(1);
let mut current = last;
loop {
let config_range = storage.configuration_at(&BlockId::Hash(current))?;
match config_range.config {
Some(config) => configs.push((config_range.zero.0, config_range.end, config)),
None if !fail_if_disabled => return Ok((storage, configs)),
None => return Err(sp_blockchain::Error::ChangesTriesNotSupported),
}
if config_range.zero.0 < first {
break
}
current = *self
.backend
.blockchain()
.expect_header(BlockId::Hash(config_range.zero.1))?
.parent_hash();
}
Ok((storage, configs))
}
/// Apply a checked and validated block to an operation. If a justification is provided
/// then `finalized` *must* be true.
fn apply_block(
@@ -811,7 +560,7 @@ where
sc_consensus::StorageChanges::Changes(storage_changes) => {
self.backend
.begin_state_operation(&mut operation.op, BlockId::Hash(parent_hash))?;
let (main_sc, child_sc, offchain_sc, tx, _, changes_trie_tx, tx_index) =
let (main_sc, child_sc, offchain_sc, tx, _, tx_index) =
storage_changes.into_inner();
if self.config.offchain_indexing_api {
@@ -822,9 +571,6 @@ where
operation.op.update_storage(main_sc.clone(), child_sc.clone())?;
operation.op.update_transaction_index(tx_index)?;
if let Some(changes_trie_transaction) = changes_trie_tx {
operation.op.update_changes_trie(changes_trie_transaction)?;
}
Some((main_sc, child_sc))
},
sc_consensus::StorageChanges::Import(changes) => {
@@ -1003,11 +749,8 @@ where
)?;
let state = self.backend.state_at(at)?;
let changes_trie_state =
changes_tries_state_at_block(&at, self.backend.changes_trie_storage())?;
let gen_storage_changes = runtime_api
.into_storage_changes(&state, changes_trie_state.as_ref(), *parent_hash)
.into_storage_changes(&state, *parent_hash)
.map_err(sp_blockchain::Error::Storage)?;
if import_block.header.state_root() != &gen_storage_changes.transaction_storage_root
@@ -1356,25 +1099,6 @@ where
.map(|(r, p)| (r, StorageProof::merge(vec![p, code_proof])))
}
fn header_proof(
&self,
id: &BlockId<Block>,
) -> sp_blockchain::Result<(Block::Header, StorageProof)> {
self.header_proof_with_cht_size(id, cht::size())
}
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>> {
self.key_changes_proof_with_cht_size(first, last, min, max, storage_key, key, cht::size())
}
fn read_proof_collection(
&self,
id: &BlockId<Block>,
@@ -1540,7 +1264,7 @@ where
fn new_block_at<R: Into<RecordProof>>(
&self,
parent: &BlockId<Block>,
inherent_digests: DigestFor<Block>,
inherent_digests: Digest,
record_proof: R,
) -> sp_blockchain::Result<sc_block_builder::BlockBuilder<Block, Self, B>> {
sc_block_builder::BlockBuilder::new(
@@ -1555,7 +1279,7 @@ where
fn new_block(
&self,
inherent_digests: DigestFor<Block>,
inherent_digests: Digest,
) -> sp_blockchain::Result<sc_block_builder::BlockBuilder<Block, Self, B>> {
let info = self.chain_info();
sc_block_builder::BlockBuilder::new(
@@ -1703,89 +1427,6 @@ where
.child_storage_hash(child_info, &key.0)
.map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))?)
}
fn max_key_changes_range(
&self,
first: NumberFor<Block>,
last: BlockId<Block>,
) -> sp_blockchain::Result<Option<(NumberFor<Block>, BlockId<Block>)>> {
let last_number = self.backend.blockchain().expect_block_number_from_id(&last)?;
let last_hash = self.backend.blockchain().expect_block_hash_from_id(&last)?;
if first > last_number {
return Err(sp_blockchain::Error::ChangesTrieAccessFailed(
"Invalid changes trie range".into(),
))
}
let (storage, configs) = match self.require_changes_trie(first, last_hash, false).ok() {
Some((storage, configs)) => (storage, configs),
None => return Ok(None),
};
let first_available_changes_trie = configs.last().map(|config| config.0);
match first_available_changes_trie {
Some(first_available_changes_trie) => {
let oldest_unpruned = storage.oldest_pruned_digest_range_end();
let first = std::cmp::max(first_available_changes_trie, oldest_unpruned);
Ok(Some((first, last)))
},
None => Ok(None),
}
}
fn key_changes(
&self,
first: NumberFor<Block>,
last: BlockId<Block>,
storage_key: Option<&PrefixedStorageKey>,
key: &StorageKey,
) -> sp_blockchain::Result<Vec<(NumberFor<Block>, u32)>> {
let last_number = self.backend.blockchain().expect_block_number_from_id(&last)?;
let last_hash = self.backend.blockchain().expect_block_hash_from_id(&last)?;
let (storage, configs) = self.require_changes_trie(first, last_hash, true)?;
let mut result = Vec::new();
let best_number = self.backend.blockchain().info().best_number;
for (config_zero, config_end, config) in configs {
let range_first = ::std::cmp::max(first, config_zero + One::one());
let range_anchor = match config_end {
Some((config_end_number, config_end_hash)) =>
if last_number > config_end_number {
ChangesTrieAnchorBlockId {
hash: config_end_hash,
number: config_end_number,
}
} else {
ChangesTrieAnchorBlockId {
hash: convert_hash(&last_hash),
number: last_number,
}
},
None =>
ChangesTrieAnchorBlockId { hash: convert_hash(&last_hash), number: last_number },
};
let config_range = ChangesTrieConfigurationRange {
config: &config,
zero: config_zero.clone(),
end: config_end.map(|(config_end_number, _)| config_end_number),
};
let result_range: Vec<(NumberFor<Block>, u32)> = key_changes::<HashFor<Block>, _>(
config_range,
storage.storage(),
range_first,
&range_anchor,
best_number,
storage_key,
&key.0,
)
.and_then(|r| r.map(|r| r.map(|(block, tx)| (block, tx))).collect::<Result<_, _>>())
.map_err(|err| sp_blockchain::Error::ChangesTrieAccessFailed(err))?;
result.extend(result_range);
}
Ok(result)
}
}
impl<B, E, Block, RA> HeaderMetadata<Block> for Client<B, E, Block, RA>
@@ -1913,16 +1554,6 @@ where
}
}
impl<B, E, Block, RA> ProvideCache<Block> for Client<B, E, Block, RA>
where
B: backend::Backend<Block>,
Block: BlockT,
{
fn cache(&self) -> Option<Arc<dyn Cache<Block>>> {
self.backend.blockchain().cache()
}
}
impl<B, E, Block, RA> ProvideRuntimeApi<Block> for Client<B, E, Block, RA>
where
B: backend::Backend<Block>,
+1 -1
View File
@@ -68,7 +68,7 @@ use sc_client_api::{blockchain::HeaderBackend, BlockchainEvents};
pub use sc_consensus::ImportQueue;
pub use sc_executor::NativeExecutionDispatch;
#[doc(hidden)]
pub use sc_network::config::{OnDemand, TransactionImport, TransactionImportFuture};
pub use sc_network::config::{TransactionImport, TransactionImportFuture};
pub use sc_rpc::Metadata as RpcMetadata;
pub use sc_tracing::TracingReceiver;
pub use sc_transaction_pool::Options as TransactionPoolOptions;
-1
View File
@@ -19,7 +19,6 @@ tokio = { version = "1.10.0", features = ["time"] }
log = "0.4.8"
fdlimit = "0.2.1"
parking_lot = "0.11.1"
sc-light = { version = "4.0.0-dev", path = "../../light" }
sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" }
sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" }
sp-state-machine = { version = "0.10.0-dev", path = "../../../primitives/state-machine" }
@@ -21,7 +21,6 @@ use std::sync::Arc;
type TestBackend = sc_client_api::in_mem::Backend<substrate_test_runtime::Block>;
#[test]
fn test_leaves_with_complex_block_tree() {
let backend = Arc::new(TestBackend::new());
+8 -266
View File
@@ -27,29 +27,25 @@ use sc_client_db::{
use sc_consensus::{
BlockCheckParams, BlockImport, BlockImportParams, ForkChoiceStrategy, ImportResult,
};
use sc_service::client::{self, new_in_mem, Client, LocalCallExecutor};
use sc_service::client::{new_in_mem, Client, LocalCallExecutor};
use sp_api::ProvideRuntimeApi;
use sp_consensus::{BlockOrigin, BlockStatus, Error as ConsensusError, SelectChain};
use sp_core::{blake2_256, testing::TaskExecutor, ChangesTrieConfiguration, H256};
use sp_core::{testing::TaskExecutor, H256};
use sp_runtime::{
generic::BlockId,
traits::{BlakeTwo256, Block as BlockT, Header as HeaderT},
ConsensusEngineId, DigestItem, Justifications,
ConsensusEngineId, Justifications,
};
use sp_state_machine::{
backend::Backend as _, ExecutionStrategy, InMemoryBackend, OverlayedChanges, StateMachine,
};
use sp_storage::{ChildInfo, StorageKey};
use sp_trie::{trie_types::Layout, TrieConfiguration};
use std::{
collections::{HashMap, HashSet},
sync::Arc,
};
use std::{collections::HashSet, sync::Arc};
use substrate_test_runtime::TestAPI;
use substrate_test_runtime_client::{
prelude::*,
runtime::{
self,
genesismap::{insert_genesis_block, GenesisConfig},
Block, BlockNumber, Digest, Hash, Header, RuntimeApi, Transfer,
},
@@ -57,6 +53,8 @@ use substrate_test_runtime_client::{
Sr25519Keyring, TestClientBuilder, TestClientBuilderExt,
};
mod db;
const TEST_ENGINE_ID: ConsensusEngineId = *b"TEST";
pub struct ExecutorDispatch;
@@ -77,86 +75,6 @@ fn executor() -> sc_executor::NativeElseWasmExecutor<ExecutorDispatch> {
sc_executor::NativeElseWasmExecutor::new(sc_executor::WasmExecutionMethod::Interpreted, None, 8)
}
pub fn prepare_client_with_key_changes() -> (
client::Client<
substrate_test_runtime_client::Backend,
substrate_test_runtime_client::ExecutorDispatch,
Block,
RuntimeApi,
>,
Vec<H256>,
Vec<(u64, u64, Vec<u8>, Vec<(u64, u32)>)>,
) {
// prepare block structure
let blocks_transfers = vec![
vec![
(AccountKeyring::Alice, AccountKeyring::Dave),
(AccountKeyring::Bob, AccountKeyring::Dave),
],
vec![(AccountKeyring::Charlie, AccountKeyring::Eve)],
vec![],
vec![(AccountKeyring::Alice, AccountKeyring::Dave)],
];
// prepare client ang import blocks
let mut local_roots = Vec::new();
let config = Some(ChangesTrieConfiguration::new(4, 2));
let mut remote_client = TestClientBuilder::new().changes_trie_config(config).build();
let mut nonces: HashMap<_, u64> = Default::default();
for (i, block_transfers) in blocks_transfers.into_iter().enumerate() {
let mut builder = remote_client.new_block(Default::default()).unwrap();
for (from, to) in block_transfers {
builder
.push_transfer(Transfer {
from: from.into(),
to: to.into(),
amount: 1,
nonce: *nonces.entry(from).and_modify(|n| *n = *n + 1).or_default(),
})
.unwrap();
}
let block = builder.build().unwrap().block;
block_on(remote_client.import(BlockOrigin::Own, block)).unwrap();
let header = remote_client.header(&BlockId::Number(i as u64 + 1)).unwrap().unwrap();
let trie_root = header
.digest()
.log(DigestItem::as_changes_trie_root)
.map(|root| H256::from_slice(root.as_ref()))
.unwrap();
local_roots.push(trie_root);
}
// prepare test cases
let alice = blake2_256(&runtime::system::balance_of_key(AccountKeyring::Alice.into())).to_vec();
let bob = blake2_256(&runtime::system::balance_of_key(AccountKeyring::Bob.into())).to_vec();
let charlie =
blake2_256(&runtime::system::balance_of_key(AccountKeyring::Charlie.into())).to_vec();
let dave = blake2_256(&runtime::system::balance_of_key(AccountKeyring::Dave.into())).to_vec();
let eve = blake2_256(&runtime::system::balance_of_key(AccountKeyring::Eve.into())).to_vec();
let ferdie =
blake2_256(&runtime::system::balance_of_key(AccountKeyring::Ferdie.into())).to_vec();
let test_cases = vec![
(1, 4, alice.clone(), vec![(4, 0), (1, 0)]),
(1, 3, alice.clone(), vec![(1, 0)]),
(2, 4, alice.clone(), vec![(4, 0)]),
(2, 3, alice.clone(), vec![]),
(1, 4, bob.clone(), vec![(1, 1)]),
(1, 1, bob.clone(), vec![(1, 1)]),
(2, 4, bob.clone(), vec![]),
(1, 4, charlie.clone(), vec![(2, 0)]),
(1, 4, dave.clone(), vec![(4, 0), (1, 1), (1, 0)]),
(1, 1, dave.clone(), vec![(1, 1), (1, 0)]),
(3, 4, dave.clone(), vec![(4, 0)]),
(1, 4, eve.clone(), vec![(2, 0)]),
(1, 1, eve.clone(), vec![]),
(3, 4, eve.clone(), vec![]),
(1, 4, ferdie.clone(), vec![]),
];
(remote_client, local_roots, test_cases)
}
fn construct_block(
backend: &InMemoryBackend<BlakeTwo256>,
number: BlockNumber,
@@ -184,7 +102,6 @@ fn construct_block(
StateMachine::new(
backend,
sp_state_machine::disabled_changes_trie_state::<_, u64>(),
&mut overlay,
&executor(),
"Core_initialize_block",
@@ -199,7 +116,6 @@ fn construct_block(
for tx in transactions.iter() {
StateMachine::new(
backend,
sp_state_machine::disabled_changes_trie_state::<_, u64>(),
&mut overlay,
&executor(),
"BlockBuilder_apply_extrinsic",
@@ -214,7 +130,6 @@ fn construct_block(
let ret_data = StateMachine::new(
backend,
sp_state_machine::disabled_changes_trie_state::<_, u64>(),
&mut overlay,
&executor(),
"BlockBuilder_finalize_block",
@@ -248,7 +163,6 @@ fn block1(genesis_hash: Hash, backend: &InMemoryBackend<BlakeTwo256>) -> (Vec<u8
#[test]
fn construct_genesis_should_work_with_native() {
let mut storage = GenesisConfig::new(
None,
vec![Sr25519Keyring::One.public().into(), Sr25519Keyring::Two.public().into()],
vec![AccountKeyring::One.into(), AccountKeyring::Two.into()],
1000,
@@ -267,7 +181,6 @@ fn construct_genesis_should_work_with_native() {
let _ = StateMachine::new(
&backend,
sp_state_machine::disabled_changes_trie_state::<_, u64>(),
&mut overlay,
&executor(),
"Core_execute_block",
@@ -283,7 +196,6 @@ fn construct_genesis_should_work_with_native() {
#[test]
fn construct_genesis_should_work_with_wasm() {
let mut storage = GenesisConfig::new(
None,
vec![Sr25519Keyring::One.public().into(), Sr25519Keyring::Two.public().into()],
vec![AccountKeyring::One.into(), AccountKeyring::Two.into()],
1000,
@@ -302,7 +214,6 @@ fn construct_genesis_should_work_with_wasm() {
let _ = StateMachine::new(
&backend,
sp_state_machine::disabled_changes_trie_state::<_, u64>(),
&mut overlay,
&executor(),
"Core_execute_block",
@@ -318,7 +229,6 @@ fn construct_genesis_should_work_with_wasm() {
#[test]
fn construct_genesis_with_bad_transaction_should_panic() {
let mut storage = GenesisConfig::new(
None,
vec![Sr25519Keyring::One.public().into(), Sr25519Keyring::Two.public().into()],
vec![AccountKeyring::One.into(), AccountKeyring::Two.into()],
68,
@@ -337,7 +247,6 @@ fn construct_genesis_with_bad_transaction_should_panic() {
let r = StateMachine::new(
&backend,
sp_state_machine::disabled_changes_trie_state::<_, u64>(),
&mut overlay,
&executor(),
"Core_execute_block",
@@ -906,23 +815,6 @@ fn best_containing_on_longest_chain_with_max_depth_higher_than_best() {
);
}
#[test]
fn key_changes_works() {
let (client, _, test_cases) = prepare_client_with_key_changes();
for (index, (begin, end, key, expected_result)) in test_cases.into_iter().enumerate() {
let end = client.block_hash(end).unwrap().unwrap();
let actual_result =
client.key_changes(begin, BlockId::Hash(end), None, &StorageKey(key)).unwrap();
if actual_result != expected_result {
panic!(
"Failed test {}: actual = {:?}, expected = {:?}",
index, actual_result, expected_result,
);
}
}
}
#[test]
fn import_with_justification() {
let mut client = substrate_test_runtime_client::new();
@@ -1229,12 +1121,8 @@ fn doesnt_import_blocks_that_revert_finality() {
ClientExt::finalize_block(&client, BlockId::Hash(a2.hash()), None).unwrap();
let import_err = block_on(client.import(BlockOrigin::Own, b3)).err().unwrap();
let expected_err = ConsensusError::ClientImport(
sp_blockchain::Error::RuntimeApiError(sp_api::ApiError::Application(Box::new(
sp_blockchain::Error::NotInFinalizedChain,
)))
.to_string(),
);
let expected_err =
ConsensusError::ClientImport(sp_blockchain::Error::NotInFinalizedChain.to_string());
assert_eq!(import_err.to_string(), expected_err.to_string());
@@ -1536,152 +1424,6 @@ fn returns_status_for_pruned_blocks() {
);
}
#[test]
fn imports_blocks_with_changes_tries_config_change() {
// create client with initial 4^2 configuration
let mut client = TestClientBuilder::with_default_backend()
.changes_trie_config(Some(ChangesTrieConfiguration {
digest_interval: 4,
digest_levels: 2,
}))
.build();
// ===================================================================
// blocks 1,2,3,4,5,6,7,8,9,10 are empty
// block 11 changes the key
// block 12 is the L1 digest that covers this change
// blocks 13,14,15,16,17,18,19,20,21,22 are empty
// block 23 changes the configuration to 5^1 AND is skewed digest
// ===================================================================
// blocks 24,25 are changing the key
// block 26 is empty
// block 27 changes the key
// block 28 is the L1 digest (NOT SKEWED!!!) that covers changes AND changes configuration to
// `3^1`
// ===================================================================
// block 29 is empty
// block 30 changes the key
// block 31 is L1 digest that covers this change
// ===================================================================
(1..11).for_each(|number| {
let block = client
.new_block_at(&BlockId::Number(number - 1), Default::default(), false)
.unwrap()
.build()
.unwrap()
.block;
block_on(client.import(BlockOrigin::Own, block)).unwrap();
});
(11..12).for_each(|number| {
let mut block = client
.new_block_at(&BlockId::Number(number - 1), Default::default(), false)
.unwrap();
block
.push_storage_change(vec![42], Some(number.to_le_bytes().to_vec()))
.unwrap();
let block = block.build().unwrap().block;
block_on(client.import(BlockOrigin::Own, block)).unwrap();
});
(12..23).for_each(|number| {
let block = client
.new_block_at(&BlockId::Number(number - 1), Default::default(), false)
.unwrap()
.build()
.unwrap()
.block;
block_on(client.import(BlockOrigin::Own, block)).unwrap();
});
(23..24).for_each(|number| {
let mut block = client
.new_block_at(&BlockId::Number(number - 1), Default::default(), false)
.unwrap();
block
.push_changes_trie_configuration_update(Some(ChangesTrieConfiguration {
digest_interval: 5,
digest_levels: 1,
}))
.unwrap();
let block = block.build().unwrap().block;
block_on(client.import(BlockOrigin::Own, block)).unwrap();
});
(24..26).for_each(|number| {
let mut block = client
.new_block_at(&BlockId::Number(number - 1), Default::default(), false)
.unwrap();
block
.push_storage_change(vec![42], Some(number.to_le_bytes().to_vec()))
.unwrap();
let block = block.build().unwrap().block;
block_on(client.import(BlockOrigin::Own, block)).unwrap();
});
(26..27).for_each(|number| {
let block = client
.new_block_at(&BlockId::Number(number - 1), Default::default(), false)
.unwrap()
.build()
.unwrap()
.block;
block_on(client.import(BlockOrigin::Own, block)).unwrap();
});
(27..28).for_each(|number| {
let mut block = client
.new_block_at(&BlockId::Number(number - 1), Default::default(), false)
.unwrap();
block
.push_storage_change(vec![42], Some(number.to_le_bytes().to_vec()))
.unwrap();
let block = block.build().unwrap().block;
block_on(client.import(BlockOrigin::Own, block)).unwrap();
});
(28..29).for_each(|number| {
let mut block = client
.new_block_at(&BlockId::Number(number - 1), Default::default(), false)
.unwrap();
block
.push_changes_trie_configuration_update(Some(ChangesTrieConfiguration {
digest_interval: 3,
digest_levels: 1,
}))
.unwrap();
let block = block.build().unwrap().block;
block_on(client.import(BlockOrigin::Own, block)).unwrap();
});
(29..30).for_each(|number| {
let block = client
.new_block_at(&BlockId::Number(number - 1), Default::default(), false)
.unwrap()
.build()
.unwrap()
.block;
block_on(client.import(BlockOrigin::Own, block)).unwrap();
});
(30..31).for_each(|number| {
let mut block = client
.new_block_at(&BlockId::Number(number - 1), Default::default(), false)
.unwrap();
block
.push_storage_change(vec![42], Some(number.to_le_bytes().to_vec()))
.unwrap();
let block = block.build().unwrap().block;
block_on(client.import(BlockOrigin::Own, block)).unwrap();
});
(31..32).for_each(|number| {
let block = client
.new_block_at(&BlockId::Number(number - 1), Default::default(), false)
.unwrap()
.build()
.unwrap()
.block;
block_on(client.import(BlockOrigin::Own, block)).unwrap();
});
// now check that configuration cache works
assert_eq!(
client.key_changes(1, BlockId::Number(31), None, &StorageKey(vec![42])).unwrap(),
vec![(30, 0), (27, 0), (25, 0), (24, 0), (11, 0)]
);
}
#[test]
fn storage_keys_iter_prefix_and_start_key_works() {
let child_info = ChildInfo::new_default(b"child");
+3 -131
View File
@@ -18,7 +18,7 @@
//! Chain api required for the transaction pool.
use codec::{Decode, Encode};
use codec::Encode;
use futures::{
channel::{mpsc, oneshot},
future::{ready, Future, FutureExt, Ready},
@@ -28,16 +28,12 @@ use futures::{
use std::{marker::PhantomData, pin::Pin, sync::Arc};
use prometheus_endpoint::Registry as PrometheusRegistry;
use sc_client_api::{
blockchain::HeaderBackend,
light::{Fetcher, RemoteBodyRequest, RemoteCallRequest},
BlockBackend,
};
use sc_client_api::{blockchain::HeaderBackend, BlockBackend};
use sp_api::{ApiExt, ProvideRuntimeApi};
use sp_core::traits::SpawnEssentialNamed;
use sp_runtime::{
generic::BlockId,
traits::{self, Block as BlockT, BlockIdTo, Hash as HashT, Header as HeaderT},
traits::{self, Block as BlockT, BlockIdTo},
transaction_validity::{TransactionSource, TransactionValidity},
};
use sp_transaction_pool::runtime_api::TaggedTransactionQueue;
@@ -290,127 +286,3 @@ where
validate_transaction_blocking(&*self.client, at, source, uxt)
}
}
/// The transaction pool logic for light client.
pub struct LightChainApi<Client, F, Block> {
client: Arc<Client>,
fetcher: Arc<F>,
_phantom: PhantomData<Block>,
}
impl<Client, F, Block> LightChainApi<Client, F, Block> {
/// Create new transaction pool logic.
pub fn new(client: Arc<Client>, fetcher: Arc<F>) -> Self {
LightChainApi { client, fetcher, _phantom: Default::default() }
}
}
impl<Client, F, Block> graph::ChainApi for LightChainApi<Client, F, Block>
where
Block: BlockT,
Client: HeaderBackend<Block> + 'static,
F: Fetcher<Block> + 'static,
{
type Block = Block;
type Error = error::Error;
type ValidationFuture =
Box<dyn Future<Output = error::Result<TransactionValidity>> + Send + Unpin>;
type BodyFuture = Pin<
Box<
dyn Future<Output = error::Result<Option<Vec<<Self::Block as BlockT>::Extrinsic>>>>
+ Send,
>,
>;
fn validate_transaction(
&self,
at: &BlockId<Self::Block>,
source: TransactionSource,
uxt: graph::ExtrinsicFor<Self>,
) -> Self::ValidationFuture {
let header_hash = self.client.expect_block_hash_from_id(at);
let header_and_hash = header_hash.and_then(|header_hash| {
self.client
.expect_header(BlockId::Hash(header_hash))
.map(|header| (header_hash, header))
});
let (block, header) = match header_and_hash {
Ok((header_hash, header)) => (header_hash, header),
Err(err) => return Box::new(ready(Err(err.into()))),
};
let remote_validation_request = self.fetcher.remote_call(RemoteCallRequest {
block,
header,
method: "TaggedTransactionQueue_validate_transaction".into(),
call_data: (source, uxt, block).encode(),
retry_count: None,
});
let remote_validation_request = remote_validation_request.then(move |result| {
let result: error::Result<TransactionValidity> =
result.map_err(Into::into).and_then(|result| {
Decode::decode(&mut &result[..]).map_err(|e| {
Error::RuntimeApi(format!("Error decoding tx validation result: {:?}", e))
})
});
ready(result)
});
Box::new(remote_validation_request)
}
fn block_id_to_number(
&self,
at: &BlockId<Self::Block>,
) -> error::Result<Option<graph::NumberFor<Self>>> {
Ok(self.client.block_number_from_id(at)?)
}
fn block_id_to_hash(
&self,
at: &BlockId<Self::Block>,
) -> error::Result<Option<graph::BlockHash<Self>>> {
Ok(self.client.block_hash_from_id(at)?)
}
fn hash_and_length(
&self,
ex: &graph::ExtrinsicFor<Self>,
) -> (graph::ExtrinsicHash<Self>, usize) {
ex.using_encoded(|x| (<<Block::Header as HeaderT>::Hashing as HashT>::hash(x), x.len()))
}
fn block_body(&self, id: &BlockId<Self::Block>) -> Self::BodyFuture {
let header = self
.client
.header(*id)
.and_then(|h| h.ok_or_else(|| sp_blockchain::Error::UnknownBlock(format!("{}", id))));
let header = match header {
Ok(header) => header,
Err(err) => {
log::warn!(target: "txpool", "Failed to query header: {:?}", err);
return Box::pin(ready(Ok(None)))
},
};
let fetcher = self.fetcher.clone();
async move {
let transactions = fetcher
.remote_body(RemoteBodyRequest { header, retry_count: None })
.await
.unwrap_or_else(|e| {
log::warn!(target: "txpool", "Failed to fetch block body: {:?}", e);
Vec::new()
});
Ok(Some(transactions))
}
.boxed()
}
fn block_header(
&self,
at: &BlockId<Self::Block>,
) -> Result<Option<<Self::Block as BlockT>::Header>, Self::Error> {
self.client.header(*at).map_err(Into::into)
}
}
+1 -31
View File
@@ -38,7 +38,7 @@ pub mod test_helpers {
};
}
pub use crate::api::{FullChainApi, LightChainApi};
pub use crate::api::FullChainApi;
use futures::{
channel::oneshot,
future::{self, ready},
@@ -79,9 +79,6 @@ type PolledIterator<PoolApi> = Pin<Box<dyn Future<Output = ReadyIteratorFor<Pool
/// A transaction pool for a full node.
pub type FullPool<Block, Client> = BasicPool<FullChainApi<Client, Block>, Block>;
/// A transaction pool for a light node.
pub type LightPool<Block, Client, Fetcher> =
BasicPool<LightChainApi<Client, Fetcher, Block>, Block>;
/// Basic implementation of transaction pool that can be customized by providing PoolApi.
pub struct BasicPool<PoolApi, Block>
@@ -364,33 +361,6 @@ where
}
}
impl<Block, Client, Fetcher> LightPool<Block, Client, Fetcher>
where
Block: BlockT,
Client: sp_blockchain::HeaderBackend<Block> + sc_client_api::UsageProvider<Block> + 'static,
Fetcher: sc_client_api::Fetcher<Block> + 'static,
{
/// Create new basic transaction pool for a light node with the provided api.
pub fn new_light(
options: graph::Options,
prometheus: Option<&PrometheusRegistry>,
spawner: impl SpawnEssentialNamed,
client: Arc<Client>,
fetcher: Arc<Fetcher>,
) -> Self {
let pool_api = Arc::new(LightChainApi::new(client.clone(), fetcher));
Self::with_revalidation_type(
options,
false.into(),
pool_api,
prometheus,
RevalidationType::Light,
spawner,
client.usage_info().chain.best_number,
)
}
}
impl<Block, Client> FullPool<Block, Client>
where
Block: BlockT,