mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 02:21:14 +00:00
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:
@@ -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>>;
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
},
|
||||
(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())]
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
-2351
File diff suppressed because it is too large
Load Diff
-187
@@ -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(¤t)?;
|
||||
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
@@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
-413
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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>,
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 = []
|
||||
@@ -1,3 +0,0 @@
|
||||
Light client components.
|
||||
|
||||
License: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
¶ms.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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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>>,
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user