mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-28 00:28:01 +00:00
Make genesis state locally-available on light client (#1622)
* make genesis state available on light client * RemoteOrLocalCallExecutor * code_is_executed_locally_or_remotely * OnDemandOrGenesisState tests * some comments
This commit is contained in:
committed by
Gav Wood
parent
5d82cf243e
commit
381cf26f55
@@ -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)?;
|
||||
|
||||
Ok(OnDemandState {
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
@@ -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<()> {
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user