diff --git a/core/client/src/backend.rs b/core/client/src/backend.rs index 60a0069db490264432481beabdbfd5b73d3b17fd..108b30df03b199a59c5fd411ebbfc2062a410932 100644 --- a/core/client/src/backend.rs +++ b/core/client/src/backend.rs @@ -177,4 +177,7 @@ pub trait RemoteBackend<Block, H>: Backend<Block, H> where Block: BlockT, H: Hasher<Out=Block::Hash>, -{} +{ + /// Returns true if the state for given block is available locally. + fn is_local_state_available(&self, block: &BlockId<Block>) -> bool; +} diff --git a/core/client/src/call_executor.rs b/core/client/src/call_executor.rs index 4c2ff946b56b6b21c424f24d266ba7a66a5c5259..4c274fc43633d9107ee371c2a4d0bb8cfdc26614 100644 --- a/core/client/src/call_executor.rs +++ b/core/client/src/call_executor.rs @@ -154,7 +154,7 @@ impl<B, E> Clone for LocalCallExecutor<B, E> where E: Clone { impl<B, E, Block> CallExecutor<Block, Blake2Hasher> for LocalCallExecutor<B, E> where - B: backend::LocalBackend<Block, Blake2Hasher>, + B: backend::Backend<Block, Blake2Hasher>, E: CodeExecutor<Blake2Hasher> + RuntimeInfo, Block: BlockT<Hash=H256>, { diff --git a/core/client/src/in_mem.rs b/core/client/src/in_mem.rs index 7102426b8f49390b87c1c452057990062391ac06..6f53ad7d2a348d085d5be7b625b974b949c22e80 100644 --- a/core/client/src/in_mem.rs +++ b/core/client/src/in_mem.rs @@ -465,17 +465,11 @@ where } fn reset_storage(&mut self, mut top: StorageMap, children: ChildrenStorageMap) -> error::Result<H::Out> { - if top.iter().any(|(k, _)| well_known_keys::is_child_storage_key(k)) { - return Err(error::ErrorKind::GenesisInvalid.into()); - } + check_genesis_storage(&top, &children)?; let mut transaction: Vec<(Option<Vec<u8>>, Vec<u8>, Option<Vec<u8>>)> = Default::default(); for (child_key, child_map) in children { - if !well_known_keys::is_child_storage_key(&child_key) { - return Err(error::ErrorKind::GenesisInvalid.into()); - } - let (root, is_default, update) = self.old_state.child_storage_root(&child_key, child_map.into_iter().map(|(k, v)| (k, Some(v)))); transaction.consolidate(update); @@ -662,6 +656,19 @@ where H::Out: HeapSizeOf + Ord, {} +impl<Block, H> backend::RemoteBackend<Block, H> for Backend<Block, H> +where + Block: BlockT, + H: Hasher<Out=Block::Hash>, + H::Out: HeapSizeOf + Ord, +{ + fn is_local_state_available(&self, block: &BlockId<Block>) -> bool { + self.blockchain.expect_block_number_from_id(block) + .map(|num| num.is_zero()) + .unwrap_or(false) + } +} + impl<Block: BlockT> Cache<Block> { fn insert(&self, at: Block::Hash, authorities: Option<Vec<AuthorityIdFor<Block>>>) { self.authorities_at.write().insert(at, authorities); @@ -708,6 +715,19 @@ pub fn cache_authorities_at<Block: BlockT>( blockchain.cache.insert(at, authorities); } +/// Check that genesis storage is valid. +pub fn check_genesis_storage(top: &StorageMap, children: &ChildrenStorageMap) -> error::Result<()> { + if top.iter().any(|(k, _)| well_known_keys::is_child_storage_key(k)) { + return Err(error::ErrorKind::GenesisInvalid.into()); + } + + if children.keys().any(|child_key| !well_known_keys::is_child_storage_key(&child_key)) { + return Err(error::ErrorKind::GenesisInvalid.into()); + } + + Ok(()) +} + #[cfg(test)] mod tests { use std::sync::Arc; diff --git a/core/client/src/light/backend.rs b/core/client/src/light/backend.rs index ca5c84cae2bc62a435e9ad9dd90d829a3a525db3..ff9e6e113efa339bc5128c6dfd27ed33f092ffd5 100644 --- a/core/client/src/light/backend.rs +++ b/core/client/src/light/backend.rs @@ -17,14 +17,15 @@ //! 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, Weak}; use futures::{Future, IntoFuture}; use parking_lot::RwLock; use runtime_primitives::{generic::BlockId, Justification, StorageMap, ChildrenStorageMap}; -use state_machine::{Backend as StateBackend, TrieBackend}; -use runtime_primitives::traits::{Block as BlockT, NumberFor, AuthorityIdFor}; -use crate::in_mem; +use state_machine::{Backend as StateBackend, TrieBackend, backend::InMemory as InMemoryState}; +use runtime_primitives::traits::{Block as BlockT, NumberFor, AuthorityIdFor, Zero, Header}; +use crate::in_mem::{self, check_genesis_storage}; use crate::backend::{AuxStore, Backend as ClientBackend, BlockImportOperation, RemoteBackend, NewBlockState}; use crate::blockchain::HeaderBackend as BlockchainHeaderBackend; use crate::error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult}; @@ -34,18 +35,22 @@ use hash_db::Hasher; use trie::MemoryDB; use heapsize::HeapSizeOf; +const IN_MEMORY_EXPECT_PROOF: &'static str = "InMemory state backend has Void error type and always suceeds; qed"; + /// Light client backend. -pub struct Backend<S, F> { +pub struct Backend<S, F, H> { blockchain: Arc<Blockchain<S, F>>, + genesis_state: RwLock<Option<InMemoryState<H>>>, } /// Light block (header and justification) import operation. -pub struct ImportOperation<Block: BlockT, S, F> { +pub struct ImportOperation<Block: BlockT, S, F, H> { header: Option<Block::Header>, authorities: Option<Vec<AuthorityIdFor<Block>>>, leaf_state: NewBlockState, aux_ops: Vec<(Vec<u8>, Option<Vec<u8>>)>, finalized_blocks: Vec<BlockId<Block>>, + storage_update: Option<InMemoryState<H>>, _phantom: ::std::marker::PhantomData<(S, F)>, } @@ -57,10 +62,21 @@ pub struct OnDemandState<Block: BlockT, S, F> { cached_header: RwLock<Option<Block::Header>>, } -impl<S, F> Backend<S, F> { +/// On-demand or in-memory genesis state. +pub enum OnDemandOrGenesisState<Block: BlockT, S, F, H> { + /// On-demand state - storage values are fetched from remote nodes. + OnDemand(OnDemandState<Block, S, F>), + /// Genesis state - storage values are stored in-memory. + Genesis(InMemoryState<H>), +} + +impl<S, F, H> Backend<S, F, H> { /// Create new light backend. pub fn new(blockchain: Arc<Blockchain<S, F>>) -> Self { - Self { blockchain } + Self { + blockchain, + genesis_state: RwLock::new(None), + } } /// Get shared blockchain reference. @@ -69,7 +85,7 @@ impl<S, F> Backend<S, F> { } } -impl<S: AuxStore, F> AuxStore for Backend<S, F> { +impl<S: AuxStore, F, H> AuxStore for Backend<S, F, H> { fn insert_aux< 'a, 'b: 'a, @@ -85,16 +101,16 @@ impl<S: AuxStore, F> AuxStore for Backend<S, F> { } } -impl<S, F, Block, H> ClientBackend<Block, H> for Backend<S, F> where +impl<S, F, Block, H> ClientBackend<Block, H> for Backend<S, F, H> where Block: BlockT, S: BlockchainStorage<Block>, F: Fetcher<Block>, H: Hasher<Out=Block::Hash>, H::Out: HeapSizeOf + Ord, { - type BlockImportOperation = ImportOperation<Block, S, F>; + type BlockImportOperation = ImportOperation<Block, S, F, H>; type Blockchain = Blockchain<S, F>; - type State = OnDemandState<Block, S, F>; + type State = OnDemandOrGenesisState<Block, S, F, H>; type ChangesTrieStorage = in_mem::ChangesTrieStorage<H>; fn begin_operation(&self) -> ClientResult<Self::BlockImportOperation> { @@ -104,6 +120,7 @@ impl<S, F, Block, H> ClientBackend<Block, H> for Backend<S, F> where leaf_state: NewBlockState::Normal, aux_ops: Vec::new(), finalized_blocks: Vec::new(), + storage_update: None, _phantom: Default::default(), }) } @@ -116,7 +133,7 @@ impl<S, F, Block, H> ClientBackend<Block, H> for Backend<S, F> where Ok(()) } - fn commit_operation(&self, operation: Self::BlockImportOperation) -> ClientResult<()> { + 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)?; @@ -124,12 +141,18 @@ impl<S, F, Block, H> ClientBackend<Block, H> for Backend<S, F> where } if let Some(header) = operation.header { + let is_genesis_import = header.number().is_zero(); self.blockchain.storage().import_header( header, operation.authorities, 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 { @@ -158,17 +181,23 @@ impl<S, F, Block, H> ClientBackend<Block, H> for Backend<S, F> where } fn state_at(&self, block: BlockId<Block>) -> ClientResult<Self::State> { - let block_hash = match block { - BlockId::Hash(h) => Some(h), - BlockId::Number(n) => self.blockchain.hash(n).unwrap_or_default(), - }; + 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(OnDemandOrGenesisState::Genesis(genesis_state)); + } + } - Ok(OnDemandState { + // 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.ok_or_else(|| ClientErrorKind::UnknownBlock(format!("{}", block)))?, + block: block_hash, cached_header: RwLock::new(None), - }) + })) } fn revert(&self, _n: NumberFor<Block>) -> ClientResult<NumberFor<Block>> { @@ -176,16 +205,23 @@ impl<S, F, Block, H> ClientBackend<Block, H> for Backend<S, F> where } } -impl<S, F, Block, H> RemoteBackend<Block, H> for Backend<S, F> +impl<S, F, Block, H> RemoteBackend<Block, H> for Backend<S, F, H> where Block: BlockT, S: BlockchainStorage<Block>, F: Fetcher<Block>, H: Hasher<Out=Block::Hash>, H::Out: HeapSizeOf + 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) + } +} -impl<S, F, Block, H> BlockImportOperation<Block, H> for ImportOperation<Block, S, F> +impl<S, F, Block, H> BlockImportOperation<Block, H> for ImportOperation<Block, S, F, H> where Block: BlockT, F: Fetcher<Block>, @@ -193,7 +229,7 @@ where H: Hasher<Out=Block::Hash>, H::Out: HeapSizeOf + Ord, { - type State = OnDemandState<Block, S, F>; + type State = OnDemandOrGenesisState<Block, S, F, H>; fn state(&self) -> ClientResult<Option<&Self::State>> { // None means 'locally-stateless' backend @@ -227,9 +263,19 @@ where } fn reset_storage(&mut self, top: StorageMap, children: ChildrenStorageMap) -> ClientResult<H::Out> { - let in_mem = in_mem::Backend::<Block, H>::new(); - let mut op = in_mem.begin_operation()?; - op.reset_storage(top, children) + check_genesis_storage(&top, &children)?; + + // this is only called when genesis block is imported => shouldn't be performance bottleneck + let mut storage: HashMap<Option<Vec<u8>>, StorageMap> = HashMap::new(); + storage.insert(None, top); + for (child_key, child_storage) in children { + storage.insert(Some(child_key), child_storage); + } + let storage_update: InMemoryState<H> = storage.into(); + let (storage_root, _) = storage_update.storage_root(::std::iter::empty()); + self.storage_update = Some(storage_update); + + Ok(storage_root) } fn insert_aux<I>(&mut self, ops: I) -> ClientResult<()> @@ -322,14 +368,139 @@ where } } +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: HeapSizeOf + Ord, +{ + type Error = ClientError; + type Transaction = (); + type TrieBackendStorage = MemoryDB<H>; + + fn storage(&self, key: &[u8]) -> ClientResult<Option<Vec<u8>>> { + match *self { + OnDemandOrGenesisState::OnDemand(ref state) => + StateBackend::<H>::storage(state, key), + OnDemandOrGenesisState::Genesis(ref state) => + Ok(state.storage(key).expect(IN_MEMORY_EXPECT_PROOF)), + } + } + + 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) => + Ok(state.child_storage(storage_key, key).expect(IN_MEMORY_EXPECT_PROOF)), + } + } + + 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), + } + } + + 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), + } + } + + fn storage_root<I>(&self, delta: I) -> (H::Out, Self::Transaction) + 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, ()) + }, + } + } + + fn child_storage_root<I>(&self, key: &[u8], delta: I) -> (Vec<u8>, bool, Self::Transaction) + 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) => { + let (root, is_equal, _) = state.child_storage_root(key, delta); + (root, is_equal, ()) + }, + } + } + + fn pairs(&self) -> Vec<(Vec<u8>, Vec<u8>)> { + match *self { + OnDemandOrGenesisState::OnDemand(ref state) => + StateBackend::<H>::pairs(state), + OnDemandOrGenesisState::Genesis(ref state) => state.pairs(), + } + } + + fn keys(&self, prefix: &Vec<u8>) -> Vec<Vec<u8>> { + match *self { + OnDemandOrGenesisState::OnDemand(ref state) => + StateBackend::<H>::keys(state, prefix), + OnDemandOrGenesisState::Genesis(ref state) => state.keys(prefix), + } + } + + fn try_into_trie_backend(self) -> Option<TrieBackend<Self::TrieBackendStorage, H>> { + match self { + OnDemandOrGenesisState::OnDemand(state) => state.try_into_trie_backend(), + OnDemandOrGenesisState::Genesis(state) => state.try_into_trie_backend(), + } + } +} + #[cfg(test)] mod tests { use primitives::Blake2Hasher; - use test_client::runtime::Block; + use test_client::{self, runtime::Block}; + use crate::backend::NewBlockState; use crate::light::blockchain::tests::{DummyBlockchain, DummyStorage}; use super::*; #[test] + fn local_state_is_created_when_genesis_state_is_available() { + 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 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(_) => (), + _ => 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()))); + + match backend.state_at(BlockId::Number(0)).unwrap() { + OnDemandOrGenesisState::OnDemand(_) => (), + _ => panic!("unexpected state"), + } + } + fn light_aux_store_is_updated_via_non_importing_op() { let backend = Backend::new(Arc::new(DummyBlockchain::new(DummyStorage::new()))); let mut op = ClientBackend::<Block, Blake2Hasher>::begin_operation(&backend).unwrap(); diff --git a/core/client/src/light/blockchain.rs b/core/client/src/light/blockchain.rs index 6cad6c684176d043149821be1397317c3965b3b0..89fe5f8a3a4d7261d97f01486706c9f6781d19e4 100644 --- a/core/client/src/light/blockchain.rs +++ b/core/client/src/light/blockchain.rs @@ -199,12 +199,20 @@ pub mod tests { Err(ClientErrorKind::Backend("Test error".into()).into()) } - fn number(&self, _hash: Hash) -> ClientResult<Option<NumberFor<Block>>> { - Err(ClientErrorKind::Backend("Test error".into()).into()) + fn number(&self, hash: Hash) -> ClientResult<Option<NumberFor<Block>>> { + if hash == Default::default() { + Ok(Some(Default::default())) + } else { + Err(ClientErrorKind::Backend("Test error".into()).into()) + } } - fn hash(&self, _number: u64) -> ClientResult<Option<Hash>> { - Err(ClientErrorKind::Backend("Test error".into()).into()) + fn hash(&self, number: u64) -> ClientResult<Option<Hash>> { + if number == 0 { + Ok(Some(Default::default())) + } else { + Err(ClientErrorKind::Backend("Test error".into()).into()) + } } } @@ -235,7 +243,7 @@ pub mod tests { _state: NewBlockState, _aux_ops: Vec<(Vec<u8>, Option<Vec<u8>>)>, ) -> ClientResult<()> { - Err(ClientErrorKind::Backend("Test error".into()).into()) + Ok(()) } fn finalize_header(&self, _block: BlockId<Block>) -> ClientResult<()> { diff --git a/core/client/src/light/call_executor.rs b/core/client/src/light/call_executor.rs index 734a1994bfb1b52b6108ae4cc818d5e433f1e283..05fd9dffeddd0afb2475a82ad7c3806f9a332c29 100644 --- a/core/client/src/light/call_executor.rs +++ b/core/client/src/light/call_executor.rs @@ -17,7 +17,7 @@ //! Light client call exector. Executes methods on remote full nodes, fetching //! execution proof and checking it locally. -use std::{collections::HashSet, marker::PhantomData, sync::Arc}; +use std::{collections::HashSet, sync::Arc, panic::UnwindSafe}; use futures::{IntoFuture, Future}; use codec::{Encode, Decode}; @@ -28,6 +28,7 @@ use state_machine::{self, Backend as StateBackend, CodeExecutor, OverlayedChange create_proof_check_backend, execution_proof_check_on_trie_backend, ExecutionManager}; use hash_db::Hasher; +use crate::backend::RemoteBackend; use crate::blockchain::Backend as ChainBackend; use crate::call_executor::CallExecutor; use crate::error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult}; @@ -38,35 +39,43 @@ use trie::MemoryDB; /// Call executor that executes methods on remote node, querying execution proof /// and checking proof by re-executing locally. -pub struct RemoteCallExecutor<B, F, H> { +pub struct RemoteCallExecutor<B, F> { blockchain: Arc<B>, fetcher: Arc<F>, - _hasher: PhantomData<H>, } -impl<B, F, H> Clone for RemoteCallExecutor<B, F, H> { +/// Remote or local call executor. +/// +/// 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> { + backend: Arc<B>, + remote: R, + local: L, + _block: ::std::marker::PhantomData<Block>, +} + +impl<B, F> Clone for RemoteCallExecutor<B, F> { fn clone(&self) -> Self { RemoteCallExecutor { blockchain: self.blockchain.clone(), fetcher: self.fetcher.clone(), - _hasher: Default::default(), } } } -impl<B, F, H> RemoteCallExecutor<B, F, H> { +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, _hasher: PhantomData } + RemoteCallExecutor { blockchain, fetcher } } } -impl<B, F, Block, H> CallExecutor<Block, H> for RemoteCallExecutor<B, F, H> +impl<B, F, Block> CallExecutor<Block, Blake2Hasher> for RemoteCallExecutor<B, F> where - Block: BlockT, + Block: BlockT<Hash=H256>, B: ChainBackend<Block>, F: Fetcher<Block>, - H: Hasher<Out=Block::Hash>, Block::Hash: Ord, { type Error = ClientError; @@ -118,7 +127,7 @@ where } fn call_at_state< - S: StateBackend<H>, + S: StateBackend<Blake2Hasher>, FF: FnOnce( Result<NativeOrEncoded<R>, Self::Error>, Result<NativeOrEncoded<R>, Self::Error> @@ -132,13 +141,13 @@ where _call_data: &[u8], _m: ExecutionManager<FF>, _native_call: Option<NC>, - ) -> ClientResult<(NativeOrEncoded<R>, S::Transaction, Option<MemoryDB<H>>)> { + ) -> ClientResult<(NativeOrEncoded<R>, S::Transaction, Option<MemoryDB<Blake2Hasher>>)> { Err(ClientErrorKind::NotAvailableOnLightClient.into()) } - fn prove_at_trie_state<S: state_machine::TrieBackendStorage<H>>( + fn prove_at_trie_state<S: state_machine::TrieBackendStorage<Blake2Hasher>>( &self, - _state: &state_machine::TrieBackend<S, H>, + _state: &state_machine::TrieBackend<S, Blake2Hasher>, _changes: &mut OverlayedChanges, _method: &str, _call_data: &[u8] @@ -151,6 +160,177 @@ where } } +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 { + 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> + 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; + + fn call(&self, id: &BlockId<Block>, method: &str, call_data: &[u8]) -> ClientResult<Vec<u8>> { + match self.backend.is_local_state_available(id) { + true => self.local.call(id, method, call_data), + false => self.remote.call(id, method, call_data), + } + } + + fn contextual_call< + PB: Fn() -> ClientResult<Block::Header>, + EM: Fn( + Result<NativeOrEncoded<R>, Self::Error>, + Result<NativeOrEncoded<R>, Self::Error> + ) -> Result<NativeOrEncoded<R>, Self::Error>, + R: Encode + Decode + PartialEq, + NC: FnOnce() -> R + UnwindSafe, + >( + &self, + at: &BlockId<Block>, + method: &str, + call_data: &[u8], + changes: &mut OverlayedChanges, + initialised_block: &mut Option<BlockId<Block>>, + prepare_environment_block: PB, + _manager: ExecutionManager<EM>, + native_call: Option<NC>, + ) -> 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, + at, + method, + call_data, + changes, + initialised_block, + prepare_environment_block, + ExecutionManager::NativeWhenPossible, + native_call, + ).map_err(|e| ClientErrorKind::Execution(Box::new(e.to_string())).into()), + false => CallExecutor::contextual_call::< + _, + fn( + Result<NativeOrEncoded<R>, Remote::Error>, + Result<NativeOrEncoded<R>, Remote::Error>, + ) -> Result<NativeOrEncoded<R>, Remote::Error>, + _, + NC + >( + &self.remote, + at, + method, + call_data, + changes, + initialised_block, + prepare_environment_block, + ExecutionManager::NativeWhenPossible, + native_call, + ).map_err(|e| ClientErrorKind::Execution(Box::new(e.to_string())).into()), + } + } + + 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), + } + } + + fn call_at_state< + 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() -> R + UnwindSafe, + >(&self, + state: &S, + changes: &mut OverlayedChanges, + method: &str, + call_data: &[u8], + _manager: ExecutionManager<FF>, + native_call: Option<NC>, + ) -> ClientResult<(NativeOrEncoded<R>, S::Transaction, Option<MemoryDB<Blake2Hasher>>)> { + // 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, + ).map_err(|e| ClientErrorKind::Execution(Box::new(e.to_string())).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>>)> { + self.remote.prove_at_trie_state(state, changes, method, call_data) + } + + 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. @@ -243,6 +423,9 @@ mod tests { use consensus::BlockOrigin; use test_client::{self, runtime::{Block, Header}, runtime::RuntimeApi, TestClient}; use executor::NativeExecutionDispatch; + use crate::backend::{Backend, NewBlockState}; + use crate::in_mem::Backend as InMemBackend; + use crate::light::fetcher::tests::OkCallFetcher; use super::*; #[test] @@ -309,4 +492,22 @@ mod tests { let local_block: Header = Decode::decode(&mut &block[..]).unwrap(); assert_eq!(local_block.number, 3); } + + #[test] + fn code_is_executed_locally_or_remotely() { + let backend = Arc::new(InMemBackend::new()); + let def = H256::default(); + let header0 = test_client::runtime::Header::new(0, def, def, def, Default::default()); + let hash0 = header0.hash(); + let header1 = test_client::runtime::Header::new(1, def, def, hash0, Default::default()); + let hash1 = header1.hash(); + 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); + assert_eq!(remote_or_local.call(&BlockId::Number(0), "test_method", &[]).unwrap(), vec![1]); + assert_eq!(remote_or_local.call(&BlockId::Number(1), "test_method", &[]).unwrap(), vec![2]); + } } diff --git a/core/client/src/light/mod.rs b/core/client/src/light/mod.rs index 4dc25affd126fed4cc35f87548213e925623cdcf..6bb7f7ac92dbab9ca6307cdc699d889b268a06b0 100644 --- a/core/client/src/light/mod.rs +++ b/core/client/src/light/mod.rs @@ -23,18 +23,19 @@ pub mod fetcher; use std::sync::Arc; +use executor::RuntimeInfo; use primitives::{H256, Blake2Hasher}; use runtime_primitives::BuildStorage; use runtime_primitives::traits::Block as BlockT; use state_machine::{CodeExecutor, ExecutionStrategy}; +use crate::call_executor::LocalCallExecutor; 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; +use crate::light::call_executor::{RemoteCallExecutor, RemoteOrLocalCallExecutor}; use crate::light::fetcher::{Fetcher, LightDataChecker}; -use hash_db::Hasher; /// 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>> { @@ -42,37 +43,48 @@ pub fn new_light_blockchain<B: BlockT, S: BlockchainStorage<B>, F>(storage: S) - } /// Create an instance of light client backend. -pub fn new_light_backend<B: BlockT, S: BlockchainStorage<B>, F: Fetcher<B>>(blockchain: Arc<Blockchain<S, F>>, fetcher: Arc<F>) -> Arc<Backend<S, F>> { +pub fn new_light_backend<B, S, F>(blockchain: Arc<Blockchain<S, F>>, fetcher: Arc<F>) -> Arc<Backend<S, F, 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>( - backend: Arc<Backend<S, F>>, +pub fn new_light<B, S, F, GS, RA, E>( + backend: Arc<Backend<S, F, Blake2Hasher>>, fetcher: Arc<F>, genesis_storage: GS, -) -> ClientResult<Client<Backend<S, F>, RemoteCallExecutor<Blockchain<S, F>, F, Blake2Hasher>, B, RA>> -where - B: BlockT<Hash=H256>, - S: BlockchainStorage<B>, - F: Fetcher<B>, - GS: BuildStorage, - + 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> +>, B, RA>> + where + B: BlockT<Hash=H256>, + S: BlockchainStorage<B>, + F: Fetcher<B>, + GS: BuildStorage, + E: CodeExecutor<Blake2Hasher> + RuntimeInfo, { - let executor = RemoteCallExecutor::new(backend.blockchain().clone(), fetcher); + let remote_executor = RemoteCallExecutor::new(backend.blockchain().clone(), fetcher); + let local_executor = LocalCallExecutor::new(backend.clone(), code_executor); + let executor = RemoteOrLocalCallExecutor::new(backend.clone(), remote_executor, local_executor); Client::new(backend, executor, genesis_storage, ExecutionStrategy::NativeWhenPossible, ExecutionStrategy::NativeWhenPossible) } /// Create an instance of fetch data checker. -pub fn new_fetch_checker<E, H, B: BlockT, S: BlockchainStorage<B>, F>( +pub fn new_fetch_checker<E, B: BlockT, S: BlockchainStorage<B>, F>( blockchain: Arc<Blockchain<S, F>>, executor: E, -) -> LightDataChecker<E, H, B, S, F> +) -> LightDataChecker<E, Blake2Hasher, B, S, F> where - E: CodeExecutor<H>, - H: Hasher, - + E: CodeExecutor<Blake2Hasher>, { LightDataChecker::new(blockchain, executor) } diff --git a/core/service/src/components.rs b/core/service/src/components.rs index c966911996b6278fd703c1382e1cf353316c6258..c1a9c63fe9e086d308542bde4b97e8f30e99bfb9 100644 --- a/core/service/src/components.rs +++ b/core/service/src/components.rs @@ -60,16 +60,32 @@ pub type FullExecutor<F> = client::LocalCallExecutor< pub type LightBackend<F> = client::light::backend::Backend< client_db::light::LightStorage<<F as ServiceFactory>::Block>, network::OnDemand<<F as ServiceFactory>::Block, NetworkService<F>>, + Blake2Hasher, >; /// Light client executor type for a factory. -pub type LightExecutor<F> = client::light::call_executor::RemoteCallExecutor< - client::light::blockchain::Blockchain< +pub type LightExecutor<F> = client::light::call_executor::RemoteOrLocalCallExecutor< + <F as ServiceFactory>::Block, + client::light::backend::Backend< client_db::light::LightStorage<<F as ServiceFactory>::Block>, + network::OnDemand<<F as ServiceFactory>::Block, NetworkService<F>>, + Blake2Hasher + >, + client::light::call_executor::RemoteCallExecutor< + client::light::blockchain::Blockchain< + client_db::light::LightStorage<<F as ServiceFactory>::Block>, + network::OnDemand<<F as ServiceFactory>::Block, NetworkService<F>> + >, network::OnDemand<<F as ServiceFactory>::Block, NetworkService<F>> >, - network::OnDemand<<F as ServiceFactory>::Block, NetworkService<F>>, - Blake2Hasher, + client::LocalCallExecutor< + client::light::backend::Backend< + client_db::light::LightStorage<<F as ServiceFactory>::Block>, + network::OnDemand<<F as ServiceFactory>::Block, NetworkService<F>>, + Blake2Hasher + >, + CodeExecutor<F> + > >; /// Full client type for a factory. @@ -499,10 +515,10 @@ impl<Factory: ServiceFactory> Components for LightComponents<Factory> { }; let db_storage = client_db::light::LightStorage::new(db_settings)?; let light_blockchain = client::light::new_light_blockchain(db_storage); - let fetch_checker = Arc::new(client::light::new_fetch_checker::<_, Blake2Hasher, _, _, _>(light_blockchain.clone(), executor)); + let fetch_checker = Arc::new(client::light::new_fetch_checker(light_blockchain.clone(), executor.clone())); let fetcher = Arc::new(network::OnDemand::new(fetch_checker)); let client_backend = client::light::new_light_backend(light_blockchain, fetcher.clone()); - let client = client::light::new_light(client_backend, fetcher.clone(), &config.chain_spec)?; + let client = client::light::new_light(client_backend, fetcher.clone(), &config.chain_spec, executor)?; Ok((Arc::new(client), Some(fetcher))) }