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 all 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