Files
pezkuwi-subxt/substrate/client/network/src/protocol/light_dispatch.rs
T
Toralf Wittner 51a45c5d9f network: Use "one shot" protocol handler. (#3520)
* network: Use "one shot" protocol handler.

Add two new `NetworkBehaviour`s, one handling remote block requests
and another one to handle light client requests (both local and from
remote). The change is motivated by the desire to use multiple
substreams of a single connection for different protocols. To achieve
this, libp2p's `OneShotHandler` is used as a protocol handler in each
behaviour. It will open a fresh substream for the duration of the
request and close it afterwards. For block requests, we currently only
handle incoming requests from remote and tests are missing. For light
client handling we support incoming requests from remote and also
ported a substantial amount of functionality over from
`light_dispatch.rs` (including several tests). However the result lacks
in at least two aspects:

(1) We require external updates w.r.t. the best block per peer and
currently nothing updates this information.
(2) We carry a lot of peer-related state around.

Both aspects could be simplified by externalising peer selection and
just requiring a specific peer ID where the request should be sent to.
We still have to maintain some peer related state due to the way
libp2p's swarm and network behaviour work (e.g. we must make sure to
always issue `NetworkBehaviourAction::SendEvent`s to peers we are
connected to, otherwise the actions die a silent death.

Another change implemented here is the use of protocol buffers as the
encoding for network messages. Certain individual fields of messages
are still SCALE encoded. There has been some discussion about this
in another PR (https://github.com/paritytech/substrate/pull/3452), so
far without resolution.

* Uncomment `Behaviour::light_client_request`.

* Add license headers.
2020-02-12 11:50:52 +01:00

1310 lines
43 KiB
Rust

// Copyright 2017-2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Light client requests service.
//!
//! Handles requests for data coming from our local light client and that must be answered by
//! nodes on the network.
use std::collections::{HashMap, VecDeque};
use std::sync::Arc;
use std::time::Duration;
use wasm_timer::Instant;
use log::{trace, info};
use futures::channel::oneshot::{Sender as OneShotSender};
use linked_hash_map::{Entry, LinkedHashMap};
use sp_blockchain::Error as ClientError;
use sc_client_api::{FetchChecker, RemoteHeaderRequest,
RemoteCallRequest, RemoteReadRequest, RemoteChangesRequest, ChangesProof,
RemoteReadChildRequest, RemoteBodyRequest, StorageProof};
use crate::message::{self, BlockAttributes, Direction, FromBlock, RequestId};
use libp2p::PeerId;
use crate::config::Roles;
use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor};
use sc_peerset::ReputationChange;
/// Remote request timeout.
const REQUEST_TIMEOUT: Duration = Duration::from_secs(15);
/// Default request retry count.
const RETRY_COUNT: usize = 1;
/// Reputation change for a peer when a request timed out.
pub(crate) const TIMEOUT_REPUTATION_CHANGE: i32 = -(1 << 8);
/// Trait used by the `LightDispatch` service to communicate messages back to the network.
pub trait LightDispatchNetwork<B: BlockT> {
/// Adjusts the reputation of the given peer.
fn report_peer(&mut self, who: &PeerId, reputation_change: ReputationChange);
/// Disconnect from the given peer. Used in case of misbehaviour.
fn disconnect_peer(&mut self, who: &PeerId);
/// Send to `who` a request for a header.
fn send_header_request(&mut self, who: &PeerId, id: RequestId, block: <<B as BlockT>::Header as HeaderT>::Number);
/// Send to `who` a read request.
fn send_read_request(
&mut self,
who: &PeerId,
id: RequestId,
block: <B as BlockT>::Hash,
keys: Vec<Vec<u8>>,
);
/// Send to `who` a child read request.
fn send_read_child_request(
&mut self,
who: &PeerId,
id: RequestId,
block: <B as BlockT>::Hash,
storage_key: Vec<u8>,
child_info: Vec<u8>,
child_type: u32,
keys: Vec<Vec<u8>>,
);
/// Send to `who` a call request.
fn send_call_request(
&mut self,
who: &PeerId,
id: RequestId,
block: <B as BlockT>::Hash,
method: String,
data: Vec<u8>
);
/// Send to `who` a changes request.
fn send_changes_request(
&mut self,
who: &PeerId,
id: RequestId,
first: <B as BlockT>::Hash,
last: <B as BlockT>::Hash,
min: <B as BlockT>::Hash,
max: <B as BlockT>::Hash,
storage_key: Option<Vec<u8>>,
key: Vec<u8>,
);
/// Send to `who` a body request.
fn send_body_request(
&mut self,
who: &PeerId,
id: RequestId,
fields: BlockAttributes,
from: FromBlock<<B as BlockT>::Hash, <<B as BlockT>::Header as HeaderT>::Number>,
to: Option<B::Hash>,
direction: Direction,
max: Option<u32>
);
}
/// Light client requests service. Dispatches requests to appropriate peers.
pub struct LightDispatch<B: BlockT> {
/// Verifies that responses are correct. Passed at initialization.
checker: Arc<dyn FetchChecker<B>>,
/// Numeric ID to assign to the next outgoing request. Used to assign responses to their
/// corresponding request.
next_request_id: u64,
/// Requests that we have yet to send out on the network.
pending_requests: VecDeque<Request<B>>,
/// List of nodes to which we have sent a request and that are yet to answer.
active_peers: LinkedHashMap<PeerId, Request<B>>,
/// List of nodes that we know of that aren't doing anything and that are available for new
/// requests.
idle_peers: VecDeque<PeerId>,
/// Best known block for each node in `active_peers` and `idle_peers`.
best_blocks: HashMap<PeerId, NumberFor<B>>,
}
struct Request<Block: BlockT> {
id: u64,
/// When the request got created or sent out to the network.
timestamp: Instant,
/// Number of remaining attempts to fulfill this request. If it reaches 0, we interrupt the
/// attempt.
retry_count: usize,
data: RequestData<Block>,
}
/// One request for data made by the `Client`.
///
/// Contains a `Sender` where to send the result.
pub(crate) enum RequestData<Block: BlockT> {
RemoteBody(RemoteBodyRequest<Block::Header>, OneShotSender<Result<Vec<Block::Extrinsic>, ClientError>>),
RemoteHeader(RemoteHeaderRequest<Block::Header>, OneShotSender<Result<Block::Header, ClientError>>),
RemoteRead(
RemoteReadRequest<Block::Header>,
OneShotSender<Result<HashMap<Vec<u8>, Option<Vec<u8>>>, ClientError>>,
),
RemoteReadChild(
RemoteReadChildRequest<Block::Header>,
OneShotSender<Result<HashMap<Vec<u8>, Option<Vec<u8>>>, ClientError>>
),
RemoteCall(RemoteCallRequest<Block::Header>, OneShotSender<Result<Vec<u8>, ClientError>>),
RemoteChanges(
RemoteChangesRequest<Block::Header>,
OneShotSender<Result<Vec<(NumberFor<Block>, u32)>, ClientError>>
),
}
enum Accept<Block: BlockT> {
Ok,
CheckFailed(ClientError, RequestData<Block>),
Unexpected(RequestData<Block>),
}
/// Dummy implementation of `FetchChecker` that always assumes that responses are bad.
///
/// Considering that it is the responsibility of the client to build the fetcher, it can use this
/// implementation if it knows that it will never perform any request.
#[derive(Default, Clone)]
pub struct AlwaysBadChecker;
impl<Block: BlockT> FetchChecker<Block> for AlwaysBadChecker {
fn check_header_proof(
&self,
_request: &RemoteHeaderRequest<Block::Header>,
_remote_header: Option<Block::Header>,
_remote_proof: StorageProof,
) -> Result<Block::Header, ClientError> {
Err(ClientError::Msg("AlwaysBadChecker".into()))
}
fn check_read_proof(
&self,
_request: &RemoteReadRequest<Block::Header>,
_remote_proof: StorageProof,
) -> Result<HashMap<Vec<u8>,Option<Vec<u8>>>, ClientError> {
Err(ClientError::Msg("AlwaysBadChecker".into()))
}
fn check_read_child_proof(
&self,
_request: &RemoteReadChildRequest<Block::Header>,
_remote_proof: StorageProof,
) -> Result<HashMap<Vec<u8>, Option<Vec<u8>>>, ClientError> {
Err(ClientError::Msg("AlwaysBadChecker".into()))
}
fn check_execution_proof(
&self,
_request: &RemoteCallRequest<Block::Header>,
_remote_proof: StorageProof,
) -> Result<Vec<u8>, ClientError> {
Err(ClientError::Msg("AlwaysBadChecker".into()))
}
fn check_changes_proof(
&self,
_request: &RemoteChangesRequest<Block::Header>,
_remote_proof: ChangesProof<Block::Header>
) -> Result<Vec<(NumberFor<Block>, u32)>, ClientError> {
Err(ClientError::Msg("AlwaysBadChecker".into()))
}
fn check_body_proof(
&self,
_request: &RemoteBodyRequest<Block::Header>,
_body: Vec<Block::Extrinsic>
) -> Result<Vec<Block::Extrinsic>, ClientError> {
Err(ClientError::Msg("AlwaysBadChecker".into()))
}
}
impl<B: BlockT> LightDispatch<B> where
B::Header: HeaderT,
{
/// Creates new light client requests processer.
pub fn new(checker: Arc<dyn FetchChecker<B>>) -> Self {
LightDispatch {
checker,
next_request_id: 0,
pending_requests: VecDeque::new(),
active_peers: LinkedHashMap::new(),
idle_peers: VecDeque::new(),
best_blocks: HashMap::new(),
}
}
/// Inserts a new request in the list of requests to execute.
pub(crate) fn add_request(&mut self, network: impl LightDispatchNetwork<B>, data: RequestData<B>) {
self.insert(RETRY_COUNT, data);
self.dispatch(network);
}
/// Inserts a new request in the list of requests to execute.
fn insert(&mut self, retry_count: usize, data: RequestData<B>) {
let request_id = self.next_request_id;
self.next_request_id += 1;
self.pending_requests.push_back(Request {
id: request_id,
timestamp: Instant::now(),
retry_count,
data,
});
}
/// Try to accept response from given peer.
fn accept_response(
&mut self,
rtype: &str,
mut network: impl LightDispatchNetwork<B>,
peer: PeerId,
request_id: u64,
try_accept: impl FnOnce(Request<B>, &Arc<dyn FetchChecker<B>>) -> Accept<B>
) {
let request = match self.remove(peer.clone(), request_id) {
Some(request) => request,
None => {
info!("Invalid remote {} response from peer {}", rtype, peer);
network.report_peer(&peer, ReputationChange::new_fatal("Invalid remote response"));
network.disconnect_peer(&peer);
self.remove_peer(peer);
return;
},
};
let retry_count = request.retry_count;
let (retry_count, retry_request_data) = match try_accept(request, &self.checker) {
Accept::Ok => (retry_count, None),
Accept::CheckFailed(error, retry_request_data) => {
info!("Failed to check remote {} response from peer {}: {}", rtype, peer, error);
network.report_peer(&peer, ReputationChange::new_fatal("Failed remote response check"));
network.disconnect_peer(&peer);
self.remove_peer(peer);
if retry_count > 0 {
(retry_count - 1, Some(retry_request_data))
} else {
trace!(target: "sync", "Failed to get remote {} response for given number of retries", rtype);
retry_request_data.fail(ClientError::RemoteFetchFailed.into());
(0, None)
}
},
Accept::Unexpected(retry_request_data) => {
info!("Unexpected response to remote {} from peer", rtype);
network.report_peer(&peer, ReputationChange::new_fatal("Unexpected remote response"));
network.disconnect_peer(&peer);
self.remove_peer(peer);
(retry_count, Some(retry_request_data))
},
};
if let Some(request_data) = retry_request_data {
self.insert(retry_count, request_data);
}
self.dispatch(network);
}
/// Call this when we connect to a node on the network.
pub fn on_connect(
&mut self,
network: impl LightDispatchNetwork<B>,
peer: PeerId,
role: Roles,
best_number: NumberFor<B>
) {
if !role.is_full() {
return;
}
self.idle_peers.push_back(peer.clone());
self.best_blocks.insert(peer, best_number);
self.dispatch(network);
}
/// Sets the best seen block for the given node.
pub fn update_best_number(&mut self, network: impl LightDispatchNetwork<B>, peer: PeerId, best_number: NumberFor<B>) {
self.best_blocks.insert(peer, best_number);
self.dispatch(network);
}
/// Call this when we disconnect from a node.
pub fn on_disconnect(&mut self, network: impl LightDispatchNetwork<B>, peer: PeerId) {
self.remove_peer(peer);
self.dispatch(network);
}
/// Must be called periodically in order to perform maintenance.
pub fn maintain_peers(&mut self, mut network: impl LightDispatchNetwork<B>) {
let now = Instant::now();
loop {
match self.active_peers.front() {
Some((_, request)) if now - request.timestamp >= REQUEST_TIMEOUT => (),
_ => break,
}
let (bad_peer, request) = self.active_peers.pop_front().expect("front() is Some as checked above");
self.pending_requests.push_front(request);
network.report_peer(&bad_peer, ReputationChange::new(TIMEOUT_REPUTATION_CHANGE, "Light request timeout"));
network.disconnect_peer(&bad_peer);
}
self.dispatch(network);
}
/// Handles a remote header response message from on the network.
pub fn on_remote_header_response(
&mut self,
network: impl LightDispatchNetwork<B>,
peer: PeerId,
response: message::RemoteHeaderResponse<B::Header>
) {
self.accept_response("header", network, peer, response.id, |request, checker| match request.data {
RequestData::RemoteHeader(request, sender) => match checker.check_header_proof(
&request,
response.header,
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::RemoteHeader(request, sender)),
},
data => Accept::Unexpected(data),
})
}
/// Handles a remote read response message from on the network.
pub fn on_remote_read_response(
&mut self,
network: impl LightDispatchNetwork<B>,
peer: PeerId,
response: message::RemoteReadResponse
) {
self.accept_response("read", network, peer, response.id, |request, checker| match request.data {
RequestData::RemoteRead(request, sender) => {
match 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)
),
}},
RequestData::RemoteReadChild(request, sender) => {
match checker.check_read_child_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::RemoteReadChild(request, sender)
),
}},
data => Accept::Unexpected(data),
})
}
/// Handles a remote call response message from on the network.
pub fn on_remote_call_response(
&mut self,
network: impl LightDispatchNetwork<B>,
peer: PeerId,
response: message::RemoteCallResponse
) {
self.accept_response("call", network, peer, response.id, |request, checker| match request.data {
RequestData::RemoteCall(request, sender) => match checker.check_execution_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::RemoteCall(request, sender)),
},
data => Accept::Unexpected(data),
})
}
/// Handles a remote changes response message from on the network.
pub fn on_remote_changes_response(
&mut self,
network: impl LightDispatchNetwork<B>,
peer: PeerId,
response: message::RemoteChangesResponse<NumberFor<B>, B::Hash>
) {
self.accept_response("changes", network, peer, response.id, |request, checker| match request.data {
RequestData::RemoteChanges(request, sender) => match checker.check_changes_proof(
&request, ChangesProof {
max_block: response.max,
proof: response.proof,
roots: response.roots.into_iter().collect(),
roots_proof: response.roots_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::RemoteChanges(request, sender)),
},
data => Accept::Unexpected(data),
})
}
/// Handles a remote body response message from on the network.
pub fn on_remote_body_response(
&mut self,
network: impl LightDispatchNetwork<B>,
peer: PeerId,
response: message::BlockResponse<B>
) {
self.accept_response("body", network, peer, response.id, |request, checker| match request.data {
RequestData::RemoteBody(request, sender) => {
let mut bodies: Vec<_> = response
.blocks
.into_iter()
.filter_map(|b| b.body)
.collect();
// Number of bodies are hardcoded to 1 for valid `RemoteBodyResponses`
if bodies.len() != 1 {
return Accept::CheckFailed(
"RemoteBodyResponse: invalid number of blocks".into(),
RequestData::RemoteBody(request, sender),
)
}
let body = bodies.remove(0);
match checker.check_body_proof(&request, body) {
Ok(body) => {
let _ = sender.send(Ok(body));
Accept::Ok
}
Err(error) => Accept::CheckFailed(error, RequestData::RemoteBody(request, sender)),
}
}
other => Accept::Unexpected(other),
})
}
pub fn is_light_response(&self, peer: &PeerId, request_id: message::RequestId) -> bool {
self.active_peers.get(&peer).map_or(false, |r| r.id == request_id)
}
fn remove(&mut self, peer: PeerId, id: u64) -> Option<Request<B>> {
match self.active_peers.entry(peer.clone()) {
Entry::Occupied(entry) => match entry.get().id == id {
true => {
self.idle_peers.push_back(peer);
Some(entry.remove())
},
false => None,
},
Entry::Vacant(_) => None,
}
}
/// Removes a peer from the list of known peers.
///
/// Puts back the active request that this node was performing into `pending_requests`.
fn remove_peer(&mut self, peer: PeerId) {
self.best_blocks.remove(&peer);
if let Some(request) = self.active_peers.remove(&peer) {
self.pending_requests.push_front(request);
return;
}
if let Some(idle_index) = self.idle_peers.iter().position(|i| *i == peer) {
self.idle_peers.swap_remove_back(idle_index);
}
}
/// Dispatches pending requests.
fn dispatch(&mut self, mut network: impl LightDispatchNetwork<B>) {
let mut last_peer = self.idle_peers.back().cloned();
let mut unhandled_requests = VecDeque::new();
loop {
let peer = match self.idle_peers.pop_front() {
Some(peer) => peer,
None => break,
};
// check if request can (optimistically) be processed by the peer
let can_be_processed_by_peer = {
let request = match self.pending_requests.front() {
Some(r) => r,
None => {
self.idle_peers.push_front(peer);
break;
},
};
let peer_best_block = self.best_blocks.get(&peer)
.expect("entries are inserted into best_blocks when peer is connected;
entries are removed from best_blocks when peer is disconnected;
peer is in idle_peers and thus connected; qed");
request.required_block() <= *peer_best_block
};
if !can_be_processed_by_peer {
// return peer to the back of the queue
self.idle_peers.push_back(peer.clone());
// we have enumerated all peers and noone can handle request
if Some(peer) == last_peer {
let request = self.pending_requests.pop_front().expect("checked in loop condition; qed");
unhandled_requests.push_back(request);
last_peer = self.idle_peers.back().cloned();
}
continue;
}
last_peer = self.idle_peers.back().cloned();
let mut request = self.pending_requests.pop_front().expect("checked in loop condition; qed");
request.timestamp = Instant::now();
trace!(target: "sync", "Dispatching remote request {} to peer {}", request.id, peer);
request.send_to(&mut network, &peer);
self.active_peers.insert(peer, request);
}
self.pending_requests.append(&mut unhandled_requests);
}
}
impl<Block: BlockT> Request<Block> {
/// Returns the block that the remote needs to have in order to be able to fulfill
/// this request.
fn required_block(&self) -> NumberFor<Block> {
match self.data {
RequestData::RemoteHeader(ref data, _) => data.block,
RequestData::RemoteRead(ref data, _) => *data.header.number(),
RequestData::RemoteReadChild(ref data, _) => *data.header.number(),
RequestData::RemoteCall(ref data, _) => *data.header.number(),
RequestData::RemoteChanges(ref data, _) => data.max_block.0,
RequestData::RemoteBody(ref data, _) => *data.header.number(),
}
}
fn send_to(&self, out: &mut impl LightDispatchNetwork<Block>, peer: &PeerId) {
match self.data {
RequestData::RemoteHeader(ref data, _) =>
out.send_header_request(
peer,
self.id,
data.block,
),
RequestData::RemoteRead(ref data, _) =>
out.send_read_request(
peer,
self.id,
data.block,
data.keys.clone(),
),
RequestData::RemoteReadChild(ref data, _) =>
out.send_read_child_request(
peer,
self.id,
data.block,
data.storage_key.clone(),
data.child_info.clone(),
data.child_type,
data.keys.clone(),
),
RequestData::RemoteCall(ref data, _) =>
out.send_call_request(
peer,
self.id,
data.block,
data.method.clone(),
data.call_data.clone(),
),
RequestData::RemoteChanges(ref data, _) =>
out.send_changes_request(
peer,
self.id,
data.first_block.1.clone(),
data.last_block.1.clone(),
data.tries_roots.1.clone(),
data.max_block.1.clone(),
data.storage_key.clone(),
data.key.clone(),
),
RequestData::RemoteBody(ref data, _) =>
out.send_body_request(
peer,
self.id,
message::BlockAttributes::BODY,
message::FromBlock::Hash(data.header.hash()),
None,
message::Direction::Ascending,
Some(1)
),
}
}
}
impl<Block: BlockT> RequestData<Block> {
fn fail(self, error: ClientError) {
// don't care if anyone is listening
match self {
RequestData::RemoteHeader(_, sender) => { let _ = sender.send(Err(error)); },
RequestData::RemoteCall(_, sender) => { let _ = sender.send(Err(error)); },
RequestData::RemoteRead(_, sender) => { let _ = sender.send(Err(error)); },
RequestData::RemoteReadChild(_, sender) => { let _ = sender.send(Err(error)); },
RequestData::RemoteChanges(_, sender) => { let _ = sender.send(Err(error)); },
RequestData::RemoteBody(_, sender) => { let _ = sender.send(Err(error)); },
}
}
}
#[cfg(test)]
pub mod tests {
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use std::time::Instant;
use futures::channel::oneshot;
use sp_core::storage::ChildInfo;
use sp_runtime::traits::{Block as BlockT, NumberFor, Header as HeaderT};
use sp_blockchain::{Error as ClientError, Result as ClientResult};
use sc_client_api::{FetchChecker, RemoteHeaderRequest,
ChangesProof, RemoteCallRequest, RemoteReadRequest,
RemoteReadChildRequest, RemoteChangesRequest, RemoteBodyRequest};
use crate::config::Roles;
use crate::message::{self, BlockAttributes, Direction, FromBlock, RequestId};
use libp2p::PeerId;
use super::{REQUEST_TIMEOUT, LightDispatch, LightDispatchNetwork, RequestData, StorageProof};
use sp_test_primitives::{Block, Header};
pub(crate) struct DummyFetchChecker<B> {
pub(crate) ok: bool,
_mark: std::marker::PhantomData<B>
}
impl<B> DummyFetchChecker<B> {
pub(crate) fn new(ok: bool) -> Self {
DummyFetchChecker { ok, _mark: std::marker::PhantomData }
}
}
impl<B: BlockT> FetchChecker<B> for DummyFetchChecker<B> {
fn check_header_proof(
&self,
_request: &RemoteHeaderRequest<B::Header>,
header: Option<B::Header>,
_remote_proof: StorageProof,
) -> ClientResult<B::Header> {
match self.ok {
true if header.is_some() => Ok(header.unwrap()),
_ => Err(ClientError::Backend("Test error".into())),
}
}
fn check_read_proof(
&self,
request: &RemoteReadRequest<B::Header>,
_: StorageProof,
) -> ClientResult<HashMap<Vec<u8>, Option<Vec<u8>>>> {
match self.ok {
true => Ok(request.keys
.iter()
.cloned()
.map(|k| (k, Some(vec![42])))
.collect()
),
false => Err(ClientError::Backend("Test error".into())),
}
}
fn check_read_child_proof(
&self,
request: &RemoteReadChildRequest<B::Header>,
_: StorageProof,
) -> ClientResult<HashMap<Vec<u8>, Option<Vec<u8>>>> {
match self.ok {
true => Ok(request.keys
.iter()
.cloned()
.map(|k| (k, Some(vec![42])))
.collect()
),
false => Err(ClientError::Backend("Test error".into())),
}
}
fn check_execution_proof(&self, _: &RemoteCallRequest<B::Header>, _: StorageProof) -> ClientResult<Vec<u8>> {
match self.ok {
true => Ok(vec![42]),
false => Err(ClientError::Backend("Test error".into())),
}
}
fn check_changes_proof(
&self,
_: &RemoteChangesRequest<B::Header>,
_: ChangesProof<B::Header>
) -> ClientResult<Vec<(NumberFor<B>, u32)>> {
match self.ok {
true => Ok(vec![(100.into(), 2)]),
false => Err(ClientError::Backend("Test error".into())),
}
}
fn check_body_proof(
&self,
_: &RemoteBodyRequest<B::Header>,
body: Vec<B::Extrinsic>
) -> ClientResult<Vec<B::Extrinsic>> {
match self.ok {
true => Ok(body),
false => Err(ClientError::Backend("Test error".into())),
}
}
}
fn dummy(ok: bool) -> LightDispatch<Block> {
LightDispatch::new(Arc::new(DummyFetchChecker::new(ok)))
}
fn total_peers(light_dispatch: &LightDispatch<Block>) -> usize {
light_dispatch.idle_peers.len() + light_dispatch.active_peers.len()
}
fn receive_call_response(
network_interface: impl LightDispatchNetwork<Block>,
light_dispatch: &mut LightDispatch<Block>,
peer: PeerId,
id: message::RequestId
) {
light_dispatch.on_remote_call_response(network_interface, peer, message::RemoteCallResponse {
id: id,
proof: StorageProof::empty(),
});
}
pub(crate) fn dummy_header() -> Header {
Header {
parent_hash: Default::default(),
number: 0,
state_root: Default::default(),
extrinsics_root: Default::default(),
digest: Default::default(),
}
}
#[derive(Default)]
struct DummyNetwork {
disconnected_peers: HashSet<PeerId>,
}
impl<'a, B: BlockT> LightDispatchNetwork<B> for &'a mut DummyNetwork {
fn report_peer(&mut self, _: &PeerId, _: crate::ReputationChange) {}
fn disconnect_peer(&mut self, who: &PeerId) {
self.disconnected_peers.insert(who.clone());
}
fn send_header_request(&mut self, _: &PeerId, _: RequestId, _: <<B as BlockT>::Header as HeaderT>::Number) {}
fn send_read_request(&mut self, _: &PeerId, _: RequestId, _: <B as BlockT>::Hash, _: Vec<Vec<u8>>) {}
fn send_read_child_request(&mut self, _: &PeerId, _: RequestId, _: <B as BlockT>::Hash, _: Vec<u8>,
_: Vec<u8>, _: u32, _: Vec<Vec<u8>>) {}
fn send_call_request(&mut self, _: &PeerId, _: RequestId, _: <B as BlockT>::Hash, _: String, _: Vec<u8>) {}
fn send_changes_request(&mut self, _: &PeerId, _: RequestId, _: <B as BlockT>::Hash, _: <B as BlockT>::Hash,
_: <B as BlockT>::Hash, _: <B as BlockT>::Hash, _: Option<Vec<u8>>, _: Vec<u8>) {}
fn send_body_request(&mut self, _: &PeerId, _: RequestId, _: BlockAttributes, _: FromBlock<<B as BlockT>::Hash,
<<B as BlockT>::Header as HeaderT>::Number>, _: Option<B::Hash>, _: Direction, _: Option<u32>) {}
}
fn assert_disconnected_peer(dummy: &DummyNetwork) {
assert_eq!(dummy.disconnected_peers.len(), 1);
}
#[test]
fn knows_about_peers_roles() {
let mut network_interface = DummyNetwork::default();
let mut light_dispatch = dummy(true);
let peer0 = PeerId::random();
let peer1 = PeerId::random();
let peer2 = PeerId::random();
light_dispatch.on_connect(&mut network_interface, peer0, Roles::LIGHT, 1000);
light_dispatch.on_connect(&mut network_interface, peer1.clone(), Roles::FULL, 2000);
light_dispatch.on_connect(&mut network_interface, peer2.clone(), Roles::AUTHORITY, 3000);
assert_eq!(vec![peer1.clone(), peer2.clone()], light_dispatch.idle_peers.iter().cloned().collect::<Vec<_>>());
assert_eq!(light_dispatch.best_blocks.get(&peer1), Some(&2000));
assert_eq!(light_dispatch.best_blocks.get(&peer2), Some(&3000));
}
#[test]
fn disconnects_from_idle_peer() {
let peer0 = PeerId::random();
let mut network_interface = DummyNetwork::default();
let mut light_dispatch = dummy(true);
light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 100);
assert_eq!(1, total_peers(&light_dispatch));
assert!(!light_dispatch.best_blocks.is_empty());
light_dispatch.on_disconnect(&mut network_interface, peer0);
assert_eq!(0, total_peers(&light_dispatch));
assert!(light_dispatch.best_blocks.is_empty());
}
#[test]
fn disconnects_from_timeouted_peer() {
let mut light_dispatch = dummy(true);
let mut network_interface = DummyNetwork::default();
let peer0 = PeerId::random();
let peer1 = PeerId::random();
light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 1000);
light_dispatch.on_connect(&mut network_interface, peer1.clone(), Roles::FULL, 1000);
assert_eq!(vec![peer0.clone(), peer1.clone()], light_dispatch.idle_peers.iter().cloned().collect::<Vec<_>>());
assert!(light_dispatch.active_peers.is_empty());
light_dispatch.add_request(&mut network_interface, RequestData::RemoteCall(RemoteCallRequest {
block: Default::default(),
header: dummy_header(),
method: "test".into(),
call_data: vec![],
retry_count: None,
}, oneshot::channel().0));
assert_eq!(vec![peer1.clone()], light_dispatch.idle_peers.iter().cloned().collect::<Vec<_>>());
assert_eq!(vec![peer0.clone()], light_dispatch.active_peers.keys().cloned().collect::<Vec<_>>());
light_dispatch.active_peers[&peer0].timestamp = Instant::now() - REQUEST_TIMEOUT - REQUEST_TIMEOUT;
light_dispatch.maintain_peers(&mut network_interface);
assert!(light_dispatch.idle_peers.is_empty());
assert_eq!(vec![peer1.clone()], light_dispatch.active_peers.keys().cloned().collect::<Vec<_>>());
assert_disconnected_peer(&network_interface);
}
#[test]
fn disconnects_from_peer_on_response_with_wrong_id() {
let mut light_dispatch = dummy(true);
let peer0 = PeerId::random();
let mut network_interface = DummyNetwork::default();
light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 1000);
light_dispatch.add_request(&mut network_interface, RequestData::RemoteCall(RemoteCallRequest {
block: Default::default(),
header: dummy_header(),
method: "test".into(),
call_data: vec![],
retry_count: None,
}, oneshot::channel().0));
receive_call_response(&mut network_interface, &mut light_dispatch, peer0, 1);
assert_disconnected_peer(&network_interface);
assert_eq!(light_dispatch.pending_requests.len(), 1);
}
#[test]
fn disconnects_from_peer_on_incorrect_response() {
let mut light_dispatch = dummy(false);
let mut network_interface = DummyNetwork::default();
let peer0 = PeerId::random();
light_dispatch.add_request(&mut network_interface, RequestData::RemoteCall(RemoteCallRequest {
block: Default::default(),
header: dummy_header(),
method: "test".into(),
call_data: vec![],
retry_count: Some(1),
}, oneshot::channel().0));
light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 1000);
receive_call_response(&mut network_interface, &mut light_dispatch, peer0.clone(), 0);
assert_disconnected_peer(&network_interface);
assert_eq!(light_dispatch.pending_requests.len(), 1);
}
#[test]
fn disconnects_from_peer_on_unexpected_response() {
let mut light_dispatch = dummy(true);
let mut network_interface = DummyNetwork::default();
let peer0 = PeerId::random();
light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 1000);
receive_call_response(&mut network_interface, &mut light_dispatch, peer0, 0);
assert_disconnected_peer(&network_interface);
}
#[test]
fn disconnects_from_peer_on_wrong_response_type() {
let mut light_dispatch = dummy(false);
let peer0 = PeerId::random();
let mut network_interface = DummyNetwork::default();
light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 1000);
light_dispatch.add_request(&mut network_interface, RequestData::RemoteCall(RemoteCallRequest {
block: Default::default(),
header: dummy_header(),
method: "test".into(),
call_data: vec![],
retry_count: Some(1),
}, oneshot::channel().0));
light_dispatch.on_remote_read_response(&mut network_interface, peer0.clone(), message::RemoteReadResponse {
id: 0,
proof: StorageProof::empty(),
});
assert_disconnected_peer(&network_interface);
assert_eq!(light_dispatch.pending_requests.len(), 1);
}
#[test]
fn receives_remote_failure_after_retry_count_failures() {
let retry_count = 2;
let peer_ids = (0 .. retry_count + 1).map(|_| PeerId::random()).collect::<Vec<_>>();
let mut light_dispatch = dummy(false);
let mut network_interface = DummyNetwork::default();
for i in 0..retry_count+1 {
light_dispatch.on_connect(&mut network_interface, peer_ids[i].clone(), Roles::FULL, 1000);
}
let (tx, mut response) = oneshot::channel();
light_dispatch.add_request(&mut network_interface, RequestData::RemoteCall(RemoteCallRequest {
block: Default::default(),
header: dummy_header(),
method: "test".into(),
call_data: vec![],
retry_count: Some(retry_count)
}, tx));
for i in 0..retry_count {
assert!(response.try_recv().unwrap().is_none());
receive_call_response(&mut network_interface, &mut light_dispatch, peer_ids[i].clone(), i as u64);
}
assert!(response.try_recv().unwrap().unwrap().is_err());
}
#[test]
fn receives_remote_call_response() {
let mut light_dispatch = dummy(true);
let mut network_interface = DummyNetwork::default();
let peer0 = PeerId::random();
light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 1000);
let (tx, response) = oneshot::channel();
light_dispatch.add_request(&mut network_interface, RequestData::RemoteCall(RemoteCallRequest {
block: Default::default(),
header: dummy_header(),
method: "test".into(),
call_data: vec![],
retry_count: None,
}, tx));
receive_call_response(&mut network_interface, &mut light_dispatch, peer0.clone(), 0);
assert_eq!(futures::executor::block_on(response).unwrap().unwrap(), vec![42]);
}
#[test]
fn receives_remote_read_response() {
let mut light_dispatch = dummy(true);
let mut network_interface = DummyNetwork::default();
let peer0 = PeerId::random();
light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 1000);
let (tx, response) = oneshot::channel();
light_dispatch.add_request(&mut network_interface, RequestData::RemoteRead(RemoteReadRequest {
header: dummy_header(),
block: Default::default(),
keys: vec![b":key".to_vec()],
retry_count: None,
}, tx));
light_dispatch.on_remote_read_response(&mut network_interface, peer0.clone(), message::RemoteReadResponse {
id: 0,
proof: StorageProof::empty(),
});
assert_eq!(
futures::executor::block_on(response).unwrap().unwrap().remove(b":key".as_ref()).unwrap(),
Some(vec![42])
);
}
#[test]
fn receives_remote_read_child_response() {
let mut light_dispatch = dummy(true);
let mut network_interface = DummyNetwork::default();
let peer0 = PeerId::random();
light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 1000);
let (tx, response) = oneshot::channel();
let child_info = ChildInfo::new_default(b"unique_id_1");
let (child_info, child_type) = child_info.info();
light_dispatch.add_request(&mut network_interface, RequestData::RemoteReadChild(RemoteReadChildRequest {
header: dummy_header(),
block: Default::default(),
storage_key: b":child_storage:sub".to_vec(),
child_info: child_info.to_vec(),
child_type,
keys: vec![b":key".to_vec()],
retry_count: None,
}, tx));
light_dispatch.on_remote_read_response(&mut network_interface,
peer0.clone(), message::RemoteReadResponse {
id: 0,
proof: StorageProof::empty(),
});
assert_eq!(futures::executor::block_on(response).unwrap().unwrap().remove(b":key".as_ref()).unwrap(), Some(vec![42]));
}
#[test]
fn receives_remote_header_response() {
let mut light_dispatch = dummy(true);
let mut network_interface = DummyNetwork::default();
let peer0 = PeerId::random();
light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 1000);
let (tx, response) = oneshot::channel();
light_dispatch.add_request(&mut network_interface, RequestData::RemoteHeader(RemoteHeaderRequest {
cht_root: Default::default(),
block: 1,
retry_count: None,
}, tx));
light_dispatch.on_remote_header_response(&mut network_interface, peer0.clone(), message::RemoteHeaderResponse {
id: 0,
header: Some(Header {
parent_hash: Default::default(),
number: 1,
state_root: Default::default(),
extrinsics_root: Default::default(),
digest: Default::default(),
}),
proof: StorageProof::empty(),
});
assert_eq!(
futures::executor::block_on(response).unwrap().unwrap().hash(),
"6443a0b46e0412e626363028115a9f2cf963eeed526b8b33e5316f08b50d0dc3".parse().unwrap(),
);
}
#[test]
fn receives_remote_changes_response() {
let mut light_dispatch = dummy(true);
let mut network_interface = DummyNetwork::default();
let peer0 = PeerId::random();
light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 1000);
let (tx, response) = oneshot::channel();
light_dispatch.add_request(&mut network_interface, RequestData::RemoteChanges(RemoteChangesRequest {
changes_trie_configs: vec![sp_core::ChangesTrieConfigurationRange {
zero: (0, Default::default()),
end: None,
config: Some(sp_core::ChangesTrieConfiguration::new(4, 2)),
}],
first_block: (1, Default::default()),
last_block: (100, Default::default()),
max_block: (100, Default::default()),
tries_roots: (1, Default::default(), vec![]),
key: vec![],
storage_key: None,
retry_count: None,
}, tx));
light_dispatch.on_remote_changes_response(&mut network_interface, peer0.clone(), message::RemoteChangesResponse {
id: 0,
max: 1000,
proof: vec![vec![2]],
roots: vec![],
roots_proof: StorageProof::empty(),
});
assert_eq!(futures::executor::block_on(response).unwrap().unwrap(), vec![(100, 2)]);
}
#[test]
fn does_not_sends_request_to_peer_who_has_no_required_block() {
let mut light_dispatch = dummy(true);
let mut network_interface = DummyNetwork::default();
let peer1 = PeerId::random();
let peer2 = PeerId::random();
light_dispatch.on_connect(&mut network_interface, peer1.clone(), Roles::FULL, 100);
light_dispatch.add_request(&mut network_interface, RequestData::RemoteHeader(RemoteHeaderRequest {
cht_root: Default::default(),
block: 200,
retry_count: None,
}, oneshot::channel().0));
light_dispatch.add_request(&mut network_interface, RequestData::RemoteHeader(RemoteHeaderRequest {
cht_root: Default::default(),
block: 250,
retry_count: None,
}, oneshot::channel().0));
light_dispatch.add_request(&mut network_interface, RequestData::RemoteHeader(RemoteHeaderRequest {
cht_root: Default::default(),
block: 250,
retry_count: None,
}, oneshot::channel().0));
light_dispatch.on_connect(&mut network_interface, peer2.clone(), Roles::FULL, 150);
assert_eq!(vec![peer1.clone(), peer2.clone()], light_dispatch.idle_peers.iter().cloned().collect::<Vec<_>>());
assert_eq!(light_dispatch.pending_requests.len(), 3);
light_dispatch.update_best_number(&mut network_interface, peer1.clone(), 250);
assert_eq!(vec![peer2.clone()], light_dispatch.idle_peers.iter().cloned().collect::<Vec<_>>());
assert_eq!(light_dispatch.pending_requests.len(), 2);
light_dispatch.update_best_number(&mut network_interface, peer2.clone(), 250);
assert!(!light_dispatch.idle_peers.iter().any(|_| true));
assert_eq!(light_dispatch.pending_requests.len(), 1);
light_dispatch.on_remote_header_response(&mut network_interface, peer1.clone(), message::RemoteHeaderResponse {
id: 0,
header: Some(dummy_header()),
proof: StorageProof::empty(),
});
assert!(!light_dispatch.idle_peers.iter().any(|_| true));
assert_eq!(light_dispatch.pending_requests.len(), 0);
}
#[test]
fn does_not_loop_forever_after_dispatching_request_to_last_peer() {
// this test is a regression for a bug where the dispatch function would
// loop forever after dispatching a request to the last peer, since the
// last peer was not updated
let mut light_dispatch = dummy(true);
let mut network_interface = DummyNetwork::default();
let peer1 = PeerId::random();
let peer2 = PeerId::random();
let peer3 = PeerId::random();
light_dispatch.add_request(&mut network_interface, RequestData::RemoteHeader(RemoteHeaderRequest {
cht_root: Default::default(),
block: 250,
retry_count: None,
}, oneshot::channel().0));
light_dispatch.add_request(&mut network_interface, RequestData::RemoteHeader(RemoteHeaderRequest {
cht_root: Default::default(),
block: 250,
retry_count: None,
}, oneshot::channel().0));
light_dispatch.on_connect(&mut network_interface, peer1.clone(), Roles::FULL, 200);
light_dispatch.on_connect(&mut network_interface, peer2.clone(), Roles::FULL, 200);
light_dispatch.on_connect(&mut network_interface, peer3.clone(), Roles::FULL, 250);
assert_eq!(vec![peer1.clone(), peer2.clone()], light_dispatch.idle_peers.iter().cloned().collect::<Vec<_>>());
assert_eq!(light_dispatch.pending_requests.len(), 1);
}
#[test]
fn tries_to_send_all_pending_requests() {
let mut light_dispatch = dummy(true);
let mut network_interface = DummyNetwork::default();
let peer1 = PeerId::random();
light_dispatch.add_request(&mut network_interface, RequestData::RemoteHeader(RemoteHeaderRequest {
cht_root: Default::default(),
block: 300,
retry_count: None,
}, oneshot::channel().0));
light_dispatch.add_request(&mut network_interface, RequestData::RemoteHeader(RemoteHeaderRequest {
cht_root: Default::default(),
block: 250,
retry_count: None,
}, oneshot::channel().0));
light_dispatch.on_connect(&mut network_interface, peer1.clone(), Roles::FULL, 250);
assert!(light_dispatch.idle_peers.iter().cloned().collect::<Vec<_>>().is_empty());
assert_eq!(light_dispatch.pending_requests.len(), 1);
}
#[test]
fn remote_body_with_one_block_body_should_succeed() {
let mut light_dispatch = dummy(true);
let mut network_interface = DummyNetwork::default();
let peer1 = PeerId::random();
let header = dummy_header();
light_dispatch.on_connect(&mut network_interface, peer1.clone(), Roles::FULL, 250);
light_dispatch.add_request(&mut network_interface, RequestData::RemoteBody(RemoteBodyRequest {
header: header.clone(),
retry_count: None,
}, oneshot::channel().0));
assert!(light_dispatch.pending_requests.is_empty());
assert_eq!(light_dispatch.active_peers.len(), 1);
let block = message::BlockData::<Block> {
hash: sp_core::H256::random(),
header: None,
body: Some(Vec::new()),
message_queue: None,
receipt: None,
justification: None,
};
let response = message::generic::BlockResponse {
id: 0,
blocks: vec![block],
};
light_dispatch.on_remote_body_response(&mut network_interface, peer1.clone(), response);
assert!(light_dispatch.active_peers.is_empty());
assert_eq!(light_dispatch.idle_peers.len(), 1);
}
#[test]
fn remote_body_with_three_bodies_should_fail() {
let mut light_dispatch = dummy(true);
let mut network_interface = DummyNetwork::default();
let peer1 = PeerId::random();
let header = dummy_header();
light_dispatch.on_connect(&mut network_interface, peer1.clone(), Roles::FULL, 250);
light_dispatch.add_request(&mut network_interface, RequestData::RemoteBody(RemoteBodyRequest {
header: header.clone(),
retry_count: None,
}, oneshot::channel().0));
assert!(light_dispatch.pending_requests.is_empty());
assert_eq!(light_dispatch.active_peers.len(), 1);
let response = {
let blocks: Vec<_> = (0..3).map(|_| message::BlockData::<Block> {
hash: sp_core::H256::random(),
header: None,
body: Some(Vec::new()),
message_queue: None,
receipt: None,
justification: None,
}).collect();
message::generic::BlockResponse {
id: 0,
blocks,
}
};
light_dispatch.on_remote_body_response(&mut network_interface, peer1.clone(), response);
assert!(light_dispatch.active_peers.is_empty());
assert!(light_dispatch.idle_peers.is_empty(), "peer should be disconnected after bad response");
}
}