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 1 commit
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: 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 @@ -233,6 +236,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
55 changes: 49 additions & 6 deletions substrate/client/src/light/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
//! Everything else is requested from full nodes on demand.

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

use primitives::AuthorityId;
use runtime_primitives::{bft::Justification, generic::BlockId};
Expand All @@ -29,7 +30,7 @@ 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> {
Expand Down Expand Up @@ -152,8 +153,13 @@ impl<Block, F> StateBackend for OnDemandState<Block, F> where Block: BlockT, F:
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>>> {
self.fetcher.upgrade().ok_or(ClientErrorKind::NotAvailableOnLightClient)?
.remote_read(RemoteReadRequest {
block: self.block,
key: key.to_vec(),
})
.into_future().wait()
}

fn for_keys_with_prefix<A: FnMut(&[u8])>(&self, _prefix: &[u8], _action: A) {
Expand All @@ -179,20 +185,57 @@ impl<Block, F> TryIntoStateTrieBackend for OnDemandState<Block, F> where Block:

#[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::{self, NativeExecutionDispatch};
use error::Error as ClientError;
use test_client::runtime::{Hash, Block};
use light::fetcher::{Fetcher, RemoteCallRequest};
use test_client::{self, runtime::{Hash, Block}};
use in_mem::{Blockchain as InMemoryBlockchain};
use light::{new_fetch_checker, new_light_blockchain};
use light::fetcher::{Fetcher, FetchChecker, LightDataChecker, 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_read(&self, _request: RemoteReadRequest<Hash>) -> Self::RemoteReadResult {
err("Not implemented on test node".into())
}

fn remote_call(&self, _request: RemoteCallRequest<Hash>) -> 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();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we wrap this line?


// '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_storage = InMemoryBlockchain::<Block>::new();
local_storage.insert(remote_block_hash, remote_block_header, None, None, true);
let local_executor = test_client::LocalExecutor::with_heap_pages(8, 8);
let local_checker: LightDataChecker<
InMemoryBlockchain<Block>,
executor::NativeExecutor<test_client::LocalExecutor>,
OkCallFetcher
> = new_fetch_checker(new_light_blockchain(local_storage), local_executor);
assert_eq!(local_checker.check_read_proof(&RemoteReadRequest {
block: remote_block_hash,
key: b":auth:len".to_vec(),
}, remote_read_proof).unwrap().unwrap()[0], authorities_len as u8);
}
}
51 changes: 46 additions & 5 deletions substrate/client/src/light/fetcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
use std::sync::Arc;
use futures::IntoFuture;

use runtime_primitives::traits::{Block as BlockT};
use state_machine::CodeExecutor;
use runtime_primitives::generic::BlockId;
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT};
use state_machine::{CodeExecutor, read_proof_check};

use call_executor::CallResult;
use error::{Error as ClientError, Result as ClientResult};
use error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult};
use light::blockchain::{Blockchain, Storage as BlockchainStorage};
use light::call_executor::check_execution_proof;
use blockchain::HeaderBackend as BlockchainHeaderBackend;

/// Remote call request.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
Expand All @@ -38,20 +40,43 @@ pub struct RemoteCallRequest<Hash: ::std::fmt::Display> {
pub call_data: Vec<u8>,
}

/// Remote storage read request.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct RemoteReadRequest<Hash: ::std::fmt::Display> {
/// Read at state of given block.
pub block: Hash,
/// Storage key to read.
pub key: Vec<u8>,
}

/// Light client data fetcher. Implementations of this trait must check if remote data
/// is correct (see FetchedDataChecker) and return already checked data.
pub trait Fetcher<Block: BlockT>: Send + Sync {
/// Remote storage read future.
type RemoteReadResult: IntoFuture<Item=Option<Vec<u8>>, Error=ClientError>;
/// Remote call result future.
type RemoteCallResult: IntoFuture<Item=CallResult, Error=ClientError>;

/// Fetch remote storage value.
fn remote_read(&self, request: RemoteReadRequest<Block::Hash>) -> Self::RemoteReadResult;
/// Fetch remote call result.
fn remote_call(&self, request: RemoteCallRequest<Block::Hash>) -> Self::RemoteCallResult;
}

/// Light client remote data checker.
pub trait FetchChecker<Block: BlockT>: Send + Sync {
/// Check remote storage read proof.
fn check_read_proof(
&self,
request: &RemoteReadRequest<Block::Hash>,
remote_proof: Vec<Vec<u8>>
) -> ClientResult<Option<Vec<u8>>>;
/// Check remote method execution proof.
fn check_execution_proof(&self, request: &RemoteCallRequest<Block::Hash>, remote_proof: Vec<Vec<u8>>) -> ClientResult<CallResult>;
fn check_execution_proof(
&self,
request: &RemoteCallRequest<Block::Hash>,
remote_proof: Vec<Vec<u8>>
) -> ClientResult<CallResult>;
}

/// Remote data checker.
Expand All @@ -78,11 +103,27 @@ impl<S, E, F> LightDataChecker<S, E, F> {
impl<S, E, F, Block> FetchChecker<Block> for LightDataChecker<S, E, F>
where
Block: BlockT,
Block::Hash: Into<[u8; 32]>,
S: BlockchainStorage<Block>,
E: CodeExecutor,
F: Fetcher<Block>,
{
fn check_execution_proof(&self, request: &RemoteCallRequest<Block::Hash>, remote_proof: Vec<Vec<u8>>) -> ClientResult<CallResult> {
fn check_read_proof(
&self,
request: &RemoteReadRequest<Block::Hash>,
remote_proof: Vec<Vec<u8>>
) -> ClientResult<Option<Vec<u8>>> {
let local_header = self.blockchain.header(BlockId::Hash(request.block))?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this happens well beyond the point where the storage request is actually issued, could the header be pruned if it takes a long time to serve the request or if the header was on the verge of being pruned when the request was issued?

let local_header = local_header.ok_or_else(|| ClientErrorKind::UnknownBlock(format!("{}", request.block)))?;
let local_state_root = *local_header.state_root();
read_proof_check(local_state_root.into(), remote_proof, &request.key).map_err(Into::into)
}

fn check_execution_proof(
&self,
request: &RemoteCallRequest<Block::Hash>,
remote_proof: Vec<Vec<u8>>
) -> ClientResult<CallResult> {
check_execution_proof(&*self.blockchain, &self.executor, request, remote_proof)
}
}
1 change: 1 addition & 0 deletions substrate/network/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ error-chain = "0.12"
bitflags = "1.0"
futures = "0.1.17"
linked-hash-map = "0.5"
rustc-hex = "1.0"
ethcore-io = { git = "https://github.com/paritytech/parity.git" }
ed25519 = { path = "../../substrate/ed25519" }
substrate-primitives = { path = "../../substrate/primitives" }
Expand Down
7 changes: 7 additions & 0 deletions substrate/network/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ pub trait Client<Block: BlockT>: Send + Sync {
/// Get block justification.
fn justification(&self, id: &BlockId<Block>) -> Result<Option<Justification<Block::Hash>>, Error>;

/// Get storage read execution proof.
fn read_proof(&self, block: &Block::Hash, key: &[u8]) -> Result<Vec<Vec<u8>>, Error>;

/// Get method execution proof.
fn execution_proof(&self, block: &Block::Hash, method: &str, data: &[u8]) -> Result<(Vec<u8>, Vec<Vec<u8>>), Error>;
}
Expand Down Expand Up @@ -84,6 +87,10 @@ impl<B, E, Block> Client<Block> for SubstrateClient<B, E, Block> where
(self as &SubstrateClient<B, E, Block>).justification(id)
}

fn read_proof(&self, block: &Block::Hash, key: &[u8]) -> Result<Vec<Vec<u8>>, Error> {
(self as &SubstrateClient<B, E, Block>).read_proof(&BlockId::Hash(block.clone()), key)
}

fn execution_proof(&self, block: &Block::Hash, method: &str, data: &[u8]) -> Result<(Vec<u8>, Vec<Vec<u8>>), Error> {
(self as &SubstrateClient<B, E, Block>).execution_proof(&BlockId::Hash(block.clone()), method, data)
}
Expand Down
3 changes: 2 additions & 1 deletion substrate/network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ extern crate substrate_network_libp2p as network_libp2p;
extern crate substrate_codec as codec;
extern crate futures;
extern crate ed25519;
extern crate rustc_hex;
#[macro_use] extern crate log;
#[macro_use] extern crate bitflags;
#[macro_use] extern crate error_chain;
Expand Down Expand Up @@ -63,4 +64,4 @@ pub use network_libp2p::{NonReservedPeerMode, NetworkConfiguration, NodeIndex, P
pub use message::{generic as generic_message, RequestId, BftMessage, LocalizedBftMessage, ConsensusVote, SignedConsensusVote, SignedConsensusMessage, SignedConsensusProposal, Status as StatusMessage};
pub use error::Error;
pub use config::{Roles, ProtocolConfig};
pub use on_demand::{OnDemand, OnDemandService, RemoteCallResponse};
pub use on_demand::{OnDemand, OnDemandService, RemoteResponse};
81 changes: 77 additions & 4 deletions substrate/network/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@

use runtime_primitives::traits::{Block as BlockT, Header as HeaderT};
use codec::{Encode, Decode, Input, Output};
pub use self::generic::{BlockAnnounce, RemoteCallRequest, ConsensusVote, SignedConsensusVote, FromBlock};
pub use self::generic::{
BlockAnnounce, RemoteCallRequest, RemoteReadRequest,
ConsensusVote, SignedConsensusVote, FromBlock
};

/// A unique ID of a request.
pub type RequestId = u64;
Expand Down Expand Up @@ -135,16 +138,43 @@ impl Decode for RemoteCallResponse {
})
}
}


#[derive(Debug, PartialEq, Eq, Clone)]
/// Remote read response.
pub struct RemoteReadResponse {
/// Id of a request this response was made for.
pub id: RequestId,
/// Read proof.
pub proof: Vec<Vec<u8>>,
}

impl Encode for RemoteReadResponse {
fn encode_to<T: Output>(&self, dest: &mut T) {
dest.push(&self.id);
dest.push(&self.proof);
}
}

impl Decode for RemoteReadResponse {
fn decode<I: Input>(input: &mut I) -> Option<Self> {
Some(RemoteReadResponse {
id: Decode::decode(input)?,
proof: Decode::decode(input)?,
})
}
}

/// Generic types.
pub mod generic {
use primitives::AuthorityId;
use codec::{Decode, Encode, Input, Output};
use runtime_primitives::bft::Justification;
use ed25519;
use service::Roles;
use super::{BlockAttributes, RemoteCallResponse, RequestId, Transactions, Direction};

use super::{
BlockAttributes, RemoteCallResponse, RemoteReadResponse,
RequestId, Transactions, Direction
};

/// Block data sent in the response.
#[derive(Debug, PartialEq, Eq, Clone)]
Expand Down Expand Up @@ -446,6 +476,10 @@ pub mod generic {
RemoteCallRequest(RemoteCallRequest<Hash>),
/// Remote method call response.
RemoteCallResponse(RemoteCallResponse),
/// Remote storage read request.
RemoteReadRequest(RemoteReadRequest<Hash>),
/// Remote storage read response.
RemoteReadResponse(RemoteReadResponse),
/// Chain-specific message
ChainSpecific(Vec<u8>),
}
Expand Down Expand Up @@ -487,6 +521,14 @@ pub mod generic {
dest.push_byte(7);
dest.push(m);
}
Message::RemoteReadRequest(ref m) => {
dest.push_byte(8);
dest.push(m);
}
Message::RemoteReadResponse(ref m) => {
dest.push_byte(9);
dest.push(m);
}
Message::ChainSpecific(ref m) => {
dest.push_byte(255);
dest.push(m);
Expand All @@ -508,6 +550,8 @@ pub mod generic {
5 => Some(Message::BftMessage(Decode::decode(input)?)),
6 => Some(Message::RemoteCallRequest(Decode::decode(input)?)),
7 => Some(Message::RemoteCallResponse(Decode::decode(input)?)),
8 => Some(Message::RemoteReadRequest(Decode::decode(input)?)),
9 => Some(Message::RemoteReadResponse(Decode::decode(input)?)),
255 => Some(Message::ChainSpecific(Decode::decode(input)?)),
_ => None,
}
Expand Down Expand Up @@ -678,4 +722,33 @@ pub mod generic {
})
}
}

#[derive(Debug, PartialEq, Eq, Clone)]
/// Remote storage read request.
pub struct RemoteReadRequest<H> {
/// Unique request id.
pub id: RequestId,
/// Block at which to perform call.
pub block: H,
/// Storage key.
pub key: Vec<u8>,
}

impl<Hash: Encode> Encode for RemoteReadRequest<Hash> {
fn encode_to<T: Output>(&self, dest: &mut T) {
dest.push(&self.id);
dest.push(&self.block);
dest.push(&self.key);
}
}

impl<Hash: Decode> Decode for RemoteReadRequest<Hash> {
fn decode<I: Input>(input: &mut I) -> Option<Self> {
Some(RemoteReadRequest {
id: Decode::decode(input)?,
block: Decode::decode(input)?,
key: Decode::decode(input)?,
})
}
}
}
Loading