From c346af29e00ed893583c4406145db8fff453a8cf Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky <svyatonik@gmail.com> Date: Sun, 12 Aug 2018 13:48:40 +0300 Subject: [PATCH] Fetching storage proofs by light client (#252) * storage proofs * fixed grumbles * Update lib.rs --- substrate/Cargo.lock | 1 + substrate/substrate/client/src/blockchain.rs | 13 +- substrate/substrate/client/src/client.rs | 13 +- .../substrate/client/src/light/backend.rs | 111 ++++++++++++---- .../client/src/light/call_executor.rs | 38 +++--- .../substrate/client/src/light/fetcher.rs | 79 ++++++++---- substrate/substrate/client/src/light/mod.rs | 9 +- substrate/substrate/network/Cargo.toml | 1 + substrate/substrate/network/src/chain.rs | 7 ++ substrate/substrate/network/src/lib.rs | 3 +- substrate/substrate/network/src/message.rs | 35 +++++- substrate/substrate/network/src/on_demand.rs | 119 ++++++++++++++---- substrate/substrate/network/src/protocol.rs | 23 ++++ substrate/substrate/service/src/components.rs | 7 +- substrate/substrate/state-machine/src/lib.rs | 38 ++++++ .../state-machine/src/trie_backend.rs | 3 + 16 files changed, 388 insertions(+), 112 deletions(-) diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index 697bb822b50..d376a322e08 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -2841,6 +2841,7 @@ dependencies = [ "linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-client 0.1.0", "substrate-codec 0.1.0", "substrate-codec-derive 0.1.0", diff --git a/substrate/substrate/client/src/blockchain.rs b/substrate/substrate/client/src/blockchain.rs index 333bc8f72e1..71b054cbb30 100644 --- a/substrate/substrate/client/src/blockchain.rs +++ b/substrate/substrate/client/src/blockchain.rs @@ -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. diff --git a/substrate/substrate/client/src/client.rs b/substrate/substrate/client/src/client.rs index 8c13b05ef15..4ae139c7294 100644 --- a/substrate/substrate/client/src/client.rs +++ b/substrate/substrate/client/src/client.rs @@ -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}; @@ -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. /// diff --git a/substrate/substrate/client/src/light/backend.rs b/substrate/substrate/client/src/light/backend.rs index e3b8b42c89c..03b188b5f68 100644 --- a/substrate/substrate/client/src/light/backend.rs +++ b/substrate/substrate/client/src/light/backend.rs @@ -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}; @@ -29,7 +31,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> { @@ -37,17 +39,19 @@ pub struct Backend<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> { @@ -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 { @@ -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), }) } @@ -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 @@ -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) { @@ -175,7 +197,7 @@ 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 } @@ -183,20 +205,55 @@ 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::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); + } } diff --git a/substrate/substrate/client/src/light/call_executor.rs b/substrate/substrate/client/src/light/call_executor.rs index da196b54b7f..13355f5d7cc 100644 --- a/substrate/substrate/client/src/light/call_executor.rs +++ b/substrate/substrate/client/src/light/call_executor.rs @@ -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() @@ -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(), @@ -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(); diff --git a/substrate/substrate/client/src/light/fetcher.rs b/substrate/substrate/client/src/light/fetcher.rs index 90622bf6fc0..07c26a471de 100644 --- a/substrate/substrate/client/src/light/fetcher.rs +++ b/substrate/substrate/client/src/light/fetcher.rs @@ -16,73 +16,106 @@ //! Light client data fetcher. Fetches requested data from remote full nodes. -use std::sync::Arc; use futures::IntoFuture; -use runtime_primitives::traits::{Block as BlockT}; -use state_machine::CodeExecutor; +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 light::blockchain::{Blockchain, Storage as BlockchainStorage}; use light::call_executor::check_execution_proof; /// Remote call request. #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct RemoteCallRequest<Hash: ::std::fmt::Display> { +pub struct RemoteCallRequest<Header: HeaderT> { /// Call at state of given block. - pub block: Hash, + pub block: Header::Hash, + /// Head of block at which call is perormed. + pub header: Header, /// Method to call. pub method: String, /// Call data. pub call_data: Vec<u8>, } +/// Remote storage read request. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct RemoteReadRequest<Header: HeaderT> { + /// Read at state of given block. + pub block: Header::Hash, + /// Head of block at which read is perormed. + pub header: Header, + /// 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::Header>) -> Self::RemoteReadResult; /// Fetch remote call result. - fn remote_call(&self, request: RemoteCallRequest<Block::Hash>) -> Self::RemoteCallResult; + fn remote_call(&self, request: RemoteCallRequest<Block::Header>) -> Self::RemoteCallResult; } /// Light client remote data checker. +/// +/// Implementations of this trait should not use any blockchain data except that is +/// passed to its methods. pub trait FetchChecker<Block: BlockT>: Send + Sync { + /// Check remote storage read proof. + fn check_read_proof( + &self, + request: &RemoteReadRequest<Block::Header>, + 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::Header>, + remote_proof: Vec<Vec<u8>> + ) -> ClientResult<CallResult>; } /// Remote data checker. -pub struct LightDataChecker<S, E, F> { - blockchain: Arc<Blockchain<S, F>>, +pub struct LightDataChecker<E> { executor: E, } -impl<S, E, F> LightDataChecker<S, E, F> { +impl<E> LightDataChecker<E> { /// Create new light data checker. - pub fn new(blockchain: Arc<Blockchain<S, F>>, executor: E) -> Self { + pub fn new(executor: E) -> Self { Self { - blockchain, executor, } } - - /// Get blockchain reference. - pub fn blockchain(&self) -> &Arc<Blockchain<S, F>> { - &self.blockchain - } } -impl<S, E, F, Block> FetchChecker<Block> for LightDataChecker<S, E, F> +impl<E, Block> FetchChecker<Block> for LightDataChecker<E> where Block: BlockT, - S: BlockchainStorage<Block>, + Block::Hash: Into<[u8; 32]>, E: CodeExecutor, - F: Fetcher<Block>, { - 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) + fn check_read_proof( + &self, + request: &RemoteReadRequest<Block::Header>, + remote_proof: Vec<Vec<u8>> + ) -> ClientResult<Option<Vec<u8>>> { + let local_state_root = request.header.state_root().clone(); + read_proof_check(local_state_root.into(), remote_proof, &request.key).map_err(Into::into) + } + + fn check_execution_proof( + &self, + request: &RemoteCallRequest<Block::Header>, + remote_proof: Vec<Vec<u8>> + ) -> ClientResult<CallResult> { + check_execution_proof(&self.executor, request, remote_proof) } } diff --git a/substrate/substrate/client/src/light/mod.rs b/substrate/substrate/client/src/light/mod.rs index 40b54cdd634..57bd8ce45be 100644 --- a/substrate/substrate/client/src/light/mod.rs +++ b/substrate/substrate/client/src/light/mod.rs @@ -62,14 +62,11 @@ pub fn new_light<B, S, F, GS>( } /// Create an instance of fetch data checker. -pub fn new_fetch_checker<B, S, E, F>( - blockchain: Arc<Blockchain<S, F>>, +pub fn new_fetch_checker<E>( executor: E, -) -> LightDataChecker<S, E, F> +) -> LightDataChecker<E> where - B: BlockT, - S: BlockchainStorage<B>, E: CodeExecutor, { - LightDataChecker::new(blockchain, executor) + LightDataChecker::new(executor) } diff --git a/substrate/substrate/network/Cargo.toml b/substrate/substrate/network/Cargo.toml index dcc08675768..17b16e74d35 100644 --- a/substrate/substrate/network/Cargo.toml +++ b/substrate/substrate/network/Cargo.toml @@ -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" } diff --git a/substrate/substrate/network/src/chain.rs b/substrate/substrate/network/src/chain.rs index f20b7e2b5e6..d698ab0900a 100644 --- a/substrate/substrate/network/src/chain.rs +++ b/substrate/substrate/network/src/chain.rs @@ -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>; } @@ -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) } diff --git a/substrate/substrate/network/src/lib.rs b/substrate/substrate/network/src/lib.rs index b1130e89a8f..70926a1cdbd 100644 --- a/substrate/substrate/network/src/lib.rs +++ b/substrate/substrate/network/src/lib.rs @@ -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; @@ -64,4 +65,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}; diff --git a/substrate/substrate/network/src/message.rs b/substrate/substrate/network/src/message.rs index 4dd58965d18..dc5865e8e7f 100644 --- a/substrate/substrate/network/src/message.rs +++ b/substrate/substrate/network/src/message.rs @@ -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; @@ -132,14 +135,25 @@ pub struct RemoteCallResponse { pub proof: Vec<Vec<u8>>, } +#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] +/// 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>>, +} + /// Generic types. pub mod generic { use primitives::AuthorityId; 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, Encode, Decode)] @@ -252,6 +266,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 #[codec(index = "255")] ChainSpecific(Vec<u8>), @@ -319,4 +337,15 @@ pub mod generic { /// Call data. pub data: Vec<u8>, } + + #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] + /// 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>, + } } diff --git a/substrate/substrate/network/src/on_demand.rs b/substrate/substrate/network/src/on_demand.rs index e147caadfce..223d94206cd 100644 --- a/substrate/substrate/network/src/on_demand.rs +++ b/substrate/substrate/network/src/on_demand.rs @@ -25,7 +25,7 @@ use linked_hash_map::LinkedHashMap; use linked_hash_map::Entry; use parking_lot::Mutex; use client; -use client::light::fetcher::{Fetcher, FetchChecker, RemoteCallRequest}; +use client::light::fetcher::{Fetcher, FetchChecker, RemoteCallRequest, RemoteReadRequest}; use io::SyncIo; use message; use network_libp2p::{Severity, NodeIndex}; @@ -46,6 +46,9 @@ pub trait OnDemandService<Block: BlockT>: Send + Sync { /// Maintain peers requests. fn maintain_peers(&self, io: &mut SyncIo); + /// When read response is received from remote node. + fn on_remote_read_response(&self, io: &mut SyncIo, peer: NodeIndex, response: message::RemoteReadResponse); + /// When call response is received from remote node. fn on_remote_call_response(&self, io: &mut SyncIo, peer: NodeIndex, response: message::RemoteCallResponse); } @@ -57,8 +60,8 @@ pub struct OnDemand<B: BlockT, E: service::ExecuteInContext<B>> { } /// On-demand remote call response. -pub struct RemoteCallResponse { - receiver: Receiver<Result<client::CallResult, client::error::Error>>, +pub struct RemoteResponse<T> { + receiver: Receiver<Result<T, client::error::Error>>, } #[derive(Default)] @@ -77,16 +80,18 @@ struct Request<Block: BlockT> { } enum RequestData<Block: BlockT> { - RemoteCall(RemoteCallRequest<Block::Hash>, Sender<Result<client::CallResult, client::error::Error>>), + RemoteRead(RemoteReadRequest<Block::Header>, Sender<Result<Option<Vec<u8>>, client::error::Error>>), + RemoteCall(RemoteCallRequest<Block::Header>, Sender<Result<client::CallResult, client::error::Error>>), } enum Accept<Block: BlockT> { Ok, CheckFailed(client::error::Error, RequestData<Block>), + Unexpected(RequestData<Block>), } -impl Future for RemoteCallResponse { - type Item = client::CallResult; +impl<T> Future for RemoteResponse<T> { + type Item = T; type Error = client::error::Error; fn poll(&mut self) -> Poll<Self::Item, Self::Error> { @@ -150,6 +155,10 @@ impl<B: BlockT, E> OnDemand<B, E> where core.remove_peer(peer); Some(retry_request_data) }, + Accept::Unexpected(retry_request_data) => { + trace!(target: "sync", "Unexpected response to remote {} from peer {}", rtype, peer); + Some(retry_request_data) + }, }; if let Some(request_data) = retry_request_data { @@ -189,6 +198,20 @@ impl<B, E> OnDemandService<B> for OnDemand<B, E> where core.dispatch(); } + fn on_remote_read_response(&self, io: &mut SyncIo, peer: NodeIndex, response: message::RemoteReadResponse) { + self.accept_response("read", io, peer, response.id, |request| match request.data { + RequestData::RemoteRead(request, sender) => match self.checker.check_read_proof(&request, response.proof) { + Ok(response) => { + // we do not bother if receiver has been dropped already + let _ = sender.send(Ok(response)); + Accept::Ok + }, + Err(error) => Accept::CheckFailed(error, RequestData::RemoteRead(request, sender)), + }, + data @ _ => Accept::Unexpected(data), + }) + } + fn on_remote_call_response(&self, io: &mut SyncIo, peer: NodeIndex, response: message::RemoteCallResponse) { self.accept_response("call", io, peer, response.id, |request| match request.data { RequestData::RemoteCall(request, sender) => match self.checker.check_execution_proof(&request, response.proof) { @@ -199,6 +222,7 @@ impl<B, E> OnDemandService<B> for OnDemand<B, E> where }, Err(error) => Accept::CheckFailed(error, RequestData::RemoteCall(request, sender)), }, + data @ _ => Accept::Unexpected(data), }) } } @@ -208,12 +232,19 @@ impl<B, E> Fetcher<B> for OnDemand<B, E> where E: service::ExecuteInContext<B>, B::Header: HeaderT, { - type RemoteCallResult = RemoteCallResponse; + type RemoteReadResult = RemoteResponse<Option<Vec<u8>>>; + type RemoteCallResult = RemoteResponse<client::CallResult>; + + fn remote_read(&self, request: RemoteReadRequest<B::Header>) -> Self::RemoteReadResult { + let (sender, receiver) = channel(); + self.schedule_request(RequestData::RemoteRead(request, sender), + RemoteResponse { receiver }) + } - fn remote_call(&self, request: RemoteCallRequest<B::Hash>) -> Self::RemoteCallResult { + fn remote_call(&self, request: RemoteCallRequest<B::Header>) -> Self::RemoteCallResult { let (sender, receiver) = channel(); self.schedule_request(RequestData::RemoteCall(request, sender), - RemoteCallResponse { receiver }) + RemoteResponse { receiver }) } } @@ -301,12 +332,19 @@ impl<B, E> OnDemandCore<B, E> where impl<Block: BlockT> Request<Block> { pub fn message(&self) -> message::Message<Block> { match self.data { - RequestData::RemoteCall(ref data, _) => message::generic::Message::RemoteCallRequest(message::RemoteCallRequest { - id: self.id, - block: data.block, - method: data.method.clone(), - data: data.call_data.clone(), - }), + RequestData::RemoteRead(ref data, _) => message::generic::Message::RemoteReadRequest( + message::RemoteReadRequest { + id: self.id, + block: data.block, + key: data.key.clone(), + }), + RequestData::RemoteCall(ref data, _) => message::generic::Message::RemoteCallRequest( + message::RemoteCallRequest { + id: self.id, + block: data.block, + method: data.method.clone(), + data: data.call_data.clone(), + }), } } } @@ -319,13 +357,13 @@ pub mod tests { use futures::Future; use parking_lot::RwLock; use client; - use client::light::fetcher::{Fetcher, FetchChecker, RemoteCallRequest}; + use client::light::fetcher::{Fetcher, FetchChecker, RemoteCallRequest, RemoteReadRequest}; use message; use network_libp2p::NodeIndex; use service::{Roles, ExecuteInContext}; use test::TestIo; use super::{REQUEST_TIMEOUT, OnDemand, OnDemandService}; - use test_client::runtime::{Block, Hash}; + use test_client::runtime::{Block, Header}; pub struct DummyExecutor; struct DummyFetchChecker { ok: bool } @@ -335,7 +373,14 @@ pub mod tests { } impl FetchChecker<Block> for DummyFetchChecker { - fn check_execution_proof(&self, _request: &RemoteCallRequest<Hash>, _remote_proof: Vec<Vec<u8>>) -> client::error::Result<client::CallResult> { + fn check_read_proof(&self, _request: &RemoteReadRequest<Header>, _remote_proof: Vec<Vec<u8>>) -> client::error::Result<Option<Vec<u8>>> { + match self.ok { + true => Ok(Some(vec![42])), + false => Err(client::error::ErrorKind::Backend("Test error".into()).into()), + } + } + + fn check_execution_proof(&self, _request: &RemoteCallRequest<Header>, _remote_proof: Vec<Vec<u8>>) -> client::error::Result<client::CallResult> { match self.ok { true => Ok(client::CallResult { return_data: vec![42], @@ -365,6 +410,16 @@ pub mod tests { }); } + fn dummy_header() -> Header { + Header { + parent_hash: Default::default(), + number: 0, + state_root: Default::default(), + extrinsics_root: Default::default(), + digest: Default::default(), + } + } + #[test] fn knows_about_peers_roles() { let (_, on_demand) = dummy(true); @@ -394,7 +449,12 @@ pub mod tests { assert_eq!(vec![0, 1], on_demand.core.lock().idle_peers.iter().cloned().collect::<Vec<_>>()); assert!(on_demand.core.lock().active_peers.is_empty()); - on_demand.remote_call(RemoteCallRequest { block: Default::default(), method: "test".into(), call_data: vec![] }); + on_demand.remote_call(RemoteCallRequest { + block: Default::default(), + header: dummy_header(), + method: "test".into(), + call_data: vec![], + }); assert_eq!(vec![1], on_demand.core.lock().idle_peers.iter().cloned().collect::<Vec<_>>()); assert_eq!(vec![0], on_demand.core.lock().active_peers.keys().cloned().collect::<Vec<_>>()); @@ -412,7 +472,12 @@ pub mod tests { let mut network = TestIo::new(&queue, None); on_demand.on_connect(0, Roles::FULL); - on_demand.remote_call(RemoteCallRequest { block: Default::default(), method: "test".into(), call_data: vec![] }); + on_demand.remote_call(RemoteCallRequest { + block: Default::default(), + header: dummy_header(), + method: "test".into(), + call_data: vec![], + }); receive_call_response(&*on_demand, &mut network, 0, 1); assert!(network.to_disconnect.contains(&0)); assert_eq!(on_demand.core.lock().pending_requests.len(), 1); @@ -423,7 +488,12 @@ pub mod tests { let (_x, on_demand) = dummy(false); let queue = RwLock::new(VecDeque::new()); let mut network = TestIo::new(&queue, None); - on_demand.remote_call(RemoteCallRequest { block: Default::default(), method: "test".into(), call_data: vec![] }); + on_demand.remote_call(RemoteCallRequest { + block: Default::default(), + header: dummy_header(), + method: "test".into(), + call_data: vec![], + }); on_demand.on_connect(0, Roles::FULL); receive_call_response(&*on_demand, &mut network, 0, 0); @@ -449,7 +519,12 @@ pub mod tests { let mut network = TestIo::new(&queue, None); on_demand.on_connect(0, Roles::FULL); - let response = on_demand.remote_call(RemoteCallRequest { block: Default::default(), method: "test".into(), call_data: vec![] }); + let response = on_demand.remote_call(RemoteCallRequest { + block: Default::default(), + header: dummy_header(), + method: "test".into(), + call_data: vec![], + }); let thread = ::std::thread::spawn(move || { let result = response.wait().unwrap(); assert_eq!(result.return_data, vec![42]); diff --git a/substrate/substrate/network/src/protocol.rs b/substrate/substrate/network/src/protocol.rs index 0a409629b7d..10b08a43668 100644 --- a/substrate/substrate/network/src/protocol.rs +++ b/substrate/substrate/network/src/protocol.rs @@ -19,6 +19,7 @@ use std::{mem, cmp}; use std::sync::Arc; use std::time; use parking_lot::RwLock; +use rustc_hex::ToHex; use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Hash, HashFor, As}; use runtime_primitives::generic::BlockId; use network_libp2p::{NodeIndex, Severity}; @@ -268,6 +269,8 @@ impl<B: BlockT, S: Specialization<B>> Protocol<B, S> { GenericMessage::Transactions(m) => self.on_extrinsics(io, who, m), GenericMessage::RemoteCallRequest(request) => self.on_remote_call_request(io, who, request), GenericMessage::RemoteCallResponse(response) => self.on_remote_call_response(io, who, response), + GenericMessage::RemoteReadRequest(request) => self.on_remote_read_request(io, who, request), + GenericMessage::RemoteReadResponse(response) => self.on_remote_read_response(io, who, response), other => self.specialization.write().on_message(&mut ProtocolContext::new(&self.context_data, io), who, other), } } @@ -602,6 +605,26 @@ impl<B: BlockT, S: Specialization<B>> Protocol<B, S> { self.on_demand.as_ref().map(|s| s.on_remote_call_response(io, who, response)); } + fn on_remote_read_request(&self, io: &mut SyncIo, who: NodeIndex, request: message::RemoteReadRequest<B::Hash>) { + trace!(target: "sync", "Remote read request {} from {} ({} at {})", + request.id, who, request.key.to_hex(), request.block); + let proof = match self.context_data.chain.read_proof(&request.block, &request.key) { + Ok(proof) => proof, + Err(error) => { + trace!(target: "sync", "Remote read request {} from {} ({} at {}) failed with: {}", + request.id, who, request.key.to_hex(), request.block, error); + Default::default() + }, + }; + self.send_message(io, who, GenericMessage::RemoteReadResponse(message::RemoteReadResponse { + id: request.id, proof, + })); + } + fn on_remote_read_response(&self, io: &mut SyncIo, who: NodeIndex, response: message::RemoteReadResponse) { + trace!(target: "sync", "Remote read response {} from {}", response.id, who); + self.on_demand.as_ref().map(|s| s.on_remote_read_response(io, who, response)); + } + /// Execute a closure with access to a network context and specialization. pub fn with_spec<F, U>(&self, io: &mut SyncIo, f: F) -> U where F: FnOnce(&mut S, &mut Context<B>) -> U diff --git a/substrate/substrate/service/src/components.rs b/substrate/substrate/service/src/components.rs index fd96dc7c316..a6c83db8882 100644 --- a/substrate/substrate/service/src/components.rs +++ b/substrate/substrate/service/src/components.rs @@ -213,7 +213,10 @@ pub struct LightComponents<Factory: ServiceFactory> { _factory: PhantomData<Factory>, } -impl<Factory: ServiceFactory> Components for LightComponents<Factory> { +impl<Factory: ServiceFactory> Components for LightComponents<Factory> + where + <<Factory as ServiceFactory>::Block as BlockT>::Hash: Into<[u8; 32]>, +{ type Factory = Factory; type Executor = LightExecutor<Factory>; type Backend = LightBackend<Factory>; @@ -236,7 +239,7 @@ impl<Factory: ServiceFactory> Components for LightComponents<Factory> { }; 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(light_blockchain.clone(), executor)); + let fetch_checker = Arc::new(client::light::new_fetch_checker(executor)); 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)?; diff --git a/substrate/substrate/state-machine/src/lib.rs b/substrate/substrate/state-machine/src/lib.rs index 7c9ec577567..9b0afc37bb0 100644 --- a/substrate/substrate/state-machine/src/lib.rs +++ b/substrate/substrate/state-machine/src/lib.rs @@ -406,6 +406,30 @@ pub fn execution_proof_check<Exec: CodeExecutor>( execute(&backend, overlay, exec, method, call_data, ExecutionStrategy::NativeWhenPossible) } +/// Generate storage read proof. +pub fn prove_read<B: TryIntoTrieBackend>( + backend: B, + key: &[u8], +) -> Result<(Option<Vec<u8>>, Vec<Vec<u8>>), Box<Error>> +{ + let trie_backend = backend.try_into_trie_backend() + .ok_or_else(|| Box::new(ExecutionError::UnableToGenerateProof) as Box<Error>)?; + let proving_backend = proving_backend::ProvingBackend::new(trie_backend); + let result = proving_backend.storage(key).map_err(|e| Box::new(e) as Box<Error>)?; + Ok((result, proving_backend.extract_proof())) +} + +/// Check storage read proof, generated by `prove_read` call. +pub fn read_proof_check( + root: [u8; 32], + proof: Vec<Vec<u8>>, + key: &[u8], +) -> Result<Option<Vec<u8>>, Box<Error>> +{ + let backend = proving_backend::create_proof_check_backend(root.into(), proof)?; + backend.storage(key).map_err(|e| Box::new(e) as Box<Error>) +} + #[cfg(test)] mod tests { use super::*; @@ -599,4 +623,18 @@ mod tests { ], ); } + + #[test] + fn prove_read_and_proof_check_works() { + // fetch read proof from 'remote' full node + let remote_backend = trie_backend::tests::test_trie(); + let remote_root = remote_backend.storage_root(::std::iter::empty()).0; + let remote_proof = prove_read(remote_backend, b"value2").unwrap().1; + // check proof locally + let local_result1 = read_proof_check(remote_root, remote_proof.clone(), b"value2").unwrap(); + let local_result2 = read_proof_check(remote_root, remote_proof.clone(), &[0xff]).is_ok(); + // check that results are correct + assert_eq!(local_result1, Some(vec![24])); + assert_eq!(local_result2, false); + } } diff --git a/substrate/substrate/state-machine/src/trie_backend.rs b/substrate/substrate/state-machine/src/trie_backend.rs index 098d0f6df33..629dd3ddb3a 100644 --- a/substrate/substrate/state-machine/src/trie_backend.rs +++ b/substrate/substrate/state-machine/src/trie_backend.rs @@ -282,6 +282,9 @@ pub mod tests { trie.insert(b"value1", &[42]).unwrap(); trie.insert(b"value2", &[24]).unwrap(); trie.insert(b":code", b"return 42").unwrap(); + for i in 128u8..255u8 { + trie.insert(&[i], &[i]).unwrap(); + } } (mdb, root) } -- GitLab