diff --git a/core/client/src/backend.rs b/core/client/src/backend.rs index 60a0069db4902..108b30df03b19 100644 --- a/core/client/src/backend.rs +++ b/core/client/src/backend.rs @@ -177,4 +177,7 @@ pub trait RemoteBackend: Backend where Block: BlockT, H: Hasher, -{} +{ + /// Returns true if the state for given block is available locally. + fn is_local_state_available(&self, block: &BlockId) -> bool; +} diff --git a/core/client/src/call_executor.rs b/core/client/src/call_executor.rs index 4c2ff946b56b6..4c274fc43633d 100644 --- a/core/client/src/call_executor.rs +++ b/core/client/src/call_executor.rs @@ -154,7 +154,7 @@ impl Clone for LocalCallExecutor where E: Clone { impl CallExecutor for LocalCallExecutor where - B: backend::LocalBackend, + B: backend::Backend, E: CodeExecutor + RuntimeInfo, Block: BlockT, { diff --git a/core/client/src/in_mem.rs b/core/client/src/in_mem.rs index 7102426b8f493..6f53ad7d2a348 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 { - 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, Option>)> = 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 backend::RemoteBackend for Backend +where + Block: BlockT, + H: Hasher, + H::Out: HeapSizeOf + Ord, +{ + fn is_local_state_available(&self, block: &BlockId) -> bool { + self.blockchain.expect_block_number_from_id(block) + .map(|num| num.is_zero()) + .unwrap_or(false) + } +} + impl Cache { fn insert(&self, at: Block::Hash, authorities: Option>>) { self.authorities_at.write().insert(at, authorities); @@ -708,6 +715,19 @@ pub fn cache_authorities_at( 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 ca5c84cae2bc6..ff9e6e113efa3 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 { +pub struct Backend { blockchain: Arc>, + genesis_state: RwLock>>, } /// Light block (header and justification) import operation. -pub struct ImportOperation { +pub struct ImportOperation { header: Option, authorities: Option>>, leaf_state: NewBlockState, aux_ops: Vec<(Vec, Option>)>, finalized_blocks: Vec>, + storage_update: Option>, _phantom: ::std::marker::PhantomData<(S, F)>, } @@ -57,10 +62,21 @@ pub struct OnDemandState { cached_header: RwLock>, } -impl Backend { +/// On-demand or in-memory genesis state. +pub enum OnDemandOrGenesisState { + /// On-demand state - storage values are fetched from remote nodes. + OnDemand(OnDemandState), + /// Genesis state - storage values are stored in-memory. + Genesis(InMemoryState), +} + +impl Backend { /// Create new light backend. pub fn new(blockchain: Arc>) -> Self { - Self { blockchain } + Self { + blockchain, + genesis_state: RwLock::new(None), + } } /// Get shared blockchain reference. @@ -69,7 +85,7 @@ impl Backend { } } -impl AuxStore for Backend { +impl AuxStore for Backend { fn insert_aux< 'a, 'b: 'a, @@ -85,16 +101,16 @@ impl AuxStore for Backend { } } -impl ClientBackend for Backend where +impl ClientBackend for Backend where Block: BlockT, S: BlockchainStorage, F: Fetcher, H: Hasher, H::Out: HeapSizeOf + Ord, { - type BlockImportOperation = ImportOperation; + type BlockImportOperation = ImportOperation; type Blockchain = Blockchain; - type State = OnDemandState; + type State = OnDemandOrGenesisState; type ChangesTrieStorage = in_mem::ChangesTrieStorage; fn begin_operation(&self) -> ClientResult { @@ -104,6 +120,7 @@ impl ClientBackend for Backend where leaf_state: NewBlockState::Normal, aux_ops: Vec::new(), finalized_blocks: Vec::new(), + storage_update: None, _phantom: Default::default(), }) } @@ -116,7 +133,7 @@ impl ClientBackend for Backend 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 ClientBackend for Backend 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 ClientBackend for Backend where } fn state_at(&self, block: BlockId) -> ClientResult { - 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) -> ClientResult> { @@ -176,16 +205,23 @@ impl ClientBackend for Backend where } } -impl RemoteBackend for Backend +impl RemoteBackend for Backend where Block: BlockT, S: BlockchainStorage, F: Fetcher, H: Hasher, H::Out: HeapSizeOf + Ord, -{} +{ + fn is_local_state_available(&self, block: &BlockId) -> bool { + self.genesis_state.read().is_some() + && self.blockchain.expect_block_number_from_id(block) + .map(|num| num.is_zero()) + .unwrap_or(false) + } +} -impl BlockImportOperation for ImportOperation +impl BlockImportOperation for ImportOperation where Block: BlockT, F: Fetcher, @@ -193,7 +229,7 @@ where H: Hasher, H::Out: HeapSizeOf + Ord, { - type State = OnDemandState; + type State = OnDemandOrGenesisState; fn state(&self) -> ClientResult> { // None means 'locally-stateless' backend @@ -227,9 +263,19 @@ where } fn reset_storage(&mut self, top: StorageMap, children: ChildrenStorageMap) -> ClientResult { - let in_mem = in_mem::Backend::::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>, 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 = storage.into(); + let (storage_root, _) = storage_update.storage_root(::std::iter::empty()); + self.storage_update = Some(storage_update); + + Ok(storage_root) } fn insert_aux(&mut self, ops: I) -> ClientResult<()> @@ -322,14 +368,139 @@ where } } +impl StateBackend for OnDemandOrGenesisState +where + Block: BlockT, + F: Fetcher, + S: BlockchainStorage, + H: Hasher, + H::Out: HeapSizeOf + Ord, +{ + type Error = ClientError; + type Transaction = (); + type TrieBackendStorage = MemoryDB; + + fn storage(&self, key: &[u8]) -> ClientResult>> { + match *self { + OnDemandOrGenesisState::OnDemand(ref state) => + StateBackend::::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>> { + match *self { + OnDemandOrGenesisState::OnDemand(ref state) => + StateBackend::::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(&self, prefix: &[u8], action: A) { + match *self { + OnDemandOrGenesisState::OnDemand(ref state) => + StateBackend::::for_keys_with_prefix(state, prefix, action), + OnDemandOrGenesisState::Genesis(ref state) => state.for_keys_with_prefix(prefix, action), + } + } + + fn for_keys_in_child_storage(&self, storage_key: &[u8], action: A) { + match *self { + OnDemandOrGenesisState::OnDemand(ref state) => + StateBackend::::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(&self, delta: I) -> (H::Out, Self::Transaction) + where + I: IntoIterator, Option>)> + { + match *self { + OnDemandOrGenesisState::OnDemand(ref state) => + StateBackend::::storage_root(state, delta), + OnDemandOrGenesisState::Genesis(ref state) => { + let (root, _) = state.storage_root(delta); + (root, ()) + }, + } + } + + fn child_storage_root(&self, key: &[u8], delta: I) -> (Vec, bool, Self::Transaction) + where + I: IntoIterator, Option>)> + { + match *self { + OnDemandOrGenesisState::OnDemand(ref state) => + StateBackend::::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, Vec)> { + match *self { + OnDemandOrGenesisState::OnDemand(ref state) => + StateBackend::::pairs(state), + OnDemandOrGenesisState::Genesis(ref state) => state.pairs(), + } + } + + fn keys(&self, prefix: &Vec) -> Vec> { + match *self { + OnDemandOrGenesisState::OnDemand(ref state) => + StateBackend::::keys(state, prefix), + OnDemandOrGenesisState::Genesis(ref state) => state.keys(prefix), + } + } + + fn try_into_trie_backend(self) -> Option> { + 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::::begin_operation(&backend).unwrap(); diff --git a/core/client/src/light/blockchain.rs b/core/client/src/light/blockchain.rs index 6cad6c684176d..89fe5f8a3a4d7 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>> { - Err(ClientErrorKind::Backend("Test error".into()).into()) + fn number(&self, hash: Hash) -> ClientResult>> { + if hash == Default::default() { + Ok(Some(Default::default())) + } else { + Err(ClientErrorKind::Backend("Test error".into()).into()) + } } - fn hash(&self, _number: u64) -> ClientResult> { - Err(ClientErrorKind::Backend("Test error".into()).into()) + fn hash(&self, number: u64) -> ClientResult> { + 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, Option>)>, ) -> ClientResult<()> { - Err(ClientErrorKind::Backend("Test error".into()).into()) + Ok(()) } fn finalize_header(&self, _block: BlockId) -> ClientResult<()> { diff --git a/core/client/src/light/call_executor.rs b/core/client/src/light/call_executor.rs index 734a1994bfb1b..05fd9dffeddd0 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 { +pub struct RemoteCallExecutor { blockchain: Arc, fetcher: Arc, - _hasher: PhantomData, } -impl Clone for RemoteCallExecutor { +/// 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, B, R, L> { + backend: Arc, + remote: R, + local: L, + _block: ::std::marker::PhantomData, +} + +impl Clone for RemoteCallExecutor { fn clone(&self) -> Self { RemoteCallExecutor { blockchain: self.blockchain.clone(), fetcher: self.fetcher.clone(), - _hasher: Default::default(), } } } -impl RemoteCallExecutor { +impl RemoteCallExecutor { /// Creates new instance of remote call executor. pub fn new(blockchain: Arc, fetcher: Arc) -> Self { - RemoteCallExecutor { blockchain, fetcher, _hasher: PhantomData } + RemoteCallExecutor { blockchain, fetcher } } } -impl CallExecutor for RemoteCallExecutor +impl CallExecutor for RemoteCallExecutor where - Block: BlockT, + Block: BlockT, B: ChainBackend, F: Fetcher, - H: Hasher, Block::Hash: Ord, { type Error = ClientError; @@ -118,7 +127,7 @@ where } fn call_at_state< - S: StateBackend, + S: StateBackend, FF: FnOnce( Result, Self::Error>, Result, Self::Error> @@ -132,13 +141,13 @@ where _call_data: &[u8], _m: ExecutionManager, _native_call: Option, - ) -> ClientResult<(NativeOrEncoded, S::Transaction, Option>)> { + ) -> ClientResult<(NativeOrEncoded, S::Transaction, Option>)> { Err(ClientErrorKind::NotAvailableOnLightClient.into()) } - fn prove_at_trie_state>( + fn prove_at_trie_state>( &self, - _state: &state_machine::TrieBackend, + _state: &state_machine::TrieBackend, _changes: &mut OverlayedChanges, _method: &str, _call_data: &[u8] @@ -151,6 +160,177 @@ where } } +impl Clone for RemoteOrLocalCallExecutor + where + Block: BlockT, + B: RemoteBackend, + R: CallExecutor + Clone, + L: CallExecutor + Clone, +{ + fn clone(&self) -> Self { + RemoteOrLocalCallExecutor { + backend: self.backend.clone(), + remote: self.remote.clone(), + local: self.local.clone(), + _block: Default::default(), + } + } +} + +impl RemoteOrLocalCallExecutor + where + Block: BlockT, + B: RemoteBackend, + Remote: CallExecutor, + Local: CallExecutor, +{ + /// Creates new instance of remote/local call executor. + pub fn new(backend: Arc, remote: Remote, local: Local) -> Self { + RemoteOrLocalCallExecutor { backend, remote, local, _block: Default::default(), } + } +} + +impl CallExecutor for + RemoteOrLocalCallExecutor + where + Block: BlockT, + B: RemoteBackend, + Remote: CallExecutor, + Local: CallExecutor, +{ + type Error = ClientError; + + fn call(&self, id: &BlockId, method: &str, call_data: &[u8]) -> ClientResult> { + 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, + EM: Fn( + Result, Self::Error>, + Result, Self::Error> + ) -> Result, Self::Error>, + R: Encode + Decode + PartialEq, + NC: FnOnce() -> R + UnwindSafe, + >( + &self, + at: &BlockId, + method: &str, + call_data: &[u8], + changes: &mut OverlayedChanges, + initialised_block: &mut Option>, + prepare_environment_block: PB, + _manager: ExecutionManager, + native_call: Option, + ) -> ClientResult> where ExecutionManager: 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, Local::Error>, + Result, Local::Error>, + ) -> Result, 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, Remote::Error>, + Result, Remote::Error>, + ) -> Result, 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) -> ClientResult { + 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, + FF: FnOnce( + Result, Self::Error>, + Result, Self::Error> + ) -> Result, Self::Error>, + R: Encode + Decode + PartialEq, + NC: FnOnce() -> R + UnwindSafe, + >(&self, + state: &S, + changes: &mut OverlayedChanges, + method: &str, + call_data: &[u8], + _manager: ExecutionManager, + native_call: Option, + ) -> ClientResult<(NativeOrEncoded, S::Transaction, Option>)> { + // 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, Remote::Error>, + Result, Remote::Error>, + ) -> Result, 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>( + &self, + state: &state_machine::TrieBackend, + changes: &mut OverlayedChanges, + method: &str, + call_data: &[u8] + ) -> ClientResult<(Vec, Vec>)> { + 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 4dc25affd126f..6bb7f7ac92dba 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, F>(storage: S) -> Arc> { @@ -42,37 +43,48 @@ pub fn new_light_blockchain, F>(storage: S) - } /// Create an instance of light client backend. -pub fn new_light_backend, F: Fetcher>(blockchain: Arc>, fetcher: Arc) -> Arc> { +pub fn new_light_backend(blockchain: Arc>, fetcher: Arc) -> Arc> + where + B: BlockT, + S: BlockchainStorage, + F: Fetcher, +{ blockchain.set_fetcher(Arc::downgrade(&fetcher)); Arc::new(Backend::new(blockchain)) } /// Create an instance of light client. -pub fn new_light( - backend: Arc>, +pub fn new_light( + backend: Arc>, fetcher: Arc, genesis_storage: GS, -) -> ClientResult, RemoteCallExecutor, F, Blake2Hasher>, B, RA>> -where - B: BlockT, - S: BlockchainStorage, - F: Fetcher, - GS: BuildStorage, - + code_executor: E, +) -> ClientResult, RemoteOrLocalCallExecutor< + B, + Backend, + RemoteCallExecutor, F>, + LocalCallExecutor, E> +>, B, RA>> + where + B: BlockT, + S: BlockchainStorage, + F: Fetcher, + GS: BuildStorage, + E: CodeExecutor + 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, F>( +pub fn new_fetch_checker, F>( blockchain: Arc>, executor: E, -) -> LightDataChecker +) -> LightDataChecker where - E: CodeExecutor, - H: Hasher, - + E: CodeExecutor, { LightDataChecker::new(blockchain, executor) } diff --git a/core/service/src/components.rs b/core/service/src/components.rs index c966911996b62..c1a9c63fe9e08 100644 --- a/core/service/src/components.rs +++ b/core/service/src/components.rs @@ -60,16 +60,32 @@ pub type FullExecutor = client::LocalCallExecutor< pub type LightBackend = client::light::backend::Backend< client_db::light::LightStorage<::Block>, network::OnDemand<::Block, NetworkService>, + Blake2Hasher, >; /// Light client executor type for a factory. -pub type LightExecutor = client::light::call_executor::RemoteCallExecutor< - client::light::blockchain::Blockchain< +pub type LightExecutor = client::light::call_executor::RemoteOrLocalCallExecutor< + ::Block, + client::light::backend::Backend< client_db::light::LightStorage<::Block>, + network::OnDemand<::Block, NetworkService>, + Blake2Hasher + >, + client::light::call_executor::RemoteCallExecutor< + client::light::blockchain::Blockchain< + client_db::light::LightStorage<::Block>, + network::OnDemand<::Block, NetworkService> + >, network::OnDemand<::Block, NetworkService> >, - network::OnDemand<::Block, NetworkService>, - Blake2Hasher, + client::LocalCallExecutor< + client::light::backend::Backend< + client_db::light::LightStorage<::Block>, + network::OnDemand<::Block, NetworkService>, + Blake2Hasher + >, + CodeExecutor + > >; /// Full client type for a factory. @@ -499,10 +515,10 @@ impl Components for LightComponents { }; 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))) }