Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 9 additions & 4 deletions substrate/client/src/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,27 @@
//! Polkadot blockchain trait

use primitives::AuthorityId;
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT};
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, NumberFor};
use runtime_primitives::generic::BlockId;
use runtime_primitives::bft::Justification;

use error::Result;
use error::{ErrorKind, Result};

/// Blockchain database header backend. Does not perform any validation.
pub trait HeaderBackend<Block: BlockT>: Send + Sync {
/// Get block header. Returns `None` if block is not found.
fn header(&self, id: BlockId<Block>) -> Result<Option<<Block as BlockT>::Header>>;
fn header(&self, id: BlockId<Block>) -> Result<Option<Block::Header>>;
/// Get blockchain info.
fn info(&self) -> Result<Info<Block>>;
/// Get block status.
fn status(&self, id: BlockId<Block>) -> Result<BlockStatus>;
/// Get block hash by number. Returns `None` if the header is not in the chain.
fn hash(&self, number: <<Block as BlockT>::Header as HeaderT>::Number) -> Result<Option<<<Block as BlockT>::Header as HeaderT>::Hash>>;
fn hash(&self, number: NumberFor<Block>) -> Result<Option<Block::Hash>>;

/// Get block header. Returns `UnknownBlock` error if block is not found.
fn expect_header(&self, id: BlockId<Block>) -> Result<Block::Header> {
self.header(id)?.ok_or_else(|| ErrorKind::UnknownBlock(format!("{}", id)).into())
}
}

/// Blockchain database backend. Does not perform any validation.
Expand Down
13 changes: 12 additions & 1 deletion substrate/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Zero, One,
use runtime_primitives::BuildStorage;
use primitives::storage::{StorageKey, StorageData};
use codec::Decode;
use state_machine::{Ext, OverlayedChanges, Backend as StateBackend, CodeExecutor, ExecutionStrategy, ExecutionManager};
use state_machine::{
Ext, OverlayedChanges, Backend as StateBackend, CodeExecutor,
ExecutionStrategy, ExecutionManager, prove_read
};

use backend::{self, BlockImportOperation};
use blockchain::{self, Info as ChainInfo, Backend as ChainBackend, HeaderBackend as ChainHeaderBackend};
Expand Down Expand Up @@ -247,6 +250,14 @@ impl<B, E, Block> Client<B, E, Block> where
&self.executor
}

/// Reads storage value at a given block + key, returning read proof.
pub fn read_proof(&self, id: &BlockId<Block>, key: &[u8]) -> error::Result<Vec<Vec<u8>>> {
self.state_at(id)
.and_then(|state| prove_read(state, key)
.map(|(_, proof)| proof)
.map_err(Into::into))
}

/// Execute a call to a contract on top of state in a block of given hash
/// AND returning execution proof.
///
Expand Down
111 changes: 84 additions & 27 deletions substrate/client/src/light/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
//! Everything else is requested from full nodes on demand.

use std::sync::{Arc, Weak};
use futures::{Future, IntoFuture};
use parking_lot::RwLock;

use primitives::AuthorityId;
use runtime_primitives::{bft::Justification, generic::BlockId};
Expand All @@ -29,25 +31,27 @@ use backend::{Backend as ClientBackend, BlockImportOperation, RemoteBackend};
use blockchain::HeaderBackend as BlockchainHeaderBackend;
use error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult};
use light::blockchain::{Blockchain, Storage as BlockchainStorage};
use light::fetcher::Fetcher;
use light::fetcher::{Fetcher, RemoteReadRequest};

/// Light client backend.
pub struct Backend<S, F> {
blockchain: Arc<Blockchain<S, F>>,
}

/// Light block (header and justification) import operation.
pub struct ImportOperation<Block: BlockT, F> {
pub struct ImportOperation<Block: BlockT, S, F> {
is_new_best: bool,
header: Option<Block::Header>,
authorities: Option<Vec<AuthorityId>>,
_phantom: ::std::marker::PhantomData<F>,
_phantom: ::std::marker::PhantomData<(S, F)>,
}

/// On-demand state.
pub struct OnDemandState<Block: BlockT, F> {
pub struct OnDemandState<Block: BlockT, S, F> {
fetcher: Weak<F>,
blockchain: Weak<Blockchain<S, F>>,
block: Block::Hash,
cached_header: RwLock<Option<Block::Header>>,
}

impl<S, F> Backend<S, F> {
Expand All @@ -65,11 +69,11 @@ impl<S, F> Backend<S, F> {
impl<S, F, Block> ClientBackend<Block> for Backend<S, F> where
Block: BlockT,
S: BlockchainStorage<Block>,
F: Fetcher<Block>,
F: Fetcher<Block>
{
type BlockImportOperation = ImportOperation<Block, F>;
type BlockImportOperation = ImportOperation<Block, S, F>;
type Blockchain = Blockchain<S, F>;
type State = OnDemandState<Block, F>;
type State = OnDemandState<Block, S, F>;

fn begin_operation(&self, _block: BlockId<Block>) -> ClientResult<Self::BlockImportOperation> {
Ok(ImportOperation {
Expand All @@ -96,8 +100,10 @@ impl<S, F, Block> ClientBackend<Block> for Backend<S, F> where
};

Ok(OnDemandState {
block: block_hash.ok_or_else(|| ClientErrorKind::UnknownBlock(format!("{}", block)))?,
fetcher: self.blockchain.fetcher(),
blockchain: Arc::downgrade(&self.blockchain),
block: block_hash.ok_or_else(|| ClientErrorKind::UnknownBlock(format!("{}", block)))?,
cached_header: RwLock::new(None),
})
}

Expand All @@ -108,8 +114,13 @@ impl<S, F, Block> ClientBackend<Block> for Backend<S, F> where

impl<S, F, Block> RemoteBackend<Block> for Backend<S, F> where Block: BlockT, S: BlockchainStorage<Block>, F: Fetcher<Block> {}

impl<F, Block> BlockImportOperation<Block> for ImportOperation<Block, F> where Block: BlockT, F: Fetcher<Block> {
type State = OnDemandState<Block, F>;
impl<S, F, Block> BlockImportOperation<Block> for ImportOperation<Block, S, F>
where
Block: BlockT,
S: BlockchainStorage<Block>,
F: Fetcher<Block>,
{
type State = OnDemandState<Block, S, F>;

fn state(&self) -> ClientResult<Option<&Self::State>> {
// None means 'locally-stateless' backend
Expand Down Expand Up @@ -143,21 +154,32 @@ impl<F, Block> BlockImportOperation<Block> for ImportOperation<Block, F> where B
}
}

impl<Block: BlockT, F> Clone for OnDemandState<Block, F> {
fn clone(&self) -> Self {
OnDemandState {
fetcher: self.fetcher.clone(),
block: self.block,
}
}
}

impl<Block, F> StateBackend for OnDemandState<Block, F> where Block: BlockT, F: Fetcher<Block> {
impl<Block, S, F> StateBackend for OnDemandState<Block, S, F>
where
Block: BlockT,
S: BlockchainStorage<Block>,
F: Fetcher<Block>,
{
type Error = ClientError;
type Transaction = ();

fn storage(&self, _key: &[u8]) -> ClientResult<Option<Vec<u8>>> {
Err(ClientErrorKind::NotAvailableOnLightClient.into()) // TODO: fetch from remote node
fn storage(&self, key: &[u8]) -> ClientResult<Option<Vec<u8>>> {
let mut header = self.cached_header.read().clone();
if header.is_none() {
let cached_header = self.blockchain.upgrade()
.ok_or_else(|| ClientErrorKind::UnknownBlock(format!("{}", self.block)).into())
.and_then(|blockchain| blockchain.expect_header(BlockId::Hash(self.block)))?;
header = Some(cached_header.clone());
*self.cached_header.write() = Some(cached_header);
}

self.fetcher.upgrade().ok_or(ClientErrorKind::NotAvailableOnLightClient)?
.remote_read(RemoteReadRequest {
block: self.block,
header: header.expect("if block above guarantees that header is_some(); qed"),
key: key.to_vec(),
})
.into_future().wait()
}

fn for_keys_with_prefix<A: FnMut(&[u8])>(&self, _prefix: &[u8], _action: A) {
Expand All @@ -175,28 +197,63 @@ impl<Block, F> StateBackend for OnDemandState<Block, F> where Block: BlockT, F:
}
}

impl<Block, F> TryIntoStateTrieBackend for OnDemandState<Block, F> where Block: BlockT, F: Fetcher<Block> {
impl<Block, S, F> TryIntoStateTrieBackend for OnDemandState<Block, S, F> where Block: BlockT, F: Fetcher<Block> {
fn try_into_trie_backend(self) -> Option<StateTrieBackend> {
None
}
}

#[cfg(test)]
pub mod tests {
use futures::future::{ok, FutureResult};
use futures::future::{ok, err, FutureResult};
use parking_lot::Mutex;
use call_executor::CallResult;
use executor::NativeExecutionDispatch;
use error::Error as ClientError;
use test_client::runtime::{Hash, Block};
use light::fetcher::{Fetcher, RemoteCallRequest};
use test_client::{self, runtime::{Header, Block}};
use light::new_fetch_checker;
use light::fetcher::{Fetcher, FetchChecker, RemoteCallRequest};
use super::*;

pub type OkCallFetcher = Mutex<CallResult>;

impl Fetcher<Block> for OkCallFetcher {
type RemoteReadResult = FutureResult<Option<Vec<u8>>, ClientError>;
type RemoteCallResult = FutureResult<CallResult, ClientError>;

fn remote_call(&self, _request: RemoteCallRequest<Hash>) -> Self::RemoteCallResult {
fn remote_read(&self, _request: RemoteReadRequest<Header>) -> Self::RemoteReadResult {
err("Not implemented on test node".into())
}

fn remote_call(&self, _request: RemoteCallRequest<Header>) -> Self::RemoteCallResult {
ok((*self.lock()).clone())
}
}

#[test]
fn storage_read_proof_is_generated_and_checked() {
// prepare remote client
let remote_client = test_client::new();
let remote_block_id = BlockId::Number(0);
let remote_block_hash = remote_client.block_hash(0).unwrap().unwrap();
let mut remote_block_header = remote_client.header(&remote_block_id).unwrap().unwrap();
remote_block_header.state_root = remote_client.state_at(&remote_block_id)
.unwrap().storage_root(::std::iter::empty()).0.into();

// 'fetch' read proof from remote node
let authorities_len = remote_client.authorities_at(&remote_block_id).unwrap().len();
let remote_read_proof = remote_client.read_proof(&remote_block_id, b":auth:len").unwrap();

// check remote read proof locally
let local_executor = test_client::LocalExecutor::with_heap_pages(8);
let local_checker = new_fetch_checker(local_executor);
let request = RemoteReadRequest {
block: remote_block_hash,
header: remote_block_header,
key: b":auth:len".to_vec(),
};
assert_eq!((&local_checker as &FetchChecker<Block>).check_read_proof(
&request,
remote_read_proof).unwrap().unwrap()[0], authorities_len as u8);
}
}
38 changes: 15 additions & 23 deletions substrate/client/src/light/call_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,11 @@ impl<B, F, Block> CallExecutor<Block> for RemoteCallExecutor<B, F>
BlockId::Number(number) => self.blockchain.hash(number)?
.ok_or_else(|| ClientErrorKind::UnknownBlock(format!("{}", number)))?,
};
let block_header = self.blockchain.expect_header(id.clone())?;

self.fetcher.remote_call(RemoteCallRequest {
block: block_hash.clone(),
block: block_hash,
header: block_header,
method: method.into(),
call_data: call_data.to_vec(),
}).into_future().wait()
Expand Down Expand Up @@ -97,34 +99,17 @@ impl<B, F, Block> CallExecutor<Block> for RemoteCallExecutor<B, F>
}

/// Check remote execution proof using given backend.
pub fn check_execution_proof<Block, B, E>(
blockchain: &B,
pub fn check_execution_proof<Header, E>(
executor: &E,
request: &RemoteCallRequest<Block::Hash>,
request: &RemoteCallRequest<Header>,
remote_proof: Vec<Vec<u8>>
) -> ClientResult<CallResult>
where
Block: BlockT,
B: ChainBackend<Block>,
Header: HeaderT,
E: CodeExecutor,
{
let local_header = blockchain.header(BlockId::Hash(request.block))?;
let local_header = local_header.ok_or_else(|| ClientErrorKind::UnknownBlock(format!("{}", request.block)))?;
let local_state_root = *local_header.state_root();
do_check_execution_proof(local_state_root.into(), executor, request, remote_proof)
}
let local_state_root = request.header.state_root();

/// Check remote execution proof using given state root.
fn do_check_execution_proof<Hash, E>(
local_state_root: Hash,
executor: &E,
request: &RemoteCallRequest<Hash>,
remote_proof: Vec<Vec<u8>>,
) -> ClientResult<CallResult>
where
Hash: ::std::fmt::Display + ::std::convert::AsRef<[u8]>,
E: CodeExecutor,
{
let mut changes = OverlayedChanges::default();
let (local_result, _) = execution_proof_check(
TrieH256::from_slice(local_state_root.as_ref()).into(),
Expand Down Expand Up @@ -156,8 +141,15 @@ mod tests {

// check remote execution proof locally
let local_executor = test_client::LocalExecutor::with_heap_pages(8);
do_check_execution_proof(remote_block_storage_root.into(), &local_executor, &RemoteCallRequest {
check_execution_proof(&local_executor, &RemoteCallRequest {
block: test_client::runtime::Hash::default(),
header: test_client::runtime::Header {
state_root: remote_block_storage_root.into(),
parent_hash: Default::default(),
number: 0,
extrinsics_root: Default::default(),
digest: Default::default(),
},
method: "authorities".into(),
call_data: vec![],
}, remote_execution_proof).unwrap();
Expand Down
Loading