mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-29 20:31:04 +00:00
Make light client backend only work with locally available data (#3538)
* removing fetcher dependency from light backend * fix compilation
This commit is contained in:
committed by
Gavin Wood
parent
fd924c07ed
commit
634ca73e50
@@ -18,7 +18,7 @@
|
||||
//! Everything else is requested from full nodes on demand.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::sync::Arc;
|
||||
use parking_lot::{RwLock, Mutex};
|
||||
|
||||
use sr_primitives::{generic::BlockId, Justification, StorageOverlay, ChildrenStorageOverlay};
|
||||
@@ -32,7 +32,6 @@ use crate::backend::{
|
||||
use crate::blockchain::HeaderBackend as BlockchainHeaderBackend;
|
||||
use crate::error::{Error as ClientError, Result as ClientResult};
|
||||
use crate::light::blockchain::{Blockchain, Storage as BlockchainStorage};
|
||||
use crate::light::fetcher::{Fetcher, RemoteReadRequest};
|
||||
use hash_db::Hasher;
|
||||
use trie::MemoryDB;
|
||||
use consensus::well_known_cache_keys;
|
||||
@@ -40,14 +39,14 @@ use consensus::well_known_cache_keys;
|
||||
const IN_MEMORY_EXPECT_PROOF: &str = "InMemory state backend has Void error type and always succeeds; qed";
|
||||
|
||||
/// Light client backend.
|
||||
pub struct Backend<S, F, H: Hasher> {
|
||||
blockchain: Arc<Blockchain<S, F>>,
|
||||
pub struct Backend<S, H: Hasher> {
|
||||
blockchain: Arc<Blockchain<S>>,
|
||||
genesis_state: RwLock<Option<InMemoryState<H>>>,
|
||||
import_lock: Mutex<()>,
|
||||
}
|
||||
|
||||
/// Light block (header and justification) import operation.
|
||||
pub struct ImportOperation<Block: BlockT, S, F, H: Hasher> {
|
||||
pub struct ImportOperation<Block: BlockT, S, H: Hasher> {
|
||||
header: Option<Block::Header>,
|
||||
cache: HashMap<well_known_cache_keys::Id, Vec<u8>>,
|
||||
leaf_state: NewBlockState,
|
||||
@@ -55,28 +54,21 @@ pub struct ImportOperation<Block: BlockT, S, F, H: Hasher> {
|
||||
finalized_blocks: Vec<BlockId<Block>>,
|
||||
set_head: Option<BlockId<Block>>,
|
||||
storage_update: Option<InMemoryState<H>>,
|
||||
_phantom: ::std::marker::PhantomData<(S, F)>,
|
||||
_phantom: ::std::marker::PhantomData<(S)>,
|
||||
}
|
||||
|
||||
/// On-demand state.
|
||||
pub struct OnDemandState<Block: BlockT, S, F> {
|
||||
fetcher: Weak<F>,
|
||||
blockchain: Weak<Blockchain<S, F>>,
|
||||
block: Block::Hash,
|
||||
cached_header: RwLock<Option<Block::Header>>,
|
||||
}
|
||||
|
||||
/// On-demand or in-memory genesis state.
|
||||
pub enum OnDemandOrGenesisState<Block: BlockT, S, F, H: Hasher> {
|
||||
/// On-demand state - storage values are fetched from remote nodes.
|
||||
OnDemand(OnDemandState<Block, S, F>),
|
||||
/// Either in-memory genesis state, or locally-unavailable state.
|
||||
pub enum GenesisOrUnavailableState<H: Hasher> {
|
||||
/// Genesis state - storage values are stored in-memory.
|
||||
Genesis(InMemoryState<H>),
|
||||
/// We know that state exists, but all calls will fail with error, because it
|
||||
/// isn't locally available.
|
||||
Unavailable,
|
||||
}
|
||||
|
||||
impl<S, F, H: Hasher> Backend<S, F, H> {
|
||||
impl<S, H: Hasher> Backend<S, H> {
|
||||
/// Create new light backend.
|
||||
pub fn new(blockchain: Arc<Blockchain<S, F>>) -> Self {
|
||||
pub fn new(blockchain: Arc<Blockchain<S>>) -> Self {
|
||||
Self {
|
||||
blockchain,
|
||||
genesis_state: RwLock::new(None),
|
||||
@@ -85,12 +77,12 @@ impl<S, F, H: Hasher> Backend<S, F, H> {
|
||||
}
|
||||
|
||||
/// Get shared blockchain reference.
|
||||
pub fn blockchain(&self) -> &Arc<Blockchain<S, F>> {
|
||||
pub fn blockchain(&self) -> &Arc<Blockchain<S>> {
|
||||
&self.blockchain
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AuxStore, F, H: Hasher> AuxStore for Backend<S, F, H> {
|
||||
impl<S: AuxStore, H: Hasher> AuxStore for Backend<S, H> {
|
||||
fn insert_aux<
|
||||
'a,
|
||||
'b: 'a,
|
||||
@@ -106,16 +98,15 @@ impl<S: AuxStore, F, H: Hasher> AuxStore for Backend<S, F, H> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, F, Block, H> ClientBackend<Block, H> for Backend<S, F, H> where
|
||||
impl<S, Block, H> ClientBackend<Block, H> for Backend<S, H> where
|
||||
Block: BlockT,
|
||||
S: BlockchainStorage<Block>,
|
||||
F: Fetcher<Block>,
|
||||
H: Hasher<Out=Block::Hash>,
|
||||
H::Out: Ord,
|
||||
{
|
||||
type BlockImportOperation = ImportOperation<Block, S, F, H>;
|
||||
type Blockchain = Blockchain<S, F>;
|
||||
type State = OnDemandOrGenesisState<Block, S, F, H>;
|
||||
type BlockImportOperation = ImportOperation<Block, S, H>;
|
||||
type Blockchain = Blockchain<S>;
|
||||
type State = GenesisOrUnavailableState<H>;
|
||||
type ChangesTrieStorage = in_mem::ChangesTrieStorage<Block, H>;
|
||||
type OffchainStorage = in_mem::OffchainStorage;
|
||||
|
||||
@@ -183,7 +174,7 @@ impl<S, F, Block, H> ClientBackend<Block, H> for Backend<S, F, H> where
|
||||
self.blockchain.storage().finalize_header(block)
|
||||
}
|
||||
|
||||
fn blockchain(&self) -> &Blockchain<S, F> {
|
||||
fn blockchain(&self) -> &Blockchain<S> {
|
||||
&self.blockchain
|
||||
}
|
||||
|
||||
@@ -205,22 +196,17 @@ impl<S, F, Block, H> ClientBackend<Block, H> for Backend<S, F, H> where
|
||||
// special case for genesis block
|
||||
if block_number.is_zero() {
|
||||
if let Some(genesis_state) = self.genesis_state.read().clone() {
|
||||
return Ok(OnDemandOrGenesisState::Genesis(genesis_state));
|
||||
return Ok(GenesisOrUnavailableState::Genesis(genesis_state));
|
||||
}
|
||||
}
|
||||
|
||||
// else create on-demand state
|
||||
let block_hash = self.blockchain.expect_block_hash_from_id(&block)?;
|
||||
Ok(OnDemandOrGenesisState::OnDemand(OnDemandState {
|
||||
fetcher: self.blockchain.fetcher(),
|
||||
blockchain: Arc::downgrade(&self.blockchain),
|
||||
block: block_hash,
|
||||
cached_header: RwLock::new(None),
|
||||
}))
|
||||
// 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>) -> ClientResult<NumberFor<Block>> {
|
||||
Err(ClientError::NotAvailableOnLightClient.into())
|
||||
Err(ClientError::NotAvailableOnLightClient)
|
||||
}
|
||||
|
||||
fn get_import_lock(&self) -> &Mutex<()> {
|
||||
@@ -228,11 +214,10 @@ impl<S, F, Block, H> ClientBackend<Block, H> for Backend<S, F, H> where
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, F, Block, H> RemoteBackend<Block, H> for Backend<S, F, H>
|
||||
impl<S, Block, H> RemoteBackend<Block, H> for Backend<S, H>
|
||||
where
|
||||
Block: BlockT,
|
||||
S: BlockchainStorage<Block> + 'static,
|
||||
F: Fetcher<Block> + 'static,
|
||||
H: Hasher<Out=Block::Hash>,
|
||||
H::Out: Ord,
|
||||
{
|
||||
@@ -248,15 +233,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, F, Block, H> BlockImportOperation<Block, H> for ImportOperation<Block, S, F, H>
|
||||
impl<S, Block, H> BlockImportOperation<Block, H> for ImportOperation<Block, S, H>
|
||||
where
|
||||
Block: BlockT,
|
||||
F: Fetcher<Block>,
|
||||
S: BlockchainStorage<Block>,
|
||||
H: Hasher<Out=Block::Hash>,
|
||||
H::Out: Ord,
|
||||
{
|
||||
type State = OnDemandOrGenesisState<Block, S, F, H>;
|
||||
type State = GenesisOrUnavailableState<H>;
|
||||
|
||||
fn state(&self) -> ClientResult<Option<&Self::State>> {
|
||||
// None means 'locally-stateless' backend
|
||||
@@ -341,99 +325,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block, S, F, H> StateBackend<H> for OnDemandState<Block, S, F>
|
||||
where
|
||||
Block: BlockT,
|
||||
S: BlockchainStorage<Block>,
|
||||
F: Fetcher<Block>,
|
||||
H: Hasher<Out=Block::Hash>,
|
||||
{
|
||||
type Error = ClientError;
|
||||
type Transaction = ();
|
||||
type TrieBackendStorage = MemoryDB<H>;
|
||||
|
||||
fn storage(&self, key: &[u8]) -> ClientResult<Option<Vec<u8>>> {
|
||||
let mut header = self.cached_header.read().clone();
|
||||
if header.is_none() {
|
||||
let cached_header = self.blockchain.upgrade()
|
||||
.ok_or_else(|| ClientError::UnknownBlock(format!("{}", self.block)))
|
||||
.and_then(|blockchain| blockchain.expect_header(BlockId::Hash(self.block)))?;
|
||||
header = Some(cached_header.clone());
|
||||
*self.cached_header.write() = Some(cached_header);
|
||||
}
|
||||
|
||||
futures03::executor::block_on(
|
||||
self.fetcher.upgrade().ok_or(ClientError::NotAvailableOnLightClient)?
|
||||
.remote_read(RemoteReadRequest {
|
||||
block: self.block,
|
||||
header: header.expect("if block above guarantees that header is_some(); qed"),
|
||||
key: key.to_vec(),
|
||||
retry_count: None,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
fn child_storage(&self, _storage_key: &[u8], _key: &[u8]) -> ClientResult<Option<Vec<u8>>> {
|
||||
Err(ClientError::NotAvailableOnLightClient.into())
|
||||
}
|
||||
|
||||
fn for_keys_with_prefix<A: FnMut(&[u8])>(&self, _prefix: &[u8], _action: A) {
|
||||
// whole state is not available on light node
|
||||
}
|
||||
|
||||
fn for_key_values_with_prefix<A: FnMut(&[u8], &[u8])>(&self, _prefix: &[u8], _action: A) {
|
||||
// whole state is not available on light node
|
||||
}
|
||||
|
||||
fn for_keys_in_child_storage<A: FnMut(&[u8])>(&self, _storage_key: &[u8], _action: A) {
|
||||
// whole state is not available on light node
|
||||
}
|
||||
|
||||
fn for_child_keys_with_prefix<A: FnMut(&[u8])>(
|
||||
&self,
|
||||
_storage_key: &[u8],
|
||||
_prefix: &[u8],
|
||||
_action: A,
|
||||
) {
|
||||
// whole state is not available on light node
|
||||
}
|
||||
|
||||
fn storage_root<I>(&self, _delta: I) -> (H::Out, Self::Transaction)
|
||||
impl<H: Hasher> StateBackend<H> for GenesisOrUnavailableState<H>
|
||||
where
|
||||
I: IntoIterator<Item=(Vec<u8>, Option<Vec<u8>>)>
|
||||
{
|
||||
(H::Out::default(), ())
|
||||
}
|
||||
|
||||
fn child_storage_root<I>(&self, _key: &[u8], _delta: I) -> (Vec<u8>, bool, Self::Transaction)
|
||||
where
|
||||
I: IntoIterator<Item=(Vec<u8>, Option<Vec<u8>>)>
|
||||
{
|
||||
(H::Out::default().as_ref().to_vec(), true, ())
|
||||
}
|
||||
|
||||
fn pairs(&self) -> Vec<(Vec<u8>, Vec<u8>)> {
|
||||
// whole state is not available on light node
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn keys(&self, _prefix: &[u8]) -> Vec<Vec<u8>> {
|
||||
// whole state is not available on light node
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn as_trie_backend(&mut self) -> Option<&TrieBackend<Self::TrieBackendStorage, H>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block, S, F, H> StateBackend<H> for OnDemandOrGenesisState<Block, S, F, H>
|
||||
where
|
||||
Block: BlockT,
|
||||
F: Fetcher<Block>,
|
||||
S: BlockchainStorage<Block>,
|
||||
H: Hasher<Out=Block::Hash>,
|
||||
H::Out: Ord,
|
||||
H::Out: Ord,
|
||||
{
|
||||
type Error = ClientError;
|
||||
type Transaction = ();
|
||||
@@ -441,44 +335,39 @@ where
|
||||
|
||||
fn storage(&self, key: &[u8]) -> ClientResult<Option<Vec<u8>>> {
|
||||
match *self {
|
||||
OnDemandOrGenesisState::OnDemand(ref state) =>
|
||||
StateBackend::<H>::storage(state, key),
|
||||
OnDemandOrGenesisState::Genesis(ref state) =>
|
||||
GenesisOrUnavailableState::Genesis(ref state) =>
|
||||
Ok(state.storage(key).expect(IN_MEMORY_EXPECT_PROOF)),
|
||||
GenesisOrUnavailableState::Unavailable => Err(ClientError::NotAvailableOnLightClient),
|
||||
}
|
||||
}
|
||||
|
||||
fn child_storage(&self, storage_key: &[u8], key: &[u8]) -> ClientResult<Option<Vec<u8>>> {
|
||||
match *self {
|
||||
OnDemandOrGenesisState::OnDemand(ref state) =>
|
||||
StateBackend::<H>::child_storage(state, storage_key, key),
|
||||
OnDemandOrGenesisState::Genesis(ref state) =>
|
||||
GenesisOrUnavailableState::Genesis(ref state) =>
|
||||
Ok(state.child_storage(storage_key, 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 {
|
||||
OnDemandOrGenesisState::OnDemand(ref state) =>
|
||||
StateBackend::<H>::for_keys_with_prefix(state, prefix, action),
|
||||
OnDemandOrGenesisState::Genesis(ref state) => state.for_keys_with_prefix(prefix, action),
|
||||
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 {
|
||||
OnDemandOrGenesisState::OnDemand(ref state) =>
|
||||
StateBackend::<H>::for_key_values_with_prefix(state, prefix, action),
|
||||
OnDemandOrGenesisState::Genesis(ref state) => state.for_key_values_with_prefix(prefix, action),
|
||||
GenesisOrUnavailableState::Genesis(ref state) => state.for_key_values_with_prefix(prefix, action),
|
||||
GenesisOrUnavailableState::Unavailable => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn for_keys_in_child_storage<A: FnMut(&[u8])>(&self, storage_key: &[u8], action: A) {
|
||||
match *self {
|
||||
OnDemandOrGenesisState::OnDemand(ref state) =>
|
||||
StateBackend::<H>::for_keys_in_child_storage(state, storage_key, action),
|
||||
OnDemandOrGenesisState::Genesis(ref state) => state.for_keys_in_child_storage(storage_key, action),
|
||||
GenesisOrUnavailableState::Genesis(ref state) => state.for_keys_in_child_storage(storage_key, action),
|
||||
GenesisOrUnavailableState::Unavailable => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,10 +378,9 @@ where
|
||||
action: A,
|
||||
) {
|
||||
match *self {
|
||||
OnDemandOrGenesisState::OnDemand(ref state) =>
|
||||
StateBackend::<H>::for_child_keys_with_prefix(state, storage_key, prefix, action),
|
||||
OnDemandOrGenesisState::Genesis(ref state) =>
|
||||
GenesisOrUnavailableState::Genesis(ref state) =>
|
||||
state.for_child_keys_with_prefix(storage_key, prefix, action),
|
||||
GenesisOrUnavailableState::Unavailable => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,12 +389,9 @@ where
|
||||
I: IntoIterator<Item=(Vec<u8>, Option<Vec<u8>>)>
|
||||
{
|
||||
match *self {
|
||||
OnDemandOrGenesisState::OnDemand(ref state) =>
|
||||
StateBackend::<H>::storage_root(state, delta),
|
||||
OnDemandOrGenesisState::Genesis(ref state) => {
|
||||
let (root, _) = state.storage_root(delta);
|
||||
(root, ())
|
||||
},
|
||||
GenesisOrUnavailableState::Genesis(ref state) =>
|
||||
(state.storage_root(delta).0, ()),
|
||||
GenesisOrUnavailableState::Unavailable => (H::Out::default(), ()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -515,35 +400,32 @@ where
|
||||
I: IntoIterator<Item=(Vec<u8>, Option<Vec<u8>>)>
|
||||
{
|
||||
match *self {
|
||||
OnDemandOrGenesisState::OnDemand(ref state) =>
|
||||
StateBackend::<H>::child_storage_root(state, key, delta),
|
||||
OnDemandOrGenesisState::Genesis(ref state) => {
|
||||
GenesisOrUnavailableState::Genesis(ref state) => {
|
||||
let (root, is_equal, _) = state.child_storage_root(key, delta);
|
||||
(root, is_equal, ())
|
||||
},
|
||||
GenesisOrUnavailableState::Unavailable => (H::Out::default().as_ref().to_vec(), true, ()),
|
||||
}
|
||||
}
|
||||
|
||||
fn pairs(&self) -> Vec<(Vec<u8>, Vec<u8>)> {
|
||||
match *self {
|
||||
OnDemandOrGenesisState::OnDemand(ref state) =>
|
||||
StateBackend::<H>::pairs(state),
|
||||
OnDemandOrGenesisState::Genesis(ref state) => state.pairs(),
|
||||
GenesisOrUnavailableState::Genesis(ref state) => state.pairs(),
|
||||
GenesisOrUnavailableState::Unavailable => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn keys(&self, prefix: &[u8]) -> Vec<Vec<u8>> {
|
||||
match *self {
|
||||
OnDemandOrGenesisState::OnDemand(ref state) =>
|
||||
StateBackend::<H>::keys(state, prefix),
|
||||
OnDemandOrGenesisState::Genesis(ref state) => state.keys(prefix),
|
||||
GenesisOrUnavailableState::Genesis(ref state) => state.keys(prefix),
|
||||
GenesisOrUnavailableState::Unavailable => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_trie_backend(&mut self) -> Option<&TrieBackend<Self::TrieBackendStorage, H>> {
|
||||
match self {
|
||||
OnDemandOrGenesisState::OnDemand(ref mut state) => state.as_trie_backend(),
|
||||
OnDemandOrGenesisState::Genesis(ref mut state) => state.as_trie_backend(),
|
||||
GenesisOrUnavailableState::Genesis(ref mut state) => state.as_trie_backend(),
|
||||
GenesisOrUnavailableState::Unavailable => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -561,24 +443,24 @@ mod tests {
|
||||
let def = Default::default();
|
||||
let header0 = test_client::runtime::Header::new(0, def, def, def, Default::default());
|
||||
|
||||
let backend: Backend<_, _, Blake2Hasher> = Backend::new(Arc::new(DummyBlockchain::new(DummyStorage::new())));
|
||||
let backend: Backend<_, Blake2Hasher> = Backend::new(Arc::new(DummyBlockchain::new(DummyStorage::new())));
|
||||
let mut op = backend.begin_operation().unwrap();
|
||||
op.set_block_data(header0, None, None, NewBlockState::Final).unwrap();
|
||||
op.reset_storage(Default::default(), Default::default()).unwrap();
|
||||
backend.commit_operation(op).unwrap();
|
||||
|
||||
match backend.state_at(BlockId::Number(0)).unwrap() {
|
||||
OnDemandOrGenesisState::Genesis(_) => (),
|
||||
GenesisOrUnavailableState::Genesis(_) => (),
|
||||
_ => panic!("unexpected state"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_state_is_created_when_genesis_state_is_inavailable() {
|
||||
let backend: Backend<_, _, Blake2Hasher> = Backend::new(Arc::new(DummyBlockchain::new(DummyStorage::new())));
|
||||
fn unavailable_state_is_created_when_genesis_state_is_unavailable() {
|
||||
let backend: Backend<_, Blake2Hasher> = Backend::new(Arc::new(DummyBlockchain::new(DummyStorage::new())));
|
||||
|
||||
match backend.state_at(BlockId::Number(0)).unwrap() {
|
||||
OnDemandOrGenesisState::OnDemand(_) => (),
|
||||
GenesisOrUnavailableState::Unavailable => (),
|
||||
_ => panic!("unexpected state"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
//! blocks. CHT roots are stored for headers of ancient blocks.
|
||||
|
||||
use std::future::Future;
|
||||
use std::{sync::{Weak, Arc}, collections::HashMap};
|
||||
use parking_lot::Mutex;
|
||||
use std::{sync::Arc, collections::HashMap};
|
||||
|
||||
use sr_primitives::{Justification, generic::BlockId};
|
||||
use sr_primitives::traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero};
|
||||
@@ -30,7 +29,7 @@ use crate::blockchain::{Backend as BlockchainBackend, BlockStatus, Cache as Bloc
|
||||
HeaderBackend as BlockchainHeaderBackend, Info as BlockchainInfo, ProvideCache};
|
||||
use crate::cht;
|
||||
use crate::error::{Error as ClientError, Result as ClientResult};
|
||||
use crate::light::fetcher::{Fetcher, RemoteBodyRequest, RemoteHeaderRequest};
|
||||
use crate::light::fetcher::{Fetcher, RemoteHeaderRequest};
|
||||
|
||||
/// Light client blockchain storage.
|
||||
pub trait Storage<Block: BlockT>: AuxStore + BlockchainHeaderBackend<Block> {
|
||||
@@ -95,37 +94,25 @@ pub trait RemoteBlockchain<Block: BlockT>: Send + Sync {
|
||||
}
|
||||
|
||||
/// Light client blockchain.
|
||||
pub struct Blockchain<S, F> {
|
||||
fetcher: Mutex<Weak<F>>,
|
||||
pub struct Blockchain<S> {
|
||||
storage: S,
|
||||
}
|
||||
|
||||
impl<S, F> Blockchain<S, F> {
|
||||
impl<S> Blockchain<S> {
|
||||
/// Create new light blockchain backed with given storage.
|
||||
pub fn new(storage: S) -> Self {
|
||||
Self {
|
||||
fetcher: Mutex::new(Default::default()),
|
||||
storage,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets fetcher reference.
|
||||
pub fn set_fetcher(&self, fetcher: Weak<F>) {
|
||||
*self.fetcher.lock() = fetcher;
|
||||
}
|
||||
|
||||
/// Get fetcher weak reference.
|
||||
pub fn fetcher(&self) -> Weak<F> {
|
||||
self.fetcher.lock().clone()
|
||||
}
|
||||
|
||||
/// Get storage reference.
|
||||
pub fn storage(&self) -> &S {
|
||||
&self.storage
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, F, Block> BlockchainHeaderBackend<Block> for Blockchain<S, F> where Block: BlockT, S: Storage<Block>, F: Fetcher<Block> {
|
||||
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)),
|
||||
@@ -151,24 +138,13 @@ impl<S, F, Block> BlockchainHeaderBackend<Block> for Blockchain<S, F> where Bloc
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, F, Block> BlockchainBackend<Block> for Blockchain<S, F> where Block: BlockT, S: Storage<Block>, F: Fetcher<Block> {
|
||||
fn body(&self, id: BlockId<Block>) -> ClientResult<Option<Vec<Block::Extrinsic>>> {
|
||||
let header = match BlockchainHeaderBackend::header(self, id)? {
|
||||
Some(header) => header,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
futures03::executor::block_on(
|
||||
self.fetcher().upgrade().ok_or(ClientError::NotAvailableOnLightClient)?
|
||||
.remote_body(RemoteBodyRequest {
|
||||
header,
|
||||
retry_count: None,
|
||||
})
|
||||
).map(Some)
|
||||
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>> {
|
||||
Ok(None)
|
||||
Err(ClientError::NotAvailableOnLightClient)
|
||||
}
|
||||
|
||||
fn last_finalized(&self) -> ClientResult<Block::Hash> {
|
||||
@@ -180,24 +156,23 @@ impl<S, F, Block> BlockchainBackend<Block> for Blockchain<S, F> where Block: Blo
|
||||
}
|
||||
|
||||
fn leaves(&self) -> ClientResult<Vec<Block::Hash>> {
|
||||
unimplemented!()
|
||||
Err(ClientError::NotAvailableOnLightClient)
|
||||
}
|
||||
|
||||
fn children(&self, _parent_hash: Block::Hash) -> ClientResult<Vec<Block::Hash>> {
|
||||
unimplemented!()
|
||||
Err(ClientError::NotAvailableOnLightClient)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Storage<Block>, F, Block: BlockT> ProvideCache<Block> for Blockchain<S, F> {
|
||||
impl<S: Storage<Block>, Block: BlockT> ProvideCache<Block> for Blockchain<S> {
|
||||
fn cache(&self) -> Option<Arc<dyn BlockchainCache<Block>>> {
|
||||
self.storage.cache()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, F, Block: BlockT> RemoteBlockchain<Block> for Blockchain<S, F>
|
||||
impl<S, Block: BlockT> RemoteBlockchain<Block> for Blockchain<S>
|
||||
where
|
||||
S: Storage<Block>,
|
||||
F: Fetcher<Block> + Send + Sync,
|
||||
{
|
||||
fn header(&self, id: BlockId<Block>) -> ClientResult<LocalOrRemote<
|
||||
Block::Header,
|
||||
@@ -253,12 +228,12 @@ pub fn future_header<Block: BlockT, F: Fetcher<Block>>(
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use std::collections::HashMap;
|
||||
use parking_lot::Mutex;
|
||||
use test_client::runtime::{Hash, Block, Header};
|
||||
use crate::blockchain::Info;
|
||||
use crate::light::fetcher::tests::OkCallFetcher;
|
||||
use super::*;
|
||||
|
||||
pub type DummyBlockchain = Blockchain<DummyStorage, OkCallFetcher>;
|
||||
pub type DummyBlockchain = Blockchain<DummyStorage>;
|
||||
|
||||
pub struct DummyStorage {
|
||||
pub changes_tries_cht_roots: HashMap<u64, Hash>,
|
||||
|
||||
@@ -14,17 +14,16 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Light client call executor. Executes methods on remote full nodes, fetching
|
||||
//! execution proof and checking it locally.
|
||||
//! Methods that light client could use to execute runtime calls.
|
||||
|
||||
use std::{
|
||||
collections::HashSet, sync::Arc, panic::UnwindSafe, result,
|
||||
marker::PhantomData, cell::RefCell, rc::Rc,
|
||||
cell::RefCell, rc::Rc,
|
||||
};
|
||||
|
||||
use codec::{Encode, Decode};
|
||||
use primitives::{
|
||||
offchain::{self, NeverOffchainExt}, H256, Blake2Hasher, convert_hash, NativeOrEncoded,
|
||||
offchain, H256, Blake2Hasher, convert_hash, NativeOrEncoded,
|
||||
traits::CodeExecutor,
|
||||
};
|
||||
use sr_primitives::generic::BlockId;
|
||||
@@ -37,211 +36,40 @@ use hash_db::Hasher;
|
||||
|
||||
use crate::runtime_api::{ProofRecorder, InitializeBlock};
|
||||
use crate::backend::RemoteBackend;
|
||||
use crate::blockchain::Backend as ChainBackend;
|
||||
use crate::call_executor::CallExecutor;
|
||||
use crate::error::{Error as ClientError, Result as ClientResult};
|
||||
use crate::light::fetcher::{Fetcher, RemoteCallRequest};
|
||||
use crate::light::fetcher::RemoteCallRequest;
|
||||
use executor::{RuntimeVersion, NativeVersion};
|
||||
|
||||
/// Call executor that executes methods on remote node, querying execution proof
|
||||
/// and checking proof by re-executing locally.
|
||||
pub struct RemoteCallExecutor<B, F> {
|
||||
blockchain: Arc<B>,
|
||||
fetcher: Arc<F>,
|
||||
}
|
||||
|
||||
/// Remote or local call executor.
|
||||
/// Call executor that is able to execute calls only on genesis state.
|
||||
///
|
||||
/// Calls are executed locally if state is available locally. Otherwise, calls
|
||||
/// are redirected to remote call executor.
|
||||
pub struct RemoteOrLocalCallExecutor<Block: BlockT<Hash=H256>, B, R, L> {
|
||||
/// Trying to execute call on non-genesis state leads to error.
|
||||
pub struct GenesisCallExecutor<B, L> {
|
||||
backend: Arc<B>,
|
||||
remote: R,
|
||||
local: L,
|
||||
_block: PhantomData<Block>,
|
||||
}
|
||||
|
||||
impl<B, F> Clone for RemoteCallExecutor<B, F> {
|
||||
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 {
|
||||
RemoteCallExecutor {
|
||||
blockchain: self.blockchain.clone(),
|
||||
fetcher: self.fetcher.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, F> RemoteCallExecutor<B, F> {
|
||||
/// Creates new instance of remote call executor.
|
||||
pub fn new(blockchain: Arc<B>, fetcher: Arc<F>) -> Self {
|
||||
RemoteCallExecutor { blockchain, fetcher }
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, F, Block> CallExecutor<Block, Blake2Hasher> for RemoteCallExecutor<B, F>
|
||||
where
|
||||
Block: BlockT<Hash=H256>,
|
||||
B: ChainBackend<Block>,
|
||||
F: Fetcher<Block>,
|
||||
Block::Hash: Ord,
|
||||
{
|
||||
type Error = ClientError;
|
||||
|
||||
fn call<
|
||||
O: offchain::Externalities,
|
||||
>(
|
||||
&self,
|
||||
id: &BlockId<Block>,
|
||||
method: &str,
|
||||
call_data: &[u8],
|
||||
_strategy: ExecutionStrategy,
|
||||
_side_effects_handler: Option<&mut O>,
|
||||
) -> ClientResult<Vec<u8>>
|
||||
{
|
||||
let block_hash = self.blockchain.expect_block_hash_from_id(id)?;
|
||||
let block_header = self.blockchain.expect_header(id.clone())?;
|
||||
|
||||
futures03::executor::block_on(self.fetcher.remote_call(RemoteCallRequest {
|
||||
block: block_hash,
|
||||
header: block_header,
|
||||
method: method.into(),
|
||||
call_data: call_data.to_vec(),
|
||||
retry_count: None,
|
||||
}))
|
||||
}
|
||||
|
||||
fn contextual_call<
|
||||
'a,
|
||||
O: offchain::Externalities,
|
||||
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,
|
||||
>(
|
||||
&self,
|
||||
_initialize_block_fn: IB,
|
||||
at: &BlockId<Block>,
|
||||
method: &str,
|
||||
call_data: &[u8],
|
||||
changes: &RefCell<OverlayedChanges>,
|
||||
initialize_block: InitializeBlock<'a, Block>,
|
||||
execution_manager: ExecutionManager<EM>,
|
||||
_native_call: Option<NC>,
|
||||
side_effects_handler: Option<&mut O>,
|
||||
_recorder: &Option<Rc<RefCell<ProofRecorder<Block>>>>,
|
||||
_enable_keystore: bool,
|
||||
) -> ClientResult<NativeOrEncoded<R>> where ExecutionManager<EM>: Clone {
|
||||
let block_initialized = match initialize_block {
|
||||
InitializeBlock::Do(ref init_block) => {
|
||||
init_block.borrow().is_some()
|
||||
},
|
||||
InitializeBlock::Skip => false,
|
||||
};
|
||||
|
||||
// it is only possible to execute contextual call if changes are empty
|
||||
if !changes.borrow().is_empty() || block_initialized {
|
||||
return Err(ClientError::NotAvailableOnLightClient.into());
|
||||
}
|
||||
|
||||
self.call(
|
||||
at,
|
||||
method,
|
||||
call_data,
|
||||
(&execution_manager).into(),
|
||||
side_effects_handler,
|
||||
).map(NativeOrEncoded::Encoded)
|
||||
}
|
||||
|
||||
fn runtime_version(&self, id: &BlockId<Block>) -> ClientResult<RuntimeVersion> {
|
||||
let call_result = self.call(
|
||||
id,
|
||||
"Core_version",
|
||||
&[],
|
||||
ExecutionStrategy::NativeElseWasm,
|
||||
NeverOffchainExt::new()
|
||||
)?;
|
||||
RuntimeVersion::decode(&mut call_result.as_slice())
|
||||
.map_err(|_| ClientError::VersionInvalid.into())
|
||||
}
|
||||
|
||||
fn call_at_state<
|
||||
O: offchain::Externalities,
|
||||
S: StateBackend<Blake2Hasher>,
|
||||
FF: FnOnce(
|
||||
Result<NativeOrEncoded<R>, Self::Error>,
|
||||
Result<NativeOrEncoded<R>, Self::Error>
|
||||
) -> Result<NativeOrEncoded<R>, Self::Error>,
|
||||
R: Encode + Decode + PartialEq,
|
||||
NC: FnOnce() -> result::Result<R, &'static str>,
|
||||
>(&self,
|
||||
_state: &S,
|
||||
_changes: &mut OverlayedChanges,
|
||||
_method: &str,
|
||||
_call_data: &[u8],
|
||||
_m: ExecutionManager<FF>,
|
||||
_native_call: Option<NC>,
|
||||
_side_effects_handler: Option<&mut O>,
|
||||
) -> ClientResult<(
|
||||
NativeOrEncoded<R>,
|
||||
(S::Transaction, <Blake2Hasher as Hasher>::Out),
|
||||
Option<ChangesTrieTransaction<Blake2Hasher, NumberFor<Block>>>,
|
||||
)> {
|
||||
Err(ClientError::NotAvailableOnLightClient.into())
|
||||
}
|
||||
|
||||
fn prove_at_trie_state<S: state_machine::TrieBackendStorage<Blake2Hasher>>(
|
||||
&self,
|
||||
_state: &state_machine::TrieBackend<S, Blake2Hasher>,
|
||||
_changes: &mut OverlayedChanges,
|
||||
_method: &str,
|
||||
_call_data: &[u8]
|
||||
) -> ClientResult<(Vec<u8>, Vec<Vec<u8>>)> {
|
||||
Err(ClientError::NotAvailableOnLightClient.into())
|
||||
}
|
||||
|
||||
fn native_runtime_version(&self) -> Option<&NativeVersion> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block, B, R, L> Clone for RemoteOrLocalCallExecutor<Block, B, R, L>
|
||||
where
|
||||
Block: BlockT<Hash=H256>,
|
||||
B: RemoteBackend<Block, Blake2Hasher>,
|
||||
R: CallExecutor<Block, Blake2Hasher> + Clone,
|
||||
L: CallExecutor<Block, Blake2Hasher> + Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
RemoteOrLocalCallExecutor {
|
||||
GenesisCallExecutor {
|
||||
backend: self.backend.clone(),
|
||||
remote: self.remote.clone(),
|
||||
local: self.local.clone(),
|
||||
_block: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block, B, Remote, Local> RemoteOrLocalCallExecutor<Block, B, Remote, Local>
|
||||
impl<Block, B, Local> CallExecutor<Block, Blake2Hasher> for
|
||||
GenesisCallExecutor<B, Local>
|
||||
where
|
||||
Block: BlockT<Hash=H256>,
|
||||
B: RemoteBackend<Block, Blake2Hasher>,
|
||||
Remote: CallExecutor<Block, Blake2Hasher>,
|
||||
Local: CallExecutor<Block, Blake2Hasher>,
|
||||
{
|
||||
/// Creates new instance of remote/local call executor.
|
||||
pub fn new(backend: Arc<B>, remote: Remote, local: Local) -> Self {
|
||||
RemoteOrLocalCallExecutor { backend, remote, local, _block: Default::default(), }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block, B, Remote, Local> CallExecutor<Block, Blake2Hasher> for
|
||||
RemoteOrLocalCallExecutor<Block, B, Remote, Local>
|
||||
where
|
||||
Block: BlockT<Hash=H256>,
|
||||
B: RemoteBackend<Block, Blake2Hasher>,
|
||||
Remote: CallExecutor<Block, Blake2Hasher>,
|
||||
Local: CallExecutor<Block, Blake2Hasher>,
|
||||
{
|
||||
type Error = ClientError;
|
||||
@@ -258,7 +86,7 @@ impl<Block, B, Remote, Local> CallExecutor<Block, Blake2Hasher> for
|
||||
) -> ClientResult<Vec<u8>> {
|
||||
match self.backend.is_local_state_available(id) {
|
||||
true => self.local.call(id, method, call_data, strategy, side_effects_handler),
|
||||
false => self.remote.call(id, method, call_data, strategy, side_effects_handler),
|
||||
false => Err(ClientError::NotAvailableOnLightClient),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,36 +141,14 @@ impl<Block, B, Remote, Local> CallExecutor<Block, Blake2Hasher> for
|
||||
recorder,
|
||||
enable_keystore,
|
||||
).map_err(|e| ClientError::Execution(Box::new(e.to_string()))),
|
||||
false => CallExecutor::contextual_call::<
|
||||
_,
|
||||
_,
|
||||
fn(
|
||||
Result<NativeOrEncoded<R>, Remote::Error>,
|
||||
Result<NativeOrEncoded<R>, Remote::Error>,
|
||||
) -> Result<NativeOrEncoded<R>, Remote::Error>,
|
||||
_,
|
||||
NC
|
||||
>(
|
||||
&self.remote,
|
||||
initialize_block_fn,
|
||||
at,
|
||||
method,
|
||||
call_data,
|
||||
changes,
|
||||
initialize_block,
|
||||
ExecutionManager::NativeWhenPossible,
|
||||
native_call,
|
||||
side_effects_handler,
|
||||
recorder,
|
||||
enable_keystore,
|
||||
).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 => self.remote.runtime_version(id),
|
||||
false => Err(ClientError::NotAvailableOnLightClient),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,50 +162,29 @@ impl<Block, B, Remote, Local> CallExecutor<Block, Blake2Hasher> for
|
||||
R: Encode + Decode + PartialEq,
|
||||
NC: FnOnce() -> result::Result<R, &'static str> + UnwindSafe,
|
||||
>(&self,
|
||||
state: &S,
|
||||
changes: &mut OverlayedChanges,
|
||||
method: &str,
|
||||
call_data: &[u8],
|
||||
_state: &S,
|
||||
_changes: &mut OverlayedChanges,
|
||||
_method: &str,
|
||||
_call_data: &[u8],
|
||||
_manager: ExecutionManager<FF>,
|
||||
native_call: Option<NC>,
|
||||
side_effects_handler: Option<&mut O>,
|
||||
_native_call: Option<NC>,
|
||||
_side_effects_handler: Option<&mut O>,
|
||||
) -> ClientResult<(
|
||||
NativeOrEncoded<R>,
|
||||
(S::Transaction, <Blake2Hasher as Hasher>::Out),
|
||||
Option<ChangesTrieTransaction<Blake2Hasher, NumberFor<Block>>>,
|
||||
)> {
|
||||
// there's no actual way/need to specify native/wasm execution strategy on light node
|
||||
// => we can safely ignore passed values
|
||||
|
||||
CallExecutor::call_at_state::<
|
||||
_,
|
||||
_,
|
||||
fn(
|
||||
Result<NativeOrEncoded<R>, Remote::Error>,
|
||||
Result<NativeOrEncoded<R>, Remote::Error>,
|
||||
) -> Result<NativeOrEncoded<R>, Remote::Error>,
|
||||
_,
|
||||
NC
|
||||
>(
|
||||
&self.remote,
|
||||
state,
|
||||
changes,
|
||||
method,
|
||||
call_data,
|
||||
ExecutionManager::NativeWhenPossible,
|
||||
native_call,
|
||||
side_effects_handler,
|
||||
).map_err(|e| ClientError::Execution(Box::new(e.to_string())))
|
||||
Err(ClientError::NotAvailableOnLightClient)
|
||||
}
|
||||
|
||||
fn prove_at_trie_state<S: state_machine::TrieBackendStorage<Blake2Hasher>>(
|
||||
&self,
|
||||
state: &state_machine::TrieBackend<S, Blake2Hasher>,
|
||||
changes: &mut OverlayedChanges,
|
||||
method: &str,
|
||||
call_data: &[u8]
|
||||
_state: &state_machine::TrieBackend<S, Blake2Hasher>,
|
||||
_changes: &mut OverlayedChanges,
|
||||
_method: &str,
|
||||
_call_data: &[u8]
|
||||
) -> ClientResult<(Vec<u8>, Vec<Vec<u8>>)> {
|
||||
self.remote.prove_at_trie_state(state, changes, method, call_data)
|
||||
Err(ClientError::NotAvailableOnLightClient)
|
||||
}
|
||||
|
||||
fn native_runtime_version(&self) -> Option<&NativeVersion> {
|
||||
@@ -517,13 +302,103 @@ fn check_execution_proof_with_make_header<Header, E, H, MakeNextHeader: Fn(&Head
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use consensus::BlockOrigin;
|
||||
use test_client::{self, runtime::{Header, Digest}, ClientExt, TestClient};
|
||||
use primitives::offchain::NeverOffchainExt;
|
||||
use test_client::{self, runtime::{Header, Digest, Block}, ClientExt, TestClient};
|
||||
use executor::NativeExecutor;
|
||||
use crate::backend::{Backend, NewBlockState};
|
||||
use crate::in_mem::Backend as InMemBackend;
|
||||
use crate::light::fetcher::tests::OkCallFetcher;
|
||||
use super::*;
|
||||
|
||||
struct DummyCallExecutor;
|
||||
|
||||
impl CallExecutor<Block, Blake2Hasher> for DummyCallExecutor {
|
||||
type Error = ClientError;
|
||||
|
||||
fn call<O: offchain::Externalities>(
|
||||
&self,
|
||||
_id: &BlockId<Block>,
|
||||
_method: &str,
|
||||
_call_data: &[u8],
|
||||
_strategy: ExecutionStrategy,
|
||||
_side_effects_handler: Option<&mut O>,
|
||||
) -> Result<Vec<u8>, ClientError> {
|
||||
Ok(vec![42])
|
||||
}
|
||||
|
||||
fn contextual_call<
|
||||
'a,
|
||||
O: offchain::Externalities,
|
||||
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, &'static str> + UnwindSafe,
|
||||
>(
|
||||
&self,
|
||||
_initialize_block_fn: IB,
|
||||
_at: &BlockId<Block>,
|
||||
_method: &str,
|
||||
_call_data: &[u8],
|
||||
_changes: &RefCell<OverlayedChanges>,
|
||||
_initialize_block: InitializeBlock<'a, Block>,
|
||||
_execution_manager: ExecutionManager<EM>,
|
||||
_native_call: Option<NC>,
|
||||
_side_effects_handler: Option<&mut O>,
|
||||
_proof_recorder: &Option<Rc<RefCell<ProofRecorder<Block>>>>,
|
||||
_enable_keystore: bool,
|
||||
) -> ClientResult<NativeOrEncoded<R>> where ExecutionManager<EM>: Clone {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn runtime_version(&self, _id: &BlockId<Block>) -> Result<RuntimeVersion, ClientError> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn call_at_state<
|
||||
O: offchain::Externalities,
|
||||
S: state_machine::Backend<Blake2Hasher>,
|
||||
F: FnOnce(
|
||||
Result<NativeOrEncoded<R>, Self::Error>,
|
||||
Result<NativeOrEncoded<R>, Self::Error>
|
||||
) -> Result<NativeOrEncoded<R>, Self::Error>,
|
||||
R: Encode + Decode + PartialEq,
|
||||
NC: FnOnce() -> result::Result<R, &'static str> + UnwindSafe,
|
||||
>(&self,
|
||||
_state: &S,
|
||||
_overlay: &mut OverlayedChanges,
|
||||
_method: &str,
|
||||
_call_data: &[u8],
|
||||
_manager: ExecutionManager<F>,
|
||||
_native_call: Option<NC>,
|
||||
_side_effects_handler: Option<&mut O>,
|
||||
) -> Result<
|
||||
(
|
||||
NativeOrEncoded<R>,
|
||||
(S::Transaction, H256),
|
||||
Option<ChangesTrieTransaction<Blake2Hasher, NumberFor<Block>>>,
|
||||
),
|
||||
ClientError,
|
||||
> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn prove_at_trie_state<S: state_machine::TrieBackendStorage<Blake2Hasher>>(
|
||||
&self,
|
||||
_trie_state: &state_machine::TrieBackend<S, Blake2Hasher>,
|
||||
_overlay: &mut OverlayedChanges,
|
||||
_method: &str,
|
||||
_call_data: &[u8]
|
||||
) -> Result<(Vec<u8>, Vec<Vec<u8>>), ClientError> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn native_runtime_version(&self) -> Option<&NativeVersion> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execution_proof_is_generated_and_checked() {
|
||||
fn execute(remote_client: &TestClient, at: u64, method: &'static str) -> (Vec<u8>, Vec<u8>) {
|
||||
@@ -624,8 +499,8 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn code_is_executed_locally_or_remotely() {
|
||||
let backend = Arc::new(InMemBackend::new());
|
||||
fn code_is_executed_at_genesis_only() {
|
||||
let backend = Arc::new(InMemBackend::<Block, Blake2Hasher>::new());
|
||||
let def = H256::default();
|
||||
let header0 = test_client::runtime::Header::new(0, def, def, def, Default::default());
|
||||
let hash0 = header0.hash();
|
||||
@@ -634,34 +509,29 @@ mod tests {
|
||||
backend.blockchain().insert(hash0, header0, None, None, NewBlockState::Final).unwrap();
|
||||
backend.blockchain().insert(hash1, header1, None, None, NewBlockState::Final).unwrap();
|
||||
|
||||
let local_executor = RemoteCallExecutor::new(
|
||||
Arc::new(backend.blockchain().clone()),
|
||||
Arc::new(OkCallFetcher::new(vec![1])),
|
||||
);
|
||||
let remote_executor = RemoteCallExecutor::new(
|
||||
Arc::new(backend.blockchain().clone()),
|
||||
Arc::new(OkCallFetcher::new(vec![2])),
|
||||
);
|
||||
let remote_or_local = RemoteOrLocalCallExecutor::new(backend, remote_executor, local_executor);
|
||||
let genesis_executor = GenesisCallExecutor::new(backend, DummyCallExecutor);
|
||||
assert_eq!(
|
||||
remote_or_local.call(
|
||||
genesis_executor.call(
|
||||
&BlockId::Number(0),
|
||||
"test_method",
|
||||
&[],
|
||||
ExecutionStrategy::NativeElseWasm,
|
||||
NeverOffchainExt::new(),
|
||||
).unwrap(),
|
||||
vec![1],
|
||||
vec![42],
|
||||
);
|
||||
assert_eq!(
|
||||
remote_or_local.call(
|
||||
&BlockId::Number(1),
|
||||
"test_method",
|
||||
&[],
|
||||
ExecutionStrategy::NativeElseWasm,
|
||||
NeverOffchainExt::new(),
|
||||
).unwrap(),
|
||||
vec![2],
|
||||
|
||||
let call_on_unavailable = genesis_executor.call(
|
||||
&BlockId::Number(1),
|
||||
"test_method",
|
||||
&[],
|
||||
ExecutionStrategy::NativeElseWasm,
|
||||
NeverOffchainExt::new(),
|
||||
);
|
||||
|
||||
match call_on_unavailable {
|
||||
Err(ClientError::NotAvailableOnLightClient) => (),
|
||||
_ => unreachable!("unexpected result: {:?}", call_on_unavailable),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,15 +221,15 @@ pub trait FetchChecker<Block: BlockT>: Send + Sync {
|
||||
}
|
||||
|
||||
/// Remote data checker.
|
||||
pub struct LightDataChecker<E, H, B: BlockT, S: BlockchainStorage<B>, F> {
|
||||
blockchain: Arc<Blockchain<S, F>>,
|
||||
pub struct LightDataChecker<E, H, B: BlockT, S: BlockchainStorage<B>> {
|
||||
blockchain: Arc<Blockchain<S>>,
|
||||
executor: E,
|
||||
_hasher: PhantomData<(B, H)>,
|
||||
}
|
||||
|
||||
impl<E, H, B: BlockT, S: BlockchainStorage<B>, F> LightDataChecker<E, H, B, S, F> {
|
||||
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, F>>, executor: E) -> Self {
|
||||
pub fn new(blockchain: Arc<Blockchain<S>>, executor: E) -> Self {
|
||||
Self {
|
||||
blockchain, executor, _hasher: PhantomData
|
||||
}
|
||||
@@ -367,14 +367,13 @@ impl<E, H, B: BlockT, S: BlockchainStorage<B>, F> LightDataChecker<E, H, B, S, F
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, Block, H, S, F> FetchChecker<Block> for LightDataChecker<E, H, Block, S, F>
|
||||
impl<E, Block, H, S> FetchChecker<Block> for LightDataChecker<E, H, Block, S>
|
||||
where
|
||||
Block: BlockT,
|
||||
E: CodeExecutor<H>,
|
||||
H: Hasher,
|
||||
H::Out: Ord + 'static,
|
||||
S: BlockchainStorage<Block>,
|
||||
F: Send + Sync,
|
||||
{
|
||||
fn check_header_proof(
|
||||
&self,
|
||||
@@ -563,7 +562,6 @@ pub mod tests {
|
||||
Blake2Hasher,
|
||||
Block,
|
||||
DummyStorage,
|
||||
OkCallFetcher,
|
||||
>;
|
||||
|
||||
fn prepare_for_read_proof_check() -> (TestChecker, Header, Vec<Vec<u8>>, u32) {
|
||||
|
||||
@@ -33,55 +33,48 @@ use crate::client::Client;
|
||||
use crate::error::Result as ClientResult;
|
||||
use crate::light::backend::Backend;
|
||||
use crate::light::blockchain::{Blockchain, Storage as BlockchainStorage};
|
||||
use crate::light::call_executor::{RemoteCallExecutor, RemoteOrLocalCallExecutor};
|
||||
use crate::light::fetcher::{Fetcher, LightDataChecker};
|
||||
use crate::light::call_executor::GenesisCallExecutor;
|
||||
use crate::light::fetcher::LightDataChecker;
|
||||
|
||||
/// Create an instance of light client blockchain backend.
|
||||
pub fn new_light_blockchain<B: BlockT, S: BlockchainStorage<B>, F>(storage: S) -> Arc<Blockchain<S, F>> {
|
||||
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, F>(blockchain: Arc<Blockchain<S, F>>, fetcher: Arc<F>) -> Arc<Backend<S, F, Blake2Hasher>>
|
||||
pub fn new_light_backend<B, S>(blockchain: Arc<Blockchain<S>>) -> Arc<Backend<S, Blake2Hasher>>
|
||||
where
|
||||
B: BlockT,
|
||||
S: BlockchainStorage<B>,
|
||||
F: Fetcher<B>,
|
||||
{
|
||||
blockchain.set_fetcher(Arc::downgrade(&fetcher));
|
||||
Arc::new(Backend::new(blockchain))
|
||||
}
|
||||
|
||||
/// Create an instance of light client.
|
||||
pub fn new_light<B, S, F, GS, RA, E>(
|
||||
backend: Arc<Backend<S, F, Blake2Hasher>>,
|
||||
fetcher: Arc<F>,
|
||||
pub fn new_light<B, S, GS, RA, E>(
|
||||
backend: Arc<Backend<S, Blake2Hasher>>,
|
||||
genesis_storage: GS,
|
||||
code_executor: E,
|
||||
) -> ClientResult<Client<Backend<S, F, Blake2Hasher>, RemoteOrLocalCallExecutor<
|
||||
B,
|
||||
Backend<S, F, Blake2Hasher>,
|
||||
RemoteCallExecutor<Blockchain<S, F>, F>,
|
||||
LocalCallExecutor<Backend<S, F, Blake2Hasher>, E>
|
||||
) -> ClientResult<Client<Backend<S, Blake2Hasher>, GenesisCallExecutor<
|
||||
Backend<S, Blake2Hasher>,
|
||||
LocalCallExecutor<Backend<S, Blake2Hasher>, E>
|
||||
>, B, RA>>
|
||||
where
|
||||
B: BlockT<Hash=H256>,
|
||||
S: BlockchainStorage<B> + 'static,
|
||||
F: Fetcher<B> + 'static,
|
||||
GS: BuildStorage,
|
||||
E: CodeExecutor<Blake2Hasher> + RuntimeInfo,
|
||||
{
|
||||
let remote_executor = RemoteCallExecutor::new(backend.blockchain().clone(), fetcher);
|
||||
let local_executor = LocalCallExecutor::new(backend.clone(), code_executor, None);
|
||||
let executor = RemoteOrLocalCallExecutor::new(backend.clone(), remote_executor, local_executor);
|
||||
let executor = GenesisCallExecutor::new(backend.clone(), local_executor);
|
||||
Client::new(backend, executor, genesis_storage, Default::default())
|
||||
}
|
||||
|
||||
/// Create an instance of fetch data checker.
|
||||
pub fn new_fetch_checker<E, B: BlockT, S: BlockchainStorage<B>, F>(
|
||||
blockchain: Arc<Blockchain<S, F>>,
|
||||
pub fn new_fetch_checker<E, B: BlockT, S: BlockchainStorage<B>>(
|
||||
blockchain: Arc<Blockchain<S>>,
|
||||
executor: E,
|
||||
) -> LightDataChecker<E, Blake2Hasher, B, S, F>
|
||||
) -> LightDataChecker<E, Blake2Hasher, B, S>
|
||||
where
|
||||
E: CodeExecutor<Blake2Hasher>,
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user