feat(light client): fetch block body from remote (#2527)

* feat(on_demand): block body request

* fix(light block req): no justific + one block

* fix(bad rebase)

* feat(protocol): add messages for `remote_body`

* fix(on demand body): remove needless `take()`

* fix(network): remove messages for `on_demand_body`

* fix(grumbles): use `hash` in `remote_body_requests`

As long as we can't compute `ordered_trie_root(body)` just compare that request.header.hash() == response.header.hash()

* fix(grumbles): `hdr.ext_root == trie_root(body)`

* fix(grumbles): propogate `Err` in `fn body()`

* fix(grumbles): Vec<Block::Extrinsic>

* fix(grumbles): util_fn for `not_impl` in tests

* fix(on remote body): tests `fetch` and `on_demand`

* docs(resolve todos)
This commit is contained in:
Niklas Adolfsson
2019-05-18 02:05:00 +02:00
committed by DemiMarie-parity
parent 55937d1f08
commit 009898f309
6 changed files with 328 additions and 27 deletions
+14 -4
View File
@@ -30,7 +30,7 @@ use crate::blockchain::{Backend as BlockchainBackend, BlockStatus, Cache as Bloc
HeaderBackend as BlockchainHeaderBackend, Info as BlockchainInfo, ProvideCache};
use crate::cht;
use crate::error::{Error as ClientError, Result as ClientResult};
use crate::light::fetcher::{Fetcher, RemoteHeaderRequest};
use crate::light::fetcher::{Fetcher, RemoteBodyRequest, RemoteHeaderRequest};
/// Light client blockchain storage.
pub trait Storage<Block: BlockT>: AuxStore + BlockchainHeaderBackend<Block> {
@@ -144,9 +144,19 @@ impl<S, F, Block> BlockchainHeaderBackend<Block> for Blockchain<S, F> where Bloc
}
impl<S, F, Block> BlockchainBackend<Block> for Blockchain<S, F> where Block: BlockT, S: Storage<Block>, F: Fetcher<Block> {
fn body(&self, _id: BlockId<Block>) -> ClientResult<Option<Vec<Block::Extrinsic>>> {
// TODO: #1445 fetch from remote node
Ok(None)
fn body(&self, id: BlockId<Block>) -> ClientResult<Option<Vec<Block::Extrinsic>>> {
let header = match self.header(id)? {
Some(header) => header,
None => return Ok(None),
};
self.fetcher().upgrade().ok_or(ClientError::NotAvailableOnLightClient)?
.remote_body(RemoteBodyRequest {
header,
retry_count: None,
})
.into_future().wait()
.map(Some)
}
fn justification(&self, _id: BlockId<Block>) -> ClientResult<Option<Justification>> {
+120 -12
View File
@@ -22,8 +22,9 @@ use std::marker::PhantomData;
use futures::IntoFuture;
use hash_db::{HashDB, Hasher};
use parity_codec::Encode;
use primitives::{ChangesTrieConfiguration, convert_hash};
use runtime_primitives::traits::{As, Block as BlockT, Header as HeaderT, NumberFor};
use runtime_primitives::traits::{As, Block as BlockT, Header as HeaderT, Hash, HashFor, NumberFor};
use state_machine::{CodeExecutor, ChangesTrieRootsStorage, ChangesTrieAnchorBlockId,
TrieBackend, read_proof_check, key_changes_proof_check,
create_proof_check_backend_storage, read_child_proof_check};
@@ -124,17 +125,28 @@ pub struct ChangesProof<Header: HeaderT> {
pub roots_proof: Vec<Vec<u8>>,
}
/// Remote block body request
#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)]
pub struct RemoteBodyRequest<Header: HeaderT> {
/// Header of the requested block body
pub header: Header,
/// Number of times to retry request. None means that default RETRY_COUNT is used.
pub retry_count: Option<usize>,
}
/// 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 header future.
type RemoteHeaderResult: IntoFuture<Item=Block::Header, Error=ClientError>;
type RemoteHeaderResult: IntoFuture<Item = Block::Header, Error = ClientError>;
/// Remote storage read future.
type RemoteReadResult: IntoFuture<Item=Option<Vec<u8>>, Error=ClientError>;
type RemoteReadResult: IntoFuture<Item = Option<Vec<u8>>, Error = ClientError>;
/// Remote call result future.
type RemoteCallResult: IntoFuture<Item=Vec<u8>, Error=ClientError>;
type RemoteCallResult: IntoFuture<Item = Vec<u8>, Error = ClientError>;
/// Remote changes result future.
type RemoteChangesResult: IntoFuture<Item=Vec<(NumberFor<Block>, u32)>, Error=ClientError>;
type RemoteChangesResult: IntoFuture<Item = Vec<(NumberFor<Block>, u32)>, Error = ClientError>;
/// Remote block body result future.
type RemoteBodyResult: IntoFuture<Item = Vec<Block::Extrinsic>, Error = ClientError>;
/// Fetch remote header.
fn remote_header(&self, request: RemoteHeaderRequest<Block::Header>) -> Self::RemoteHeaderResult;
@@ -153,6 +165,8 @@ pub trait Fetcher<Block: BlockT>: Send + Sync {
/// Fetch remote changes ((block number, extrinsic index)) where given key has been changed
/// at a given blocks range.
fn remote_changes(&self, request: RemoteChangesRequest<Block::Header>) -> Self::RemoteChangesResult;
/// Fetch remote block body
fn remote_body(&self, request: RemoteBodyRequest<Block::Header>) -> Self::RemoteBodyResult;
}
/// Light client remote data checker.
@@ -191,6 +205,12 @@ pub trait FetchChecker<Block: BlockT>: Send + Sync {
request: &RemoteChangesRequest<Block::Header>,
proof: ChangesProof<Block::Header>
) -> ClientResult<Vec<(NumberFor<Block>, u32)>>;
/// Check remote body proof.
fn check_body_proof(
&self,
request: &RemoteBodyRequest<Block::Header>,
body: Vec<Block::Extrinsic>
) -> ClientResult<Vec<Block::Extrinsic>>;
}
/// Remote data checker.
@@ -396,6 +416,25 @@ impl<E, Block, H, S, F> FetchChecker<Block> for LightDataChecker<E, H, Block, S,
) -> ClientResult<Vec<(NumberFor<Block>, u32)>> {
self.check_changes_proof_with_cht_size(request, remote_proof, cht::SIZE)
}
fn check_body_proof(
&self,
request: &RemoteBodyRequest<Block::Header>,
body: Vec<Block::Extrinsic>
) -> ClientResult<Vec<Block::Extrinsic>> {
// TODO: #2621
let extrinsics_root = HashFor::<Block>::ordered_trie_root(body.iter().map(Encode::encode));
if *request.header.extrinsics_root() == extrinsics_root {
Ok(body)
} else {
Err(format!("RemoteBodyRequest: invalid extrinsics root expected: {} but got {}",
*request.header.extrinsics_root(),
extrinsics_root,
).into())
}
}
}
/// A view of BTreeMap<Number, Hash> as a changes trie roots storage.
@@ -438,7 +477,7 @@ pub mod tests {
use crate::error::Error as ClientError;
use test_client::{
self, TestClient, blockchain::HeaderBackend, AccountKeyring,
runtime::{self, Hash, Block, Header}
runtime::{self, Hash, Block, Header, Extrinsic}
};
use consensus::BlockOrigin;
@@ -446,7 +485,7 @@ pub mod tests {
use crate::light::fetcher::{Fetcher, FetchChecker, LightDataChecker,
RemoteCallRequest, RemoteHeaderRequest};
use crate::light::blockchain::tests::{DummyStorage, DummyBlockchain};
use primitives::{blake2_256, Blake2Hasher};
use primitives::{blake2_256, Blake2Hasher, H256};
use primitives::storage::{StorageKey, well_known_keys};
use runtime_primitives::generic::BlockId;
use state_machine::Backend;
@@ -454,22 +493,30 @@ pub mod tests {
pub type OkCallFetcher = Mutex<Vec<u8>>;
fn not_implemented_in_tests<T, E>() -> FutureResult<T, E>
where
E: std::convert::From<&'static str>,
{
err("Not implemented on test node".into())
}
impl Fetcher<Block> for OkCallFetcher {
type RemoteHeaderResult = FutureResult<Header, ClientError>;
type RemoteReadResult = FutureResult<Option<Vec<u8>>, ClientError>;
type RemoteCallResult = FutureResult<Vec<u8>, ClientError>;
type RemoteChangesResult = FutureResult<Vec<(NumberFor<Block>, u32)>, ClientError>;
type RemoteBodyResult = FutureResult<Vec<Extrinsic>, ClientError>;
fn remote_header(&self, _request: RemoteHeaderRequest<Header>) -> Self::RemoteHeaderResult {
err("Not implemented on test node".into())
not_implemented_in_tests()
}
fn remote_read(&self, _request: RemoteReadRequest<Header>) -> Self::RemoteReadResult {
err("Not implemented on test node".into())
not_implemented_in_tests()
}
fn remote_read_child(&self, _request: RemoteReadChildRequest<Header>) -> Self::RemoteReadResult {
err("Not implemented on test node".into())
not_implemented_in_tests()
}
fn remote_call(&self, _request: RemoteCallRequest<Header>) -> Self::RemoteCallResult {
@@ -477,11 +524,21 @@ pub mod tests {
}
fn remote_changes(&self, _request: RemoteChangesRequest<Header>) -> Self::RemoteChangesResult {
err("Not implemented on test node".into())
not_implemented_in_tests()
}
fn remote_body(&self, _request: RemoteBodyRequest<Header>) -> Self::RemoteBodyResult {
not_implemented_in_tests()
}
}
type TestChecker = LightDataChecker<executor::NativeExecutor<test_client::LocalExecutor>, Blake2Hasher, Block, DummyStorage, OkCallFetcher>;
type TestChecker = LightDataChecker<
executor::NativeExecutor<test_client::LocalExecutor>,
Blake2Hasher,
Block,
DummyStorage,
OkCallFetcher,
>;
fn prepare_for_read_proof_check() -> (TestChecker, Header, Vec<Vec<u8>>, u32) {
// prepare remote client
@@ -537,6 +594,14 @@ pub mod tests {
(local_checker, local_cht_root, remote_block_header, remote_header_proof)
}
fn header_with_computed_extrinsics_root(extrinsics: Vec<Extrinsic>) -> Header {
let extrinsics_root =
trie::ordered_trie_root::<Blake2Hasher, _, _>(extrinsics.iter().map(Encode::encode));
// only care about `extrinsics_root`
Header::new(0, extrinsics_root, H256::zero(), H256::zero(), Default::default())
}
#[test]
fn storage_read_proof_is_generated_and_checked() {
let (local_checker, remote_block_header, remote_read_proof, authorities_len) = prepare_for_read_proof_check();
@@ -776,4 +841,47 @@ pub mod tests {
);
assert!(local_checker.check_changes_tries_proof(4, &remote_proof.roots, vec![]).is_err());
}
#[test]
fn check_body_proof_faulty() {
let header = header_with_computed_extrinsics_root(
vec![Extrinsic::IncludeData(vec![1, 2, 3, 4])]
);
let block = Block::new(header.clone(), Vec::new());
let local_checker = TestChecker::new(
Arc::new(DummyBlockchain::new(DummyStorage::new())),
test_client::LocalExecutor::new(None)
);
let body_request = RemoteBodyRequest {
header: header.clone(),
retry_count: None,
};
assert!(
local_checker.check_body_proof(&body_request, block.extrinsics).is_err(),
"vec![1, 2, 3, 4] != vec![]"
);
}
#[test]
fn check_body_proof_of_same_data_should_succeed() {
let extrinsics = vec![Extrinsic::IncludeData(vec![1, 2, 3, 4, 5, 6, 7, 8, 255])];
let header = header_with_computed_extrinsics_root(extrinsics.clone());
let block = Block::new(header.clone(), extrinsics);
let local_checker = TestChecker::new(
Arc::new(DummyBlockchain::new(DummyStorage::new())),
test_client::LocalExecutor::new(None)
);
let body_request = RemoteBodyRequest {
header: header.clone(),
retry_count: None,
};
assert!(local_checker.check_body_proof(&body_request, block.extrinsics).is_ok());
}
}