Make light client backend only work with locally available data (#3538)

* removing fetcher dependency from light backend

* fix compilation
This commit is contained in:
Svyatoslav Nikolsky
2019-09-12 18:13:26 +03:00
committed by Gavin Wood
parent fd924c07ed
commit 634ca73e50
10 changed files with 267 additions and 563 deletions
+57 -175
View File
@@ -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"),
}
}
+15 -40
View File
@@ -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>,
+142 -272
View File
@@ -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),
}
}
}
+5 -7
View File
@@ -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) {
+13 -20
View File
@@ -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>,
{