mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 13:27:57 +00:00
new crate sc-light (#6235)
* sc-light * remove unused deps * fix line width * move more fns to sc_light
This commit is contained in:
@@ -0,0 +1,518 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2020 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 std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
use sp_core::ChangesTrieConfiguration;
|
||||
use sp_core::storage::{well_known_keys, ChildInfo};
|
||||
use sp_core::offchain::storage::InMemOffchainStorage;
|
||||
use sp_state_machine::{
|
||||
Backend as StateBackend, TrieBackend, InMemoryBackend, ChangesTrieTransaction,
|
||||
StorageCollection, ChildStorageCollection,
|
||||
};
|
||||
use sp_runtime::{generic::BlockId, Justification, Storage};
|
||||
use sp_runtime::traits::{Block as BlockT, NumberFor, Zero, Header, HashFor};
|
||||
use sp_blockchain::{Error as ClientError, Result as ClientResult};
|
||||
use sc_client_api::{
|
||||
backend::{
|
||||
AuxStore, Backend as ClientBackend, BlockImportOperation, RemoteBackend, NewBlockState,
|
||||
PrunableStateChangesTrieStorage,
|
||||
},
|
||||
blockchain::{
|
||||
HeaderBackend as BlockchainHeaderBackend, well_known_cache_keys,
|
||||
},
|
||||
light::Storage as BlockchainStorage,
|
||||
in_mem::check_genesis_storage,
|
||||
UsageInfo,
|
||||
};
|
||||
use super::blockchain::Blockchain;
|
||||
use hash_db::Hasher;
|
||||
|
||||
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 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>> {
|
||||
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>>,
|
||||
_justification: Option<Justification>,
|
||||
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 reset_storage(&mut self, input: Storage) -> 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);
|
||||
self.storage_update = Some(storage_update);
|
||||
|
||||
Ok(storage_root)
|
||||
}
|
||||
|
||||
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>,
|
||||
_justification: Option<Justification>,
|
||||
) -> ClientResult<()> {
|
||||
self.finalized_blocks.push(block);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mark_head(&mut self, block: BlockId<Block>) -> ClientResult<()> {
|
||||
self.set_head = Some(block);
|
||||
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 for_keys_in_child_storage<A: FnMut(&[u8])>(
|
||||
&self,
|
||||
child_info: &ChildInfo,
|
||||
action: A,
|
||||
) {
|
||||
match *self {
|
||||
GenesisOrUnavailableState::Genesis(ref state) =>
|
||||
state.for_keys_in_child_storage(child_info, 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(&mut self, _stats: &sp_state_machine::StateMachineStats) { }
|
||||
|
||||
fn usage_info(&self) -> sp_state_machine::UsageInfo {
|
||||
sp_state_machine::UsageInfo::empty()
|
||||
}
|
||||
|
||||
fn as_trie_backend(&mut self) -> Option<&TrieBackend<Self::TrieBackendStorage, H>> {
|
||||
match self {
|
||||
GenesisOrUnavailableState::Genesis(ref mut state) => state.as_trie_backend(),
|
||||
GenesisOrUnavailableState::Unavailable => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2020 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::{Justification, generic::BlockId};
|
||||
use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero};
|
||||
|
||||
use sp_blockchain::{
|
||||
HeaderMetadata, CachedHeaderMetadata, Error as ClientError, Result as ClientResult,
|
||||
};
|
||||
pub use sc_client_api::{
|
||||
backend::{
|
||||
AuxStore, NewBlockState
|
||||
},
|
||||
blockchain::{
|
||||
Backend as BlockchainBackend, BlockStatus, Cache as BlockchainCache,
|
||||
HeaderBackend as BlockchainHeaderBackend, Info as BlockchainInfo, ProvideCache,
|
||||
well_known_cache_keys,
|
||||
},
|
||||
light::{
|
||||
RemoteBlockchain, LocalOrRemote, Storage
|
||||
},
|
||||
cht,
|
||||
};
|
||||
use crate::fetcher::RemoteHeaderRequest;
|
||||
|
||||
/// 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 justification(&self, _id: BlockId<Block>) -> ClientResult<Option<Justification>> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2020 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::{
|
||||
sync::Arc, panic::UnwindSafe, result, cell::RefCell,
|
||||
};
|
||||
|
||||
use codec::{Encode, Decode};
|
||||
use sp_core::{convert_hash, NativeOrEncoded, traits::CodeExecutor, offchain::storage::OffchainOverlayedChanges};
|
||||
use sp_runtime::{
|
||||
generic::BlockId, traits::{One, Block as BlockT, Header as HeaderT, HashFor},
|
||||
};
|
||||
use sp_externalities::Extensions;
|
||||
use sp_state_machine::{
|
||||
self, Backend as StateBackend, OverlayedChanges, ExecutionStrategy, create_proof_check_backend,
|
||||
execution_proof_check_on_trie_backend, ExecutionManager, StorageProof, CloneableSpawn,
|
||||
};
|
||||
use hash_db::Hasher;
|
||||
|
||||
use sp_api::{ProofRecorder, InitializeBlock, StorageTransactionCache};
|
||||
|
||||
use sp_blockchain::{Error as ClientError, Result as ClientResult};
|
||||
|
||||
use sc_client_api::{
|
||||
backend::RemoteBackend,
|
||||
light::RemoteCallRequest,
|
||||
call_executor::CallExecutor,
|
||||
};
|
||||
use sc_executor::{RuntimeVersion, NativeVersion};
|
||||
|
||||
/// 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>> {
|
||||
match self.backend.is_local_state_available(id) {
|
||||
true => self.local.call(id, method, call_data, strategy, extensions),
|
||||
false => Err(ClientError::NotAvailableOnLightClient),
|
||||
}
|
||||
}
|
||||
|
||||
fn contextual_call<
|
||||
'a,
|
||||
IB: Fn() -> ClientResult<()>,
|
||||
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, String> + UnwindSafe,
|
||||
>(
|
||||
&self,
|
||||
initialize_block_fn: IB,
|
||||
at: &BlockId<Block>,
|
||||
method: &str,
|
||||
call_data: &[u8],
|
||||
changes: &RefCell<OverlayedChanges>,
|
||||
offchain_changes: &RefCell<OffchainOverlayedChanges>,
|
||||
_: Option<&RefCell<StorageTransactionCache<Block, B::State>>>,
|
||||
initialize_block: InitializeBlock<'a, Block>,
|
||||
_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
|
||||
|
||||
match self.backend.is_local_state_available(at) {
|
||||
true => CallExecutor::contextual_call::<
|
||||
_,
|
||||
fn(
|
||||
Result<NativeOrEncoded<R>, Local::Error>,
|
||||
Result<NativeOrEncoded<R>, Local::Error>,
|
||||
) -> Result<NativeOrEncoded<R>, Local::Error>,
|
||||
_,
|
||||
NC
|
||||
>(
|
||||
&self.local,
|
||||
initialize_block_fn,
|
||||
at,
|
||||
method,
|
||||
call_data,
|
||||
changes,
|
||||
offchain_changes,
|
||||
None,
|
||||
initialize_block,
|
||||
ExecutionManager::NativeWhenPossible,
|
||||
native_call,
|
||||
recorder,
|
||||
extensions,
|
||||
).map_err(|e| ClientError::Execution(Box::new(e.to_string()))),
|
||||
false => Err(ClientError::NotAvailableOnLightClient),
|
||||
}
|
||||
}
|
||||
|
||||
fn runtime_version(&self, id: &BlockId<Block>) -> ClientResult<RuntimeVersion> {
|
||||
match self.backend.is_local_state_available(id) {
|
||||
true => self.local.runtime_version(id),
|
||||
false => Err(ClientError::NotAvailableOnLightClient),
|
||||
}
|
||||
}
|
||||
|
||||
fn prove_at_trie_state<S: sp_state_machine::TrieBackendStorage<HashFor<Block>>>(
|
||||
&self,
|
||||
_state: &sp_state_machine::TrieBackend<S, HashFor<Block>>,
|
||||
_changes: &mut OverlayedChanges,
|
||||
_method: &str,
|
||||
_call_data: &[u8],
|
||||
) -> ClientResult<(Vec<u8>, StorageProof)> {
|
||||
Err(ClientError::NotAvailableOnLightClient)
|
||||
}
|
||||
|
||||
fn native_runtime_version(&self) -> Option<&NativeVersion> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Prove contextual execution using given block header in environment.
|
||||
///
|
||||
/// Method is executed using passed header as environment' current block.
|
||||
/// Proof includes both environment preparation proof and method execution proof.
|
||||
pub fn prove_execution<Block, S, E>(
|
||||
mut state: S,
|
||||
header: Block::Header,
|
||||
executor: &E,
|
||||
method: &str,
|
||||
call_data: &[u8],
|
||||
) -> ClientResult<(Vec<u8>, StorageProof)>
|
||||
where
|
||||
Block: BlockT,
|
||||
S: StateBackend<HashFor<Block>>,
|
||||
E: CallExecutor<Block>,
|
||||
{
|
||||
let trie_state = state.as_trie_backend()
|
||||
.ok_or_else(||
|
||||
Box::new(sp_state_machine::ExecutionError::UnableToGenerateProof) as
|
||||
Box<dyn sp_state_machine::Error>
|
||||
)?;
|
||||
|
||||
// prepare execution environment + record preparation proof
|
||||
let mut changes = Default::default();
|
||||
let (_, init_proof) = executor.prove_at_trie_state(
|
||||
trie_state,
|
||||
&mut changes,
|
||||
"Core_initialize_block",
|
||||
&header.encode(),
|
||||
)?;
|
||||
|
||||
// execute method + record execution proof
|
||||
let (result, exec_proof) = executor.prove_at_trie_state(
|
||||
&trie_state,
|
||||
&mut changes,
|
||||
method,
|
||||
call_data,
|
||||
)?;
|
||||
let total_proof = StorageProof::merge(vec![init_proof, exec_proof]);
|
||||
|
||||
Ok((result, total_proof))
|
||||
}
|
||||
|
||||
/// Check remote contextual execution proof using given backend.
|
||||
///
|
||||
/// Method is executed using passed header as environment' current block.
|
||||
/// Proof should include both environment preparation proof and method execution proof.
|
||||
pub fn check_execution_proof<Header, E, H>(
|
||||
executor: &E,
|
||||
spawn_handle: Box<dyn CloneableSpawn>,
|
||||
request: &RemoteCallRequest<Header>,
|
||||
remote_proof: StorageProof,
|
||||
) -> ClientResult<Vec<u8>>
|
||||
where
|
||||
Header: HeaderT,
|
||||
E: CodeExecutor + Clone + 'static,
|
||||
H: Hasher,
|
||||
H::Out: Ord + codec::Codec + 'static,
|
||||
{
|
||||
check_execution_proof_with_make_header::<Header, E, H, _>(
|
||||
executor,
|
||||
spawn_handle,
|
||||
request,
|
||||
remote_proof,
|
||||
|header| <Header as HeaderT>::new(
|
||||
*header.number() + One::one(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
header.hash(),
|
||||
Default::default(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/// Check remote contextual execution proof using given backend and header factory.
|
||||
///
|
||||
/// Method is executed using passed header as environment' current block.
|
||||
/// Proof should include both environment preparation proof and method execution proof.
|
||||
pub fn check_execution_proof_with_make_header<Header, E, H, MakeNextHeader>(
|
||||
executor: &E,
|
||||
spawn_handle: Box<dyn CloneableSpawn>,
|
||||
request: &RemoteCallRequest<Header>,
|
||||
remote_proof: StorageProof,
|
||||
make_next_header: MakeNextHeader,
|
||||
) -> ClientResult<Vec<u8>>
|
||||
where
|
||||
E: CodeExecutor + Clone + 'static,
|
||||
H: Hasher,
|
||||
Header: HeaderT,
|
||||
H::Out: Ord + codec::Codec + 'static,
|
||||
MakeNextHeader: Fn(&Header) -> Header,
|
||||
{
|
||||
let local_state_root = request.header.state_root();
|
||||
let root: H::Out = convert_hash(&local_state_root);
|
||||
|
||||
// prepare execution environment + check preparation proof
|
||||
let mut changes = OverlayedChanges::default();
|
||||
let trie_backend = create_proof_check_backend(root, remote_proof)?;
|
||||
let next_header = make_next_header(&request.header);
|
||||
|
||||
// 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()?;
|
||||
|
||||
execution_proof_check_on_trie_backend::<H, Header::Number, _>(
|
||||
&trie_backend,
|
||||
&mut changes,
|
||||
executor,
|
||||
spawn_handle.clone(),
|
||||
"Core_initialize_block",
|
||||
&next_header.encode(),
|
||||
&runtime_code,
|
||||
)?;
|
||||
|
||||
// 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)
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2020 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 data fetcher. Fetches requested data from remote full nodes.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use hash_db::{HashDB, Hasher, EMPTY_PREFIX};
|
||||
use codec::{Decode, Encode};
|
||||
use sp_core::{convert_hash, traits::CodeExecutor};
|
||||
use sp_core::storage::{ChildInfo, ChildType};
|
||||
use sp_runtime::traits::{
|
||||
Block as BlockT, Header as HeaderT, Hash, HashFor, NumberFor,
|
||||
AtLeast32Bit, CheckedConversion,
|
||||
};
|
||||
use sp_state_machine::{
|
||||
ChangesTrieRootsStorage, ChangesTrieAnchorBlockId, ChangesTrieConfigurationRange,
|
||||
InMemoryChangesTrieStorage, TrieBackend, read_proof_check, key_changes_proof_check_with_db,
|
||||
read_child_proof_check, CloneableSpawn,
|
||||
};
|
||||
pub use sp_state_machine::StorageProof;
|
||||
use sp_blockchain::{Error as ClientError, Result as ClientResult};
|
||||
|
||||
pub use sc_client_api::{
|
||||
light::{
|
||||
RemoteCallRequest, RemoteHeaderRequest, RemoteReadRequest, RemoteReadChildRequest,
|
||||
RemoteChangesRequest, ChangesProof, RemoteBodyRequest, Fetcher, FetchChecker,
|
||||
Storage as BlockchainStorage,
|
||||
},
|
||||
cht,
|
||||
};
|
||||
use crate::blockchain::Blockchain;
|
||||
use crate::call_executor::check_execution_proof;
|
||||
|
||||
/// Remote data checker.
|
||||
pub struct LightDataChecker<E, H, B: BlockT, S: BlockchainStorage<B>> {
|
||||
blockchain: Arc<Blockchain<S>>,
|
||||
executor: E,
|
||||
spawn_handle: Box<dyn CloneableSpawn>,
|
||||
_hasher: PhantomData<(B, H)>,
|
||||
}
|
||||
|
||||
impl<E, H, B: BlockT, S: BlockchainStorage<B>> LightDataChecker<E, H, B, S> {
|
||||
/// Create new light data checker.
|
||||
pub fn new(blockchain: Arc<Blockchain<S>>, executor: E, spawn_handle: Box<dyn CloneableSpawn>) -> Self {
|
||||
Self {
|
||||
blockchain, executor, spawn_handle, _hasher: PhantomData
|
||||
}
|
||||
}
|
||||
|
||||
/// Check remote changes query proof assuming that CHT-s are of given size.
|
||||
pub fn check_changes_proof_with_cht_size(
|
||||
&self,
|
||||
request: &RemoteChangesRequest<B::Header>,
|
||||
remote_proof: ChangesProof<B::Header>,
|
||||
cht_size: NumberFor<B>,
|
||||
) -> ClientResult<Vec<(NumberFor<B>, u32)>>
|
||||
where
|
||||
H: Hasher,
|
||||
H::Out: Ord + codec::Codec,
|
||||
{
|
||||
// since we need roots of all changes tries for the range begin..max
|
||||
// => remote node can't use max block greater that one that we have passed
|
||||
if remote_proof.max_block > request.max_block.0 || remote_proof.max_block < request.last_block.0 {
|
||||
return Err(ClientError::ChangesTrieAccessFailed(format!(
|
||||
"Invalid max_block used by the remote node: {}. Local: {}..{}..{}",
|
||||
remote_proof.max_block, request.first_block.0, request.last_block.0, request.max_block.0,
|
||||
)).into());
|
||||
}
|
||||
|
||||
// check if remote node has responded with extra changes trie roots proofs
|
||||
// all changes tries roots must be in range [request.first_block.0; request.tries_roots.0)
|
||||
let is_extra_first_root = remote_proof.roots.keys().next()
|
||||
.map(|first_root| *first_root < request.first_block.0
|
||||
|| *first_root >= request.tries_roots.0)
|
||||
.unwrap_or(false);
|
||||
let is_extra_last_root = remote_proof.roots.keys().next_back()
|
||||
.map(|last_root| *last_root >= request.tries_roots.0)
|
||||
.unwrap_or(false);
|
||||
if is_extra_first_root || is_extra_last_root {
|
||||
return Err(ClientError::ChangesTrieAccessFailed(format!(
|
||||
"Extra changes tries roots proofs provided by the remote node: [{:?}..{:?}]. Expected in range: [{}; {})",
|
||||
remote_proof.roots.keys().next(), remote_proof.roots.keys().next_back(),
|
||||
request.first_block.0, request.tries_roots.0,
|
||||
)).into());
|
||||
}
|
||||
|
||||
// if request has been composed when some required headers were already pruned
|
||||
// => remote node has sent us CHT-based proof of required changes tries roots
|
||||
// => check that this proof is correct before proceeding with changes proof
|
||||
let remote_max_block = remote_proof.max_block;
|
||||
let remote_roots = remote_proof.roots;
|
||||
let remote_roots_proof = remote_proof.roots_proof;
|
||||
let remote_proof = remote_proof.proof;
|
||||
if !remote_roots.is_empty() {
|
||||
self.check_changes_tries_proof(
|
||||
cht_size,
|
||||
&remote_roots,
|
||||
remote_roots_proof,
|
||||
)?;
|
||||
}
|
||||
|
||||
// and now check the key changes proof + get the changes
|
||||
let mut result = Vec::new();
|
||||
let proof_storage = InMemoryChangesTrieStorage::with_proof(remote_proof);
|
||||
for config_range in &request.changes_trie_configs {
|
||||
let result_range = key_changes_proof_check_with_db::<H, _>(
|
||||
ChangesTrieConfigurationRange {
|
||||
config: config_range.config.as_ref().ok_or(ClientError::ChangesTriesNotSupported)?,
|
||||
zero: config_range.zero.0,
|
||||
end: config_range.end.map(|(n, _)| n),
|
||||
},
|
||||
&RootsStorage {
|
||||
roots: (request.tries_roots.0, &request.tries_roots.2),
|
||||
prev_roots: &remote_roots,
|
||||
},
|
||||
&proof_storage,
|
||||
request.first_block.0,
|
||||
&ChangesTrieAnchorBlockId {
|
||||
hash: convert_hash(&request.last_block.1),
|
||||
number: request.last_block.0,
|
||||
},
|
||||
remote_max_block,
|
||||
request.storage_key.as_ref(),
|
||||
&request.key)
|
||||
.map_err(|err| ClientError::ChangesTrieAccessFailed(err))?;
|
||||
result.extend(result_range);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Check CHT-based proof for changes tries roots.
|
||||
pub fn check_changes_tries_proof(
|
||||
&self,
|
||||
cht_size: NumberFor<B>,
|
||||
remote_roots: &BTreeMap<NumberFor<B>, B::Hash>,
|
||||
remote_roots_proof: StorageProof,
|
||||
) -> ClientResult<()>
|
||||
where
|
||||
H: Hasher,
|
||||
H::Out: Ord + codec::Codec,
|
||||
{
|
||||
// all the checks are sharing the same storage
|
||||
let storage = remote_roots_proof.into_memory_db();
|
||||
|
||||
// remote_roots.keys() are sorted => we can use this to group changes tries roots
|
||||
// that are belongs to the same CHT
|
||||
let blocks = remote_roots.keys().cloned();
|
||||
cht::for_each_cht_group::<B::Header, _, _, _>(cht_size, blocks, |mut storage, _, cht_blocks| {
|
||||
// get local changes trie CHT root for given CHT
|
||||
// it should be there, because it is never pruned AND request has been composed
|
||||
// when required header has been pruned (=> replaced with CHT)
|
||||
let first_block = cht_blocks.first().cloned()
|
||||
.expect("for_each_cht_group never calls callback with empty groups");
|
||||
let local_cht_root = self.blockchain.storage().changes_trie_cht_root(cht_size, first_block)?
|
||||
.ok_or(ClientError::InvalidCHTProof)?;
|
||||
|
||||
// check changes trie root for every block within CHT range
|
||||
for block in cht_blocks {
|
||||
// check if the proofs storage contains the root
|
||||
// normally this happens in when the proving backend is created, but since
|
||||
// we share the storage for multiple checks, do it here
|
||||
let mut cht_root = H::Out::default();
|
||||
cht_root.as_mut().copy_from_slice(local_cht_root.as_ref());
|
||||
if !storage.contains(&cht_root, EMPTY_PREFIX) {
|
||||
return Err(ClientError::InvalidCHTProof.into());
|
||||
}
|
||||
|
||||
// check proof for single changes trie root
|
||||
let proving_backend = TrieBackend::new(storage, cht_root);
|
||||
let remote_changes_trie_root = remote_roots[&block];
|
||||
cht::check_proof_on_proving_backend::<B::Header, H>(
|
||||
local_cht_root,
|
||||
block,
|
||||
remote_changes_trie_root,
|
||||
&proving_backend,
|
||||
)?;
|
||||
|
||||
// and return the storage to use in following checks
|
||||
storage = proving_backend.into_storage();
|
||||
}
|
||||
|
||||
Ok(storage)
|
||||
}, storage)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, Block, H, S> FetchChecker<Block> for LightDataChecker<E, H, Block, S>
|
||||
where
|
||||
Block: BlockT,
|
||||
E: CodeExecutor + Clone + 'static,
|
||||
H: Hasher,
|
||||
H::Out: Ord + codec::Codec + 'static,
|
||||
S: BlockchainStorage<Block>,
|
||||
{
|
||||
fn check_header_proof(
|
||||
&self,
|
||||
request: &RemoteHeaderRequest<Block::Header>,
|
||||
remote_header: Option<Block::Header>,
|
||||
remote_proof: StorageProof,
|
||||
) -> ClientResult<Block::Header> {
|
||||
let remote_header = remote_header.ok_or_else(||
|
||||
ClientError::from(ClientError::InvalidCHTProof))?;
|
||||
let remote_header_hash = remote_header.hash();
|
||||
cht::check_proof::<Block::Header, H>(
|
||||
request.cht_root,
|
||||
request.block,
|
||||
remote_header_hash,
|
||||
remote_proof,
|
||||
).map(|_| remote_header)
|
||||
}
|
||||
|
||||
fn check_read_proof(
|
||||
&self,
|
||||
request: &RemoteReadRequest<Block::Header>,
|
||||
remote_proof: StorageProof,
|
||||
) -> ClientResult<HashMap<Vec<u8>, Option<Vec<u8>>>> {
|
||||
read_proof_check::<H, _>(
|
||||
convert_hash(request.header.state_root()),
|
||||
remote_proof,
|
||||
request.keys.iter(),
|
||||
).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn check_read_child_proof(
|
||||
&self,
|
||||
request: &RemoteReadChildRequest<Block::Header>,
|
||||
remote_proof: StorageProof,
|
||||
) -> ClientResult<HashMap<Vec<u8>, Option<Vec<u8>>>> {
|
||||
let child_info = match ChildType::from_prefixed_key(&request.storage_key) {
|
||||
Some((ChildType::ParentKeyId, storage_key)) => ChildInfo::new_default(storage_key),
|
||||
None => return Err("Invalid child type".into()),
|
||||
};
|
||||
read_child_proof_check::<H, _>(
|
||||
convert_hash(request.header.state_root()),
|
||||
remote_proof,
|
||||
&child_info,
|
||||
request.keys.iter(),
|
||||
).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn check_execution_proof(
|
||||
&self,
|
||||
request: &RemoteCallRequest<Block::Header>,
|
||||
remote_proof: StorageProof,
|
||||
) -> ClientResult<Vec<u8>> {
|
||||
check_execution_proof::<_, _, H>(
|
||||
&self.executor,
|
||||
self.spawn_handle.clone(),
|
||||
request,
|
||||
remote_proof,
|
||||
)
|
||||
}
|
||||
|
||||
fn check_changes_proof(
|
||||
&self,
|
||||
request: &RemoteChangesRequest<Block::Header>,
|
||||
remote_proof: ChangesProof<Block::Header>
|
||||
) -> ClientResult<Vec<(NumberFor<Block>, u32)>> {
|
||||
self.check_changes_proof_with_cht_size(request, remote_proof, cht::size())
|
||||
}
|
||||
|
||||
fn check_body_proof(
|
||||
&self,
|
||||
request: &RemoteBodyRequest<Block::Header>,
|
||||
body: Vec<Block::Extrinsic>
|
||||
) -> ClientResult<Vec<Block::Extrinsic>> {
|
||||
// TODO: #2621
|
||||
let extrinsics_root = HashFor::<Block>::ordered_trie_root(
|
||||
body.iter().map(Encode::encode).collect(),
|
||||
);
|
||||
if *request.header.extrinsics_root() == extrinsics_root {
|
||||
Ok(body)
|
||||
} else {
|
||||
Err(format!("RemoteBodyRequest: invalid extrinsics root expected: {} but got {}",
|
||||
*request.header.extrinsics_root(),
|
||||
extrinsics_root,
|
||||
).into())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// A view of BTreeMap<Number, Hash> as a changes trie roots storage.
|
||||
struct RootsStorage<'a, Number: AtLeast32Bit, Hash: 'a> {
|
||||
roots: (Number, &'a [Hash]),
|
||||
prev_roots: &'a BTreeMap<Number, Hash>,
|
||||
}
|
||||
|
||||
impl<'a, H, Number, Hash> ChangesTrieRootsStorage<H, Number> for RootsStorage<'a, Number, Hash>
|
||||
where
|
||||
H: Hasher,
|
||||
Number: std::fmt::Display + std::hash::Hash + Clone + AtLeast32Bit + Encode + Decode + Send + Sync + 'static,
|
||||
Hash: 'a + Send + Sync + Clone + AsRef<[u8]>,
|
||||
{
|
||||
fn build_anchor(
|
||||
&self,
|
||||
_hash: H::Out,
|
||||
) -> Result<sp_state_machine::ChangesTrieAnchorBlockId<H::Out, Number>, String> {
|
||||
Err("build_anchor is only called when building block".into())
|
||||
}
|
||||
|
||||
fn root(
|
||||
&self,
|
||||
_anchor: &ChangesTrieAnchorBlockId<H::Out, Number>,
|
||||
block: Number,
|
||||
) -> Result<Option<H::Out>, String> {
|
||||
// we can't ask for roots from parallel forks here => ignore anchor
|
||||
let root = if block < self.roots.0 {
|
||||
self.prev_roots.get(&Number::unique_saturated_from(block)).cloned()
|
||||
} else {
|
||||
let index: Option<usize> = block.checked_sub(&self.roots.0).and_then(|index| index.checked_into());
|
||||
match index {
|
||||
Some(index) => self.roots.1.get(index as usize).cloned(),
|
||||
None => None,
|
||||
}
|
||||
};
|
||||
|
||||
Ok(root.map(|root| {
|
||||
let mut hasher_root: H::Out = Default::default();
|
||||
hasher_root.as_mut().copy_from_slice(root.as_ref());
|
||||
hasher_root
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2020 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 sc_client_api::CloneableSpawn;
|
||||
use std::sync::Arc;
|
||||
use sp_core::traits::CodeExecutor;
|
||||
|
||||
pub mod backend;
|
||||
pub mod blockchain;
|
||||
pub mod call_executor;
|
||||
pub mod fetcher;
|
||||
|
||||
pub use {backend::*, blockchain::*, call_executor::*, fetcher::*};
|
||||
|
||||
/// Create an instance of fetch data checker.
|
||||
pub fn new_fetch_checker<E, B: BlockT, S: BlockchainStorage<B>>(
|
||||
blockchain: Arc<Blockchain<S>>,
|
||||
executor: E,
|
||||
spawn_handle: Box<dyn CloneableSpawn>,
|
||||
) -> LightDataChecker<E, HashFor<B>, B, S>
|
||||
where
|
||||
E: CodeExecutor,
|
||||
{
|
||||
LightDataChecker::new(blockchain, executor, spawn_handle)
|
||||
}
|
||||
|
||||
/// Create an instance of light client blockchain backend.
|
||||
pub fn new_light_blockchain<B: BlockT, S: BlockchainStorage<B>>(storage: S) -> Arc<Blockchain<S>> {
|
||||
Arc::new(Blockchain::new(storage))
|
||||
}
|
||||
|
||||
/// 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))
|
||||
}
|
||||
Reference in New Issue
Block a user