// Copyright 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 .
//! [`NetworkBehaviour`] implementation which handles light client requests.
//!
//! Every request is coming in on a separate connection substream which gets
//! closed after we have sent the response back. Requests and responses are
//! encoded as protocol buffers (cf. `api.v1.proto`).
//!
//! For every outgoing request we likewise open a separate substream.
#![allow(unused)]
use bytes::Bytes;
use codec::{self, Encode, Decode};
use crate::{
chain::Client,
config::ProtocolId,
protocol::{api, light_dispatch::TIMEOUT_REPUTATION_CHANGE}
};
use futures::{channel::oneshot, future::BoxFuture, prelude::*, stream::FuturesUnordered};
use libp2p::{
core::{
ConnectedPoint,
Multiaddr,
PeerId,
upgrade::{InboundUpgrade, ReadOneError, UpgradeInfo, Negotiated},
upgrade::{OutboundUpgrade, read_one, write_one}
},
swarm::{NetworkBehaviour, NetworkBehaviourAction, OneShotHandler, PollParameters, SubstreamProtocol}
};
use nohash_hasher::IntMap;
use prost::Message;
use rustc_hex::ToHex;
use sc_client::light::fetcher;
use sc_client_api::StorageProof;
use sc_peerset::ReputationChange;
use sp_core::storage::{ChildInfo, StorageKey};
use sp_blockchain::{Error as ClientError};
use sp_runtime::traits::{Block, Header, NumberFor, Zero};
use std::{
collections::{BTreeMap, VecDeque, HashMap},
iter,
io,
sync::Arc,
time::{Duration, Instant},
task::{Context, Poll}
};
use void::Void;
/// Configuration options for `LightClientHandler` behaviour.
#[derive(Debug, Clone)]
pub struct Config {
max_data_size: usize,
max_pending_requests: usize,
inactivity_timeout: Duration,
request_timeout: Duration,
protocol: Bytes,
}
impl Config {
/// Create a fresh configuration with the following options:
///
/// - max. data size = 1 MiB
/// - max. pending requests = 128
/// - inactivity timeout = 15s
/// - request timeout = 15s
pub fn new(id: &ProtocolId) -> Self {
let mut c = Config {
max_data_size: 1024 * 1024,
max_pending_requests: 128,
inactivity_timeout: Duration::from_secs(15),
request_timeout: Duration::from_secs(15),
protocol: Bytes::new(),
};
c.set_protocol(id);
c
}
/// Limit the max. length of incoming request bytes.
pub fn set_max_data_size(&mut self, v: usize) -> &mut Self {
self.max_data_size = v;
self
}
/// Limit the max. number of pending requests.
pub fn set_max_pending_requests(&mut self, v: usize) -> &mut Self {
self.max_pending_requests = v;
self
}
/// Limit the max. duration the connection may remain inactive before closing it.
pub fn set_inactivity_timeout(&mut self, v: Duration) -> &mut Self {
self.inactivity_timeout = v;
self
}
/// Limit the max. request duration.
pub fn set_request_timeout(&mut self, v: Duration) -> &mut Self {
self.request_timeout = v;
self
}
/// Set protocol to use for upgrade negotiation.
pub fn set_protocol(&mut self, id: &ProtocolId) -> &mut Self {
let mut v = Vec::new();
v.extend_from_slice(b"/");
v.extend_from_slice(id.as_bytes());
v.extend_from_slice(b"/light/1");
self.protocol = v.into();
self
}
}
/// Possible errors while handling light clients.
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// There are currently too many pending request.
#[error("too many pending requests")]
TooManyRequests,
/// The response type does not correspond to the issued request.
#[error("unexpected response")]
UnexpectedResponse,
/// A bad request has been received.
#[error("bad request: {0}")]
BadRequest(&'static str),
/// The chain client errored.
#[error("client error: {0}")]
Client(#[from] ClientError),
/// Encoding or decoding of some data failed.
#[error("codec error: {0}")]
Codec(#[from] codec::Error),
}
/// The possible light client requests we support.
///
/// The associated `oneshot::Sender` will be used to convey the result of
/// their request back to them (cf. `Reply`).
//
// This is modeled after light_dispatch.rs's `RequestData` which is not
// used because we currently only support a subset of those.
#[derive(Debug)]
pub enum Request {
Header {
request: fetcher::RemoteHeaderRequest,
sender: oneshot::Sender>
},
Read {
request: fetcher::RemoteReadRequest,
sender: oneshot::Sender, Option>>, ClientError>>
},
ReadChild {
request: fetcher::RemoteReadChildRequest,
sender: oneshot::Sender, Option>>, ClientError>>
},
Call {
request: fetcher::RemoteCallRequest,
sender: oneshot::Sender, ClientError>>
},
Changes {
request: fetcher::RemoteChangesRequest,
sender: oneshot::Sender, u32)>, ClientError>>
}
}
/// The data to send back to the light client over the oneshot channel.
//
// It is unified here in order to be able to return it as a function
// result instead of delivering it to the client as a side effect of
// response processing.
#[derive(Debug)]
enum Reply {
VecU8(Vec),
VecNumberU32(Vec<(::Number, u32)>),
MapVecU8OptVecU8(HashMap, Option>>),
Header(B::Header)
}
/// Augments a light client request with metadata.
#[derive(Debug)]
struct RequestWrapper {
/// Time when this value was created.
timestamp: Instant,
/// Remaining retries.
retries: usize,
/// The actual request.
request: Request,
/// Peer information, e.g. `PeerId`.
peer: P
}
/// Information we have about some peer.
#[derive(Debug)]
struct PeerInfo {
address: Multiaddr,
best_block: Option>,
status: PeerStatus,
}
/// A peer is either idle or busy processing a request from us.
#[derive(Debug, Clone, PartialEq, Eq)]
enum PeerStatus {
/// The peer is available.
Idle,
/// We wait for the peer to return us a response for the given request ID.
BusyWith(u64),
}
/// The light client handler behaviour.
pub struct LightClientHandler {
/// This behaviour's configuration.
config: Config,
/// Blockchain client.
chain: Arc>,
/// Verifies that received responses are correct.
checker: Arc>,
/// Peer information (addresses, their best block, etc.)
peers: HashMap>,
/// Futures sending back response to remote clients.
responses: FuturesUnordered>,
/// Pending (local) requests.
pending_requests: VecDeque>,
/// Requests on their way to remote peers.
outstanding: IntMap>,
/// (Local) Request ID counter
next_request_id: u64,
/// Handle to use for reporting misbehaviour of peers.
peerset: sc_peerset::PeersetHandle,
/// Type witness term.
_marker: std::marker::PhantomData
}
impl LightClientHandler
where
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
B: Block,
{
/// Construct a new light client handler.
pub fn new
( cfg: Config
, chain: Arc>
, checker: Arc>
, peerset: sc_peerset::PeersetHandle
) -> Self
{
LightClientHandler {
config: cfg,
chain,
checker,
peers: HashMap::new(),
responses: FuturesUnordered::new(),
pending_requests: VecDeque::new(),
outstanding: IntMap::default(),
next_request_id: 1,
peerset,
_marker: std::marker::PhantomData
}
}
/// We rely on external information about peers best blocks as we lack the
/// means to determine it ourselves.
pub fn update_best_block(&mut self, peer: &PeerId, num: NumberFor) {
if let Some(info) = self.peers.get_mut(peer) {
info.best_block = Some(num)
}
}
/// Issue a new light client request.
pub fn request(&mut self, req: Request) -> Result<(), Error> {
if self.pending_requests.len() >= self.config.max_pending_requests {
return Err(Error::TooManyRequests)
}
let rw = RequestWrapper {
timestamp: Instant::now(),
retries: retries(&req),
request: req,
peer: (), // we do not know the peer yet
};
self.pending_requests.push_back(rw);
Ok(())
}
fn next_request_id(&mut self) -> u64 {
let id = self.next_request_id;
self.next_request_id += 1;
id
}
// Iterate over peers known to possess a certain block.
fn idle_peers_with_block(&mut self, num: NumberFor) -> impl Iterator- + '_ {
self.peers.iter()
.filter(move |(_, info)| {
info.status == PeerStatus::Idle && info.best_block >= Some(num)
})
.map(|(peer, _)| peer.clone())
}
// Iterate over peers without a known block.
fn idle_peers_with_unknown_block(&mut self) -> impl Iterator
- + '_ {
self.peers.iter()
.filter(|(_, info)| {
info.status == PeerStatus::Idle && info.best_block.is_none()
})
.map(|(peer, _)| peer.clone())
}
/// Remove the given peer.
///
/// If we have a request to this peer in flight, we move it back to
/// the pending requests queue.
fn remove_peer(&mut self, peer: &PeerId) {
if let Some(id) = self.outstanding.iter().find(|(_, rw)| &rw.peer == peer).map(|(k, _)| *k) {
let rw = self.outstanding.remove(&id).expect("key belongs to entry in this map");
let rw = RequestWrapper {
timestamp: rw.timestamp,
retries: rw.retries,
request: rw.request,
peer: (), // need to find another peer
};
self.pending_requests.push_back(rw);
}
self.peers.remove(peer);
}
/// Process a local request's response from remote.
///
/// If successful, this will give us the actual, checked data we should be
/// sending back to the client, otherwise an error.
fn on_response
( &mut self
, peer: &PeerId
, request: &Request
, response: api::v1::light::Response
) -> Result, Error>
{
log::trace!("response {} from {}", response.id, peer);
use api::v1::light::response::Response;
match response.response {
Some(Response::RemoteCallResponse(response)) =>
if let Request::Call { request , .. } = request {
let proof = Decode::decode(&mut response.proof.as_ref())?;
let reply = self.checker.check_execution_proof(request, proof)?;
Ok(Reply::VecU8(reply))
} else {
Err(Error::UnexpectedResponse)
}
Some(Response::RemoteReadResponse(response)) =>
match request {
Request::Read { request, .. } => {
let proof = Decode::decode(&mut response.proof.as_ref())?;
let reply = self.checker.check_read_proof(&request, proof)?;
Ok(Reply::MapVecU8OptVecU8(reply))
}
Request::ReadChild { request, .. } => {
let proof = Decode::decode(&mut response.proof.as_ref())?;
let reply = self.checker.check_read_child_proof(&request, proof)?;
Ok(Reply::MapVecU8OptVecU8(reply))
}
_ => Err(Error::UnexpectedResponse)
}
Some(Response::RemoteChangesResponse(response)) =>
if let Request::Changes { request, .. } = request {
let max_block = Decode::decode(&mut response.max.as_ref())?;
let roots_proof = Decode::decode(&mut response.roots_proof.as_ref())?;
let roots = {
let mut r = BTreeMap::new();
for pair in response.roots {
let k = Decode::decode(&mut pair.fst.as_ref())?;
let v = Decode::decode(&mut pair.snd.as_ref())?;
r.insert(k, v);
}
r
};
let reply = self.checker.check_changes_proof(&request, fetcher::ChangesProof {
max_block,
proof: response.proof,
roots,
roots_proof,
})?;
Ok(Reply::VecNumberU32(reply))
} else {
Err(Error::UnexpectedResponse)
}
Some(Response::RemoteHeaderResponse(response)) =>
if let Request::Header { request, .. } = request {
let header =
if response.header.is_empty() {
None
} else {
Some(Decode::decode(&mut response.header.as_ref())?)
};
let proof = Decode::decode(&mut response.proof.as_ref())?;
let reply = self.checker.check_header_proof(&request, header, proof)?;
Ok(Reply::Header(reply))
} else {
Err(Error::UnexpectedResponse)
}
None => Err(Error::UnexpectedResponse)
}
}
fn on_remote_call_request
( &mut self
, peer: &PeerId
, request_id: u64
, request: &api::v1::light::RemoteCallRequest
) -> Result
{
log::trace!("remote call request {} from {} ({} at {:?})",
request_id,
peer,
request.method,
request.block);
let block = Decode::decode(&mut request.block.as_ref())?;
let proof = match self.chain.execution_proof(&block, &request.method, &request.data) {
Ok((_, proof)) => proof,
Err(e) => {
log::trace!("remote call request {} from {} ({} at {:?}) failed with: {}",
request_id,
peer,
request.method,
request.block,
e);
StorageProof::empty()
}
};
let response = {
let r = api::v1::light::RemoteCallResponse { proof: proof.encode() };
api::v1::light::response::Response::RemoteCallResponse(r)
};
Ok(api::v1::light::Response { id: request_id, response: Some(response) })
}
fn on_remote_read_request
( &mut self
, peer: &PeerId
, request_id: u64
, request: &api::v1::light::RemoteReadRequest
) -> Result
{
if request.keys.is_empty() {
log::debug!("invalid remote read request sent by {}", peer);
return Err(Error::BadRequest("remote read request without keys"))
}
log::trace!("remote read request {} from {} ({} at {:?})",
request_id,
peer,
fmt_keys(request.keys.first(), request.keys.last()),
request.block);
let block = Decode::decode(&mut request.block.as_ref())?;
let proof = match self.chain.read_proof(&block, &request.keys) {
Ok(proof) => proof,
Err(error) => {
log::trace!("remote read request {} from {} ({} at {:?}) failed with: {}",
request_id,
peer,
fmt_keys(request.keys.first(), request.keys.last()),
request.block,
error);
StorageProof::empty()
}
};
let response = {
let r = api::v1::light::RemoteReadResponse { proof: proof.encode() };
api::v1::light::response::Response::RemoteReadResponse(r)
};
Ok(api::v1::light::Response { id: request_id, response: Some(response) })
}
fn on_remote_read_child_request
( &mut self
, peer: &PeerId
, request_id: u64
, request: &api::v1::light::RemoteReadChildRequest
) -> Result
{
if request.keys.is_empty() {
log::debug!("invalid remote child read request sent by {}", peer);
return Err(Error::BadRequest("remove read child request without keys"))
}
log::trace!("remote read child request {} from {} ({} {} at {:?})",
request_id,
peer,
request.storage_key.to_hex::(),
fmt_keys(request.keys.first(), request.keys.last()),
request.block);
let block = Decode::decode(&mut request.block.as_ref())?;
let proof =
if let Some(info) = ChildInfo::resolve_child_info(request.child_type, &request.child_info[..]) {
match self.chain.read_child_proof(&block, &request.storage_key, info, &request.keys) {
Ok(proof) => proof,
Err(error) => {
log::trace!("remote read child request {} from {} ({} {} at {:?}) failed with: {}",
request_id,
peer,
request.storage_key.to_hex::(),
fmt_keys(request.keys.first(), request.keys.last()),
request.block,
error);
StorageProof::empty()
}
}
} else {
log::trace!("remote read child request {} from {} ({} {} at {:?}) failed with: {}",
request_id,
peer,
request.storage_key.to_hex::(),
fmt_keys(request.keys.first(), request.keys.last()),
request.block,
"invalid child info and type"
);
StorageProof::empty()
};
let response = {
let r = api::v1::light::RemoteReadResponse { proof: proof.encode() };
api::v1::light::response::Response::RemoteReadResponse(r)
};
Ok(api::v1::light::Response { id: request_id, response: Some(response) })
}
fn on_remote_header_request
( &mut self
, peer: &PeerId
, request_id: u64
, request: &api::v1::light::RemoteHeaderRequest
) -> Result
{
log::trace!("remote header proof request {} from {} ({:?})", request_id, peer, request.block);
let block = Decode::decode(&mut request.block.as_ref())?;
let (header, proof) = match self.chain.header_proof(block) {
Ok((header, proof)) => (header.encode(), proof),
Err(error) => {
log::trace!("remote header proof request {} from {} ({:?}) failed with: {}",
request_id,
peer,
request.block,
error);
(Default::default(), StorageProof::empty())
}
};
let response = {
let r = api::v1::light::RemoteHeaderResponse { header, proof: proof.encode() };
api::v1::light::response::Response::RemoteHeaderResponse(r)
};
Ok(api::v1::light::Response { id: request_id, response: Some(response) })
}
fn on_remote_changes_request
( &mut self
, peer: &PeerId
, request_id: u64
, request: &api::v1::light::RemoteChangesRequest
) -> Result
{
log::trace!("remote changes proof request {} from {} for key {} ({:?}..{:?})",
request_id,
peer,
if !request.storage_key.is_empty() {
format!("{} : {}", request.storage_key.to_hex::(), request.key.to_hex::())
} else {
request.key.to_hex::()
},
request.first,
request.last);
let first = Decode::decode(&mut request.first.as_ref())?;
let last = Decode::decode(&mut request.last.as_ref())?;
let min = Decode::decode(&mut request.min.as_ref())?;
let max = Decode::decode(&mut request.max.as_ref())?;
let key = StorageKey(request.key.clone());
let storage_key =
if request.storage_key.is_empty() {
None
} else {
Some(StorageKey(request.storage_key.clone()))
};
let proof = match self.chain.key_changes_proof(first, last, min, max, storage_key.as_ref(), &key) {
Ok(proof) => proof,
Err(error) => {
log::trace!("remote changes proof request {} from {} for key {} ({:?}..{:?}) failed with: {}",
request_id,
peer,
if let Some(sk) = storage_key {
format!("{} : {}", sk.0.to_hex::(), key.0.to_hex::())
} else {
key.0.to_hex::()
},
request.first,
request.last,
error);
fetcher::ChangesProof:: {
max_block: Zero::zero(),
proof: Vec::new(),
roots: BTreeMap::new(),
roots_proof: StorageProof::empty(),
}
}
};
let response = {
let r = api::v1::light::RemoteChangesResponse {
max: proof.max_block.encode(),
proof: proof.proof,
roots: proof.roots.into_iter()
.map(|(k, v)| api::v1::light::Pair { fst: k.encode(), snd: v.encode() })
.collect(),
roots_proof: proof.roots_proof.encode(),
};
api::v1::light::response::Response::RemoteChangesResponse(r)
};
Ok(api::v1::light::Response { id: request_id, response: Some(response) })
}
}
impl NetworkBehaviour for LightClientHandler
where
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
B: Block
{
type ProtocolsHandler = OneShotHandler>>;
type OutEvent = Void;
fn new_handler(&mut self) -> Self::ProtocolsHandler {
let p = InboundProtocol {
max_data_size: self.config.max_data_size,
protocol: self.config.protocol.clone(),
};
OneShotHandler::new(SubstreamProtocol::new(p), self.config.inactivity_timeout)
}
fn addresses_of_peer(&mut self, peer: &PeerId) -> Vec {
self.peers.get(peer)
.map(|info| vec![info.address.clone()])
.unwrap_or_default()
}
fn inject_connected(&mut self, peer: PeerId, info: ConnectedPoint) {
let peer_address = match info {
ConnectedPoint::Listener { send_back_addr, .. } => send_back_addr,
ConnectedPoint::Dialer { address } => address
};
log::trace!("peer {} connected with address {}", peer, peer_address);
let info = PeerInfo {
address: peer_address,
best_block: None,
status: PeerStatus::Idle,
};
self.peers.insert(peer, info);
}
fn inject_disconnected(&mut self, peer: &PeerId, _: ConnectedPoint) {
log::trace!("peer {} disconnected", peer);
self.remove_peer(peer)
}
fn inject_node_event(&mut self, peer: PeerId, event: Event>) {
match event {
// An incoming request from remote has been received.
Event::Request(request, mut stream) => {
log::trace!("incoming request {} from {}", peer, request.id);
let result = match &request.request {
Some(api::v1::light::request::Request::RemoteCallRequest(r)) =>
self.on_remote_call_request(&peer, request.id, r),
Some(api::v1::light::request::Request::RemoteReadRequest(r)) =>
self.on_remote_read_request(&peer, request.id, r),
Some(api::v1::light::request::Request::RemoteHeaderRequest(r)) =>
self.on_remote_header_request(&peer, request.id, r),
Some(api::v1::light::request::Request::RemoteReadChildRequest(r)) =>
self.on_remote_read_child_request(&peer, request.id, r),
Some(api::v1::light::request::Request::RemoteChangesRequest(r)) =>
self.on_remote_changes_request(&peer, request.id, r),
None => {
log::debug!("ignoring request {} without request data from peer {}", request.id, peer);
return
}
};
match result {
Ok(response) => {
log::trace!("enqueueing response {} for peer {}", response.id, peer);
let mut data = Vec::new();
if let Err(e) = response.encode(&mut data) {
log::debug!("error encoding response {} for peer {}: {}", response.id, peer, e)
} else {
let future = async move {
if let Err(e) = write_one(&mut stream, data).await {
log::debug!("error writing response: {}", e)
}
};
self.responses.push(future.boxed())
}
}
Err(Error::BadRequest(_)) => {
self.remove_peer(&peer);
self.peerset.report_peer(peer, ReputationChange::new(-(1 << 12), "bad request"))
}
Err(e) => log::debug!("error handling request {} from peer {}: {}", request.id, peer, e)
}
}
// A response to one of our own requests has been received.
Event::Response(response) => {
let id = response.id;
if let Some(request) = self.outstanding.remove(&id) {
// We first just check if the response originates from the expected peer.
if request.peer != peer {
log::debug!("was expecting response {} from {} instead of {}", id, request.peer, peer);
self.outstanding.insert(id, request);
self.remove_peer(&peer);
self.peerset.report_peer(peer, ReputationChange::new_fatal("response from unexpected peer"));
return
}
if let Some(info) = self.peers.get_mut(&peer) {
if info.status != PeerStatus::BusyWith(id) {
// If we get here, something is wrong with our internal handling of peer
// status information. At any time, a single peer processes at most one
// request from us and its status should contain the request ID we are
// expecting a response for. If a peer would send us a response with a
// random ID, we should not have an entry for it with this peer ID in
// our `outstanding` map, so a malicious peer should not be able to get
// us here. It is our own fault and must be fixed!
panic!("unexpected peer status {:?} for {}", info.status, peer);
}
info.status = PeerStatus::Idle; // Make peer available again.
match self.on_response(&peer, &request.request, response) {
Ok(reply) => send_reply(Ok(reply), request.request),
Err(Error::UnexpectedResponse) => {
log::debug!("unexpected response {} from peer {}", id, peer);
self.remove_peer(&peer);
self.peerset.report_peer(peer, ReputationChange::new_fatal("unexpected response from peer"));
let rw = RequestWrapper {
timestamp: request.timestamp,
retries: request.retries,
request: request.request,
peer: (),
};
self.pending_requests.push_back(rw);
}
Err(other) => {
log::debug!("error handling response {} from peer {}: {}", id, peer, other);
self.remove_peer(&peer);
self.peerset.report_peer(peer, ReputationChange::new_fatal("invalid response from peer"));
if request.retries > 0 {
let rw = RequestWrapper {
timestamp: request.timestamp,
retries: request.retries - 1,
request: request.request,
peer: (),
};
self.pending_requests.push_back(rw)
} else {
send_reply(Err(ClientError::RemoteFetchFailed), request.request)
}
}
}
} else {
// If we get here, something is wrong with our internal handling of peers.
// We apparently have an entry in our `outstanding` map and the peer is the one we
// expected. So, if we can not find an entry for it in our peer information table,
// then these two collections are out of sync which must not happen and is a clear
// programmer error that must be fixed!
panic!("missing peer information for {}; response {}", peer, id);
}
} else {
log::debug!("unexpected response {} from peer {}", id, peer);
self.remove_peer(&peer);
self.peerset.report_peer(peer, ReputationChange::new_fatal("response from unexpected peer"));
}
}
}
}
fn poll(&mut self, cx: &mut Context, _: &mut impl PollParameters) -> Poll> {
// Process response sending futures.
while let Poll::Ready(Some(_)) = self.responses.poll_next_unpin(cx) {}
// If we have a pending request to send, try to find an available peer and send it.
let now = Instant::now();
while let Some(mut request) = self.pending_requests.pop_front() {
if now > request.timestamp + self.config.request_timeout {
if request.retries == 0 {
send_reply(Err(ClientError::RemoteFetchFailed), request.request);
continue
}
request.timestamp = Instant::now();
request.retries -= 1
}
let number = required_block(&request.request);
let available_peer = {
let p = self.idle_peers_with_block(number).next();
if p.is_none() {
self.idle_peers_with_unknown_block().next()
} else {
p
}
};
if let Some(peer) = available_peer {
let id = self.next_request_id();
let rq = serialise_request(id, &request.request);
let mut buf = Vec::with_capacity(rq.encoded_len());
if let Err(e) = rq.encode(&mut buf) {
log::debug!("failed to serialise request {}: {}", id, e);
send_reply(Err(ClientError::RemoteFetchFailed), request.request)
} else {
log::trace!("sending request {} to peer {}", id, peer);
let protocol = OutboundProtocol {
request: buf,
max_data_size: self.config.max_data_size,
protocol: self.config.protocol.clone(),
};
self.peers.get_mut(&peer).map(|info| info.status = PeerStatus::BusyWith(id));
let rw = RequestWrapper {
timestamp: request.timestamp,
retries: request.retries,
request: request.request,
peer: peer.clone(),
};
self.outstanding.insert(id, rw);
return Poll::Ready(NetworkBehaviourAction::SendEvent { peer_id: peer, event: protocol })
}
} else {
self.pending_requests.push_front(request);
log::debug!("no peer available to send request to");
break
}
}
// Look for ongoing requests that have timed out.
let mut expired = Vec::new();
for (id, rw) in &self.outstanding {
if now > rw.timestamp + self.config.request_timeout {
log::debug!("request {} timed out", id);
expired.push(*id)
}
}
for id in expired {
if let Some(rw) = self.outstanding.remove(&id) {
self.remove_peer(&rw.peer);
self.peerset.report_peer(rw.peer.clone(),
ReputationChange::new(TIMEOUT_REPUTATION_CHANGE, "light request timeout"));
if rw.retries == 0 {
send_reply(Err(ClientError::RemoteFetchFailed), rw.request);
continue
}
let rw = RequestWrapper {
timestamp: Instant::now(),
retries: rw.retries - 1,
request: rw.request,
peer: (),
};
self.pending_requests.push_back(rw)
}
}
Poll::Pending
}
}
fn required_block(request: &Request) -> NumberFor {
match request {
Request::Header { request, .. } => request.block,
Request::Read { request, .. } => *request.header.number(),
Request::ReadChild { request, .. } => *request.header.number(),
Request::Call { request, .. } => *request.header.number(),
Request::Changes { request, .. } => request.max_block.0,
}
}
fn retries(request: &Request) -> usize {
let rc = match request {
Request::Header { request, .. } => request.retry_count,
Request::Read { request, .. } => request.retry_count,
Request::ReadChild { request, .. } => request.retry_count,
Request::Call { request, .. } => request.retry_count,
Request::Changes { request, .. } => request.retry_count,
};
rc.unwrap_or(0)
}
fn serialise_request(id: u64, request: &Request) -> api::v1::light::Request {
let request = match request {
Request::Header { request, .. } => {
let r = api::v1::light::RemoteHeaderRequest { block: request.block.encode() };
api::v1::light::request::Request::RemoteHeaderRequest(r)
}
Request::Read { request, .. } => {
let r = api::v1::light::RemoteReadRequest {
block: request.block.encode(),
keys: request.keys.clone(),
};
api::v1::light::request::Request::RemoteReadRequest(r)
}
Request::ReadChild { request, .. } => {
let r = api::v1::light::RemoteReadChildRequest {
block: request.block.encode(),
storage_key: request.storage_key.clone(),
child_type: request.child_type.clone(),
child_info: request.child_info.clone(),
keys: request.keys.clone(),
};
api::v1::light::request::Request::RemoteReadChildRequest(r)
}
Request::Call { request, .. } => {
let r = api::v1::light::RemoteCallRequest {
block: request.block.encode(),
method: request.method.clone(),
data: request.call_data.clone(),
};
api::v1::light::request::Request::RemoteCallRequest(r)
}
Request::Changes { request, .. } => {
let r = api::v1::light::RemoteChangesRequest {
first: request.first_block.1.encode(),
last: request.last_block.1.encode(),
min: request.tries_roots.1.encode(),
max: request.max_block.1.encode(),
storage_key: request.storage_key.clone().unwrap_or_default(),
key: request.key.clone(),
};
api::v1::light::request::Request::RemoteChangesRequest(r)
}
};
api::v1::light::Request { id, request: Some(request) }
}
fn send_reply(result: Result, ClientError>, request: Request) {
fn send(item: T, sender: oneshot::Sender) {
let _ = sender.send(item); // It is okay if the other end already hung up.
}
match request {
Request::Header { request, sender } => match result {
Err(e) => send(Err(e), sender),
Ok(Reply::Header(x)) => send(Ok(x), sender),
reply => log::error!("invalid reply for header request: {:?}, {:?}", reply, request),
}
Request::Read { request, sender } => match result {
Err(e) => send(Err(e), sender),
Ok(Reply::MapVecU8OptVecU8(x)) => send(Ok(x), sender),
reply => log::error!("invalid reply for read request: {:?}, {:?}", reply, request),
}
Request::ReadChild { request, sender } => match result {
Err(e) => send(Err(e), sender),
Ok(Reply::MapVecU8OptVecU8(x)) => send(Ok(x), sender),
reply => log::error!("invalid reply for read child request: {:?}, {:?}", reply, request),
}
Request::Call { request, sender } => match result {
Err(e) => send(Err(e), sender),
Ok(Reply::VecU8(x)) => send(Ok(x), sender),
reply => log::error!("invalid reply for call request: {:?}, {:?}", reply, request),
}
Request::Changes { request, sender } => match result {
Err(e) => send(Err(e), sender),
Ok(Reply::VecNumberU32(x)) => send(Ok(x), sender),
reply => log::error!("invalid reply for changes request: {:?}, {:?}", reply, request),
}
}
}
/// Output type of inbound and outbound substream upgrades.
#[derive(Debug)]
pub enum Event {
/// Incoming request from remote and substream to use for the response.
Request(api::v1::light::Request, T),
/// Incoming response from remote.
Response(api::v1::light::Response),
}
/// Substream upgrade protocol.
///
/// Reads incoming requests from remote.
#[derive(Debug, Clone)]
pub struct InboundProtocol {
/// The max. request length in bytes.
max_data_size: usize,
/// The protocol to use for upgrade negotiation.
protocol: Bytes,
}
impl UpgradeInfo for InboundProtocol {
type Info = Bytes;
type InfoIter = iter::Once;
fn protocol_info(&self) -> Self::InfoIter {
iter::once(self.protocol.clone())
}
}
impl InboundUpgrade for InboundProtocol
where
T: AsyncRead + AsyncWrite + Unpin + Send + 'static
{
type Output = Event;
type Error = ReadOneError;
type Future = BoxFuture<'static, Result>;
fn upgrade_inbound(self, mut s: T, _: Self::Info) -> Self::Future {
let future = async move {
let vec = read_one(&mut s, self.max_data_size).await?;
match api::v1::light::Request::decode(&vec[..]) {
Ok(r) => Ok(Event::Request(r, s)),
Err(e) => Err(ReadOneError::Io(io::Error::new(io::ErrorKind::Other, e)))
}
};
future.boxed()
}
}
/// Substream upgrade protocol.
///
/// Sends a request to remote and awaits the response.
#[derive(Debug, Clone)]
pub struct OutboundProtocol {
/// The serialised protobuf request.
request: Vec,
/// The max. request length in bytes.
max_data_size: usize,
/// The protocol to use for upgrade negotiation.
protocol: Bytes,
}
impl UpgradeInfo for OutboundProtocol {
type Info = Bytes;
type InfoIter = iter::Once;
fn protocol_info(&self) -> Self::InfoIter {
iter::once(self.protocol.clone())
}
}
impl OutboundUpgrade for OutboundProtocol
where
T: AsyncRead + AsyncWrite + Unpin + Send + 'static
{
type Output = Event;
type Error = ReadOneError;
type Future = BoxFuture<'static, Result>;
fn upgrade_outbound(self, mut s: T, _: Self::Info) -> Self::Future {
let future = async move {
write_one(&mut s, &self.request).await?;
let vec = read_one(&mut s, self.max_data_size).await?;
api::v1::light::Response::decode(&vec[..])
.map(Event::Response)
.map_err(|e| {
ReadOneError::Io(io::Error::new(io::ErrorKind::Other, e))
})
};
future.boxed()
}
}
fn fmt_keys(first: Option<&Vec>, last: Option<&Vec>) -> String {
if let (Some(first), Some(last)) = (first, last) {
if first == last {
first.to_hex::()
} else {
format!("{}..{}", first.to_hex::(), last.to_hex::())
}
} else {
String::from("n/a")
}
}
#[cfg(test)]
mod tests {
use async_std::task;
use assert_matches::assert_matches;
use codec::Encode;
use crate::{
chain::Client,
config::ProtocolId,
protocol::{api, light_dispatch::tests::{DummyFetchChecker, dummy_header}}
};
use futures::{channel::oneshot, prelude::*};
use libp2p::{
PeerId,
Multiaddr,
core::{
ConnectedPoint,
identity,
muxing::{StreamMuxerBox, SubstreamRef},
transport::{Transport, boxed::Boxed, memory::MemoryTransport},
upgrade
},
noise::{self, Keypair, X25519, NoiseConfig},
swarm::{NetworkBehaviour, NetworkBehaviourAction, PollParameters},
yamux
};
use sc_client_api::StorageProof;
use sc_client::light::fetcher;
use sp_blockchain::{Error as ClientError};
use sp_core::storage::ChildInfo;
use std::{
collections::HashSet,
io,
iter::{self, FromIterator},
pin::Pin,
sync::Arc,
task::{Context, Poll}
};
use sp_runtime::{generic::Header, traits::BlakeTwo256};
use super::{Event, LightClientHandler, Request, OutboundProtocol, PeerStatus};
use void::Void;
const CHILD_INFO: ChildInfo<'static> = ChildInfo::new_default(b"foobarbaz");
type Block = sp_runtime::generic::Block, substrate_test_runtime::Extrinsic>;
type Handler = LightClientHandler>, Block>;
type Swarm = libp2p::swarm::Swarm, Handler>;
fn empty_proof() -> Vec {
StorageProof::empty().encode()
}
fn make_swarm(ok: bool, ps: sc_peerset::PeersetHandle, cf: super::Config) -> Swarm {
let client = Arc::new(substrate_test_runtime_client::new());
let checker = Arc::new(DummyFetchChecker::new(ok));
let id_key = identity::Keypair::generate_ed25519();
let dh_key = Keypair::::new().into_authentic(&id_key).unwrap();
let local_peer = id_key.public().into_peer_id();
let transport = MemoryTransport::default()
.upgrade(upgrade::Version::V1)
.authenticate(NoiseConfig::xx(dh_key).into_authenticated())
.multiplex(yamux::Config::default())
.map(|(peer, muxer), _| (peer, StreamMuxerBox::new(muxer)))
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
.boxed();
Swarm::new(transport, LightClientHandler::new(cf, client, checker, ps), local_peer)
}
fn make_config() -> super::Config {
super::Config::new(&ProtocolId::from(&b"foo"[..]))
}
struct EmptyPollParams(PeerId);
impl PollParameters for EmptyPollParams {
type SupportedProtocolsIter = iter::Empty>;
type ListenedAddressesIter = iter::Empty;
type ExternalAddressesIter = iter::Empty;
fn supported_protocols(&self) -> Self::SupportedProtocolsIter {
iter::empty()
}
fn listened_addresses(&self) -> Self::ListenedAddressesIter {
iter::empty()
}
fn external_addresses(&self) -> Self::ExternalAddressesIter {
iter::empty()
}
fn local_peer_id(&self) -> &PeerId {
&self.0
}
}
fn peerset() -> (sc_peerset::Peerset, sc_peerset::PeersetHandle) {
let cfg = sc_peerset::PeersetConfig {
in_peers: 128,
out_peers: 128,
bootnodes: Vec::new(),
reserved_only: false,
reserved_nodes: Vec::new(),
};
sc_peerset::Peerset::from_config(cfg)
}
fn make_behaviour
( ok: bool
, ps: sc_peerset::PeersetHandle
, cf: super::Config
) -> LightClientHandler>, Block>
{
let client = Arc::new(substrate_test_runtime_client::new());
let checker = Arc::new(DummyFetchChecker::new(ok));
LightClientHandler::new(cf, client, checker, ps)
}
fn empty_dialer() -> ConnectedPoint {
ConnectedPoint::Dialer { address: Multiaddr::empty() }
}
fn poll(mut b: &mut LightClientHandler) -> Poll>
where
T: AsyncRead + AsyncWrite + Unpin + Send + 'static
{
let mut p = EmptyPollParams(PeerId::random());
match future::poll_fn(|cx| Pin::new(&mut b).poll(cx, &mut p)).now_or_never() {
Some(a) => Poll::Ready(a),
None => Poll::Pending
}
}
#[test]
fn disconnects_from_peer_if_told() {
let peer = PeerId::random();
let pset = peerset();
let mut behaviour = make_behaviour(true, pset.1, make_config());
behaviour.inject_connected(peer.clone(), empty_dialer());
assert_eq!(1, behaviour.peers.len());
behaviour.inject_disconnected(&peer, empty_dialer());
assert_eq!(0, behaviour.peers.len())
}
#[test]
fn disconnects_from_peer_if_request_times_out() {
let peer0 = PeerId::random();
let peer1 = PeerId::random();
let pset = peerset();
let mut behaviour = make_behaviour(true, pset.1, make_config());
behaviour.inject_connected(peer0.clone(), empty_dialer());
behaviour.inject_connected(peer1.clone(), empty_dialer());
// We now know about two peers.
assert_eq!(HashSet::from_iter(&[peer0.clone(), peer1.clone()]), behaviour.peers.keys().collect::>());
// No requests have been made yet.
assert!(behaviour.pending_requests.is_empty());
assert!(behaviour.outstanding.is_empty());
// Issue our first request!
let chan = oneshot::channel();
let request = fetcher::RemoteCallRequest {
block: Default::default(),
header: dummy_header(),
method: "test".into(),
call_data: vec![],
retry_count: Some(1),
};
behaviour.request(Request::Call { request, sender: chan.0 }).unwrap();
assert_eq!(1, behaviour.pending_requests.len());
// The behaviour should now attempt to send the request.
assert_matches!(poll(&mut behaviour), Poll::Ready(NetworkBehaviourAction::SendEvent { peer_id, .. }) => {
assert!(peer_id == peer0 || peer_id == peer1)
});
// And we should have one busy peer.
assert!({
let (idle, busy): (Vec<_>, Vec<_>) =
behaviour.peers.iter().partition(|(_, info)| info.status == PeerStatus::Idle);
idle.len() == 1 && busy.len() == 1
&& (idle[0].0 == &peer0 || busy[0].0 == &peer0)
&& (idle[0].0 == &peer1 || busy[0].0 == &peer1)
});
// No more pending requests, but one should be outstanding.
assert_eq!(0, behaviour.pending_requests.len());
assert_eq!(1, behaviour.outstanding.len());
// We now set back the timestamp of the outstanding request to make it expire.
let request = behaviour.outstanding.values_mut().next().unwrap();
request.timestamp -= make_config().request_timeout;
// Make progress, but do not expect some action.
assert_matches!(poll(&mut behaviour), Poll::Pending);
// The request should have timed out by now and the corresponding peer be removed.
assert_eq!(1, behaviour.peers.len());
// Since we asked for one retry, the request should be back in the pending queue.
assert_eq!(1, behaviour.pending_requests.len());
// No other request should be ongoing.
assert_eq!(0, behaviour.outstanding.len());
}
#[test]
fn disconnects_from_peer_on_response_with_wrong_id() {
let peer = PeerId::random();
let pset = peerset();
let mut behaviour = make_behaviour(true, pset.1, make_config());
behaviour.inject_connected(peer.clone(), empty_dialer());
assert_eq!(1, behaviour.peers.len());
let chan = oneshot::channel();
let request = fetcher::RemoteCallRequest {
block: Default::default(),
header: dummy_header(),
method: "test".into(),
call_data: vec![],
retry_count: Some(1),
};
behaviour.request(Request::Call { request, sender: chan.0 }).unwrap();
assert_eq!(1, behaviour.pending_requests.len());
assert_eq!(0, behaviour.outstanding.len());
poll(&mut behaviour); // Make progress
assert_eq!(0, behaviour.pending_requests.len());
assert_eq!(1, behaviour.outstanding.len());
// Construct response with bogus ID
let response = {
let r = api::v1::light::RemoteCallResponse { proof: empty_proof() };
api::v1::light::Response {
id: 2365789,
response: Some(api::v1::light::response::Response::RemoteCallResponse(r)),
}
};
// Make sure our bogus ID is really not used.
assert!(!behaviour.outstanding.keys().any(|id| id == &response.id));
behaviour.inject_node_event(peer.clone(), Event::Response(response));
assert!(behaviour.peers.is_empty());
poll(&mut behaviour); // More progress
// The request should be back in the pending queue
assert_eq!(1, behaviour.pending_requests.len());
assert_eq!(0, behaviour.outstanding.len());
}
#[test]
fn disconnects_from_peer_on_incorrect_response() {
let peer = PeerId::random();
let pset = peerset();
let mut behaviour = make_behaviour(false, pset.1, make_config());
// ^--- Making sure the response data check fails.
behaviour.inject_connected(peer.clone(), empty_dialer());
assert_eq!(1, behaviour.peers.len());
let chan = oneshot::channel();
let request = fetcher::RemoteCallRequest {
block: Default::default(),
header: dummy_header(),
method: "test".into(),
call_data: vec![],
retry_count: Some(1),
};
behaviour.request(Request::Call { request, sender: chan.0 }).unwrap();
assert_eq!(1, behaviour.pending_requests.len());
assert_eq!(0, behaviour.outstanding.len());
poll(&mut behaviour); // Make progress
assert_eq!(0, behaviour.pending_requests.len());
assert_eq!(1, behaviour.outstanding.len());
let request_id = *behaviour.outstanding.keys().next().unwrap();
let response = {
let r = api::v1::light::RemoteCallResponse { proof: empty_proof() };
api::v1::light::Response {
id: request_id,
response: Some(api::v1::light::response::Response::RemoteCallResponse(r)),
}
};
behaviour.inject_node_event(peer.clone(), Event::Response(response));
assert!(behaviour.peers.is_empty());
poll(&mut behaviour); // More progress
// The request should be back in the pending queue
assert_eq!(1, behaviour.pending_requests.len());
assert_eq!(0, behaviour.outstanding.len());
}
#[test]
fn disconnects_from_peer_on_unexpected_response() {
let peer = PeerId::random();
let pset = peerset();
let mut behaviour = make_behaviour(true, pset.1, make_config());
behaviour.inject_connected(peer.clone(), empty_dialer());
assert_eq!(1, behaviour.peers.len());
assert_eq!(0, behaviour.pending_requests.len());
assert_eq!(0, behaviour.outstanding.len());
// Some unsolicited response
let response = {
let r = api::v1::light::RemoteCallResponse { proof: empty_proof() };
api::v1::light::Response {
id: 2347895932,
response: Some(api::v1::light::response::Response::RemoteCallResponse(r)),
}
};
behaviour.inject_node_event(peer.clone(), Event::Response(response));
assert!(behaviour.peers.is_empty());
poll(&mut behaviour);
assert_eq!(0, behaviour.pending_requests.len());
assert_eq!(0, behaviour.outstanding.len());
}
#[test]
fn disconnects_from_peer_on_wrong_response_type() {
let peer = PeerId::random();
let pset = peerset();
let mut behaviour = make_behaviour(true, pset.1, make_config());
behaviour.inject_connected(peer.clone(), empty_dialer());
assert_eq!(1, behaviour.peers.len());
let chan = oneshot::channel();
let request = fetcher::RemoteCallRequest {
block: Default::default(),
header: dummy_header(),
method: "test".into(),
call_data: vec![],
retry_count: Some(1),
};
behaviour.request(Request::Call { request, sender: chan.0 }).unwrap();
assert_eq!(1, behaviour.pending_requests.len());
assert_eq!(0, behaviour.outstanding.len());
poll(&mut behaviour); // Make progress
assert_eq!(0, behaviour.pending_requests.len());
assert_eq!(1, behaviour.outstanding.len());
let request_id = *behaviour.outstanding.keys().next().unwrap();
let response = {
let r = api::v1::light::RemoteReadResponse { proof: empty_proof() }; // Not a RemoteCallResponse!
api::v1::light::Response {
id: request_id,
response: Some(api::v1::light::response::Response::RemoteReadResponse(r)),
}
};
behaviour.inject_node_event(peer.clone(), Event::Response(response));
assert!(behaviour.peers.is_empty());
poll(&mut behaviour); // More progress
// The request should be back in the pending queue
assert_eq!(1, behaviour.pending_requests.len());
assert_eq!(0, behaviour.outstanding.len());
}
#[test]
fn receives_remote_failure_after_retry_count_failures() {
let peer1 = PeerId::random();
let peer2 = PeerId::random();
let peer3 = PeerId::random();
let peer4 = PeerId::random();
let pset = peerset();
let mut behaviour = make_behaviour(false, pset.1, make_config());
// ^--- Making sure the response data check fails.
behaviour.inject_connected(peer1.clone(), empty_dialer());
behaviour.inject_connected(peer2.clone(), empty_dialer());
behaviour.inject_connected(peer3.clone(), empty_dialer());
behaviour.inject_connected(peer4.clone(), empty_dialer());
assert_eq!(4, behaviour.peers.len());
let mut chan = oneshot::channel();
let request = fetcher::RemoteCallRequest {
block: Default::default(),
header: dummy_header(),
method: "test".into(),
call_data: vec![],
retry_count: Some(3), // Attempt up to three retries.
};
behaviour.request(Request::Call { request, sender: chan.0 }).unwrap();
assert_eq!(1, behaviour.pending_requests.len());
assert_eq!(0, behaviour.outstanding.len());
assert_matches!(poll(&mut behaviour), Poll::Ready(NetworkBehaviourAction::SendEvent { .. }));
assert_eq!(0, behaviour.pending_requests.len());
assert_eq!(1, behaviour.outstanding.len());
for _ in 0 .. 3 {
// Construct an invalid response
let request_id = *behaviour.outstanding.keys().next().unwrap();
let responding_peer = behaviour.outstanding.values().next().unwrap().peer.clone();
let response = {
let r = api::v1::light::RemoteCallResponse { proof: empty_proof() };
api::v1::light::Response {
id: request_id,
response: Some(api::v1::light::response::Response::RemoteCallResponse(r))
}
};
behaviour.inject_node_event(responding_peer, Event::Response(response.clone()));
assert_matches!(poll(&mut behaviour), Poll::Ready(NetworkBehaviourAction::SendEvent { .. }));
assert_matches!(chan.1.try_recv(), Ok(None))
}
// Final invalid response
let request_id = *behaviour.outstanding.keys().next().unwrap();
let responding_peer = behaviour.outstanding.values().next().unwrap().peer.clone();
let response = {
let r = api::v1::light::RemoteCallResponse { proof: empty_proof() };
api::v1::light::Response {
id: request_id,
response: Some(api::v1::light::response::Response::RemoteCallResponse(r)),
}
};
behaviour.inject_node_event(responding_peer, Event::Response(response));
assert_matches!(poll(&mut behaviour), Poll::Pending);
assert_matches!(chan.1.try_recv(), Ok(Some(Err(ClientError::RemoteFetchFailed))))
}
fn issue_request(request: Request) {
let peer = PeerId::random();
let pset = peerset();
let mut behaviour = make_behaviour(true, pset.1, make_config());
behaviour.inject_connected(peer.clone(), empty_dialer());
assert_eq!(1, behaviour.peers.len());
let response = match request {
Request::Header{..} => {
let r = api::v1::light::RemoteHeaderResponse {
header: dummy_header().encode(),
proof: empty_proof()
};
api::v1::light::Response {
id: 1,
response: Some(api::v1::light::response::Response::RemoteHeaderResponse(r)),
}
}
Request::Read{..} => {
let r = api::v1::light::RemoteReadResponse { proof: empty_proof() };
api::v1::light::Response {
id: 1,
response: Some(api::v1::light::response::Response::RemoteReadResponse(r)),
}
}
Request::ReadChild{..} => {
let r = api::v1::light::RemoteReadResponse { proof: empty_proof() };
api::v1::light::Response {
id: 1,
response: Some(api::v1::light::response::Response::RemoteReadResponse(r)),
}
}
Request::Call{..} => {
let r = api::v1::light::RemoteCallResponse { proof: empty_proof() };
api::v1::light::Response {
id: 1,
response: Some(api::v1::light::response::Response::RemoteCallResponse(r)),
}
}
Request::Changes{..} => {
let r = api::v1::light::RemoteChangesResponse {
max: iter::repeat(1).take(32).collect(),
proof: Vec::new(),
roots: Vec::new(),
roots_proof: empty_proof()
};
api::v1::light::Response {
id: 1,
response: Some(api::v1::light::response::Response::RemoteChangesResponse(r)),
}
}
};
behaviour.request(request).unwrap();
assert_eq!(1, behaviour.pending_requests.len());
assert_eq!(0, behaviour.outstanding.len());
assert_matches!(poll(&mut behaviour), Poll::Ready(NetworkBehaviourAction::SendEvent { .. }));
assert_eq!(0, behaviour.pending_requests.len());
assert_eq!(1, behaviour.outstanding.len());
assert_eq!(1, *behaviour.outstanding.keys().next().unwrap());
behaviour.inject_node_event(peer.clone(), Event::Response(response));
poll(&mut behaviour);
assert_eq!(0, behaviour.pending_requests.len());
assert_eq!(0, behaviour.outstanding.len())
}
#[test]
fn receives_remote_call_response() {
let mut chan = oneshot::channel();
let request = fetcher::RemoteCallRequest {
block: Default::default(),
header: dummy_header(),
method: "test".into(),
call_data: vec![],
retry_count: None,
};
issue_request(Request::Call { request, sender: chan.0 });
assert_matches!(chan.1.try_recv(), Ok(Some(Ok(_))))
}
#[test]
fn receives_remote_read_response() {
let mut chan = oneshot::channel();
let request = fetcher::RemoteReadRequest {
header: dummy_header(),
block: Default::default(),
keys: vec![b":key".to_vec()],
retry_count: None,
};
issue_request(Request::Read { request, sender: chan.0 });
assert_matches!(chan.1.try_recv(), Ok(Some(Ok(_))))
}
#[test]
fn receives_remote_read_child_response() {
let info = CHILD_INFO.info();
let mut chan = oneshot::channel();
let request = fetcher::RemoteReadChildRequest {
header: dummy_header(),
block: Default::default(),
storage_key: b":child_storage:sub".to_vec(),
keys: vec![b":key".to_vec()],
child_info: info.0.to_vec(),
child_type: info.1,
retry_count: None,
};
issue_request(Request::ReadChild { request, sender: chan.0 });
assert_matches!(chan.1.try_recv(), Ok(Some(Ok(_))))
}
#[test]
fn receives_remote_header_response() {
let mut chan = oneshot::channel();
let request = fetcher::RemoteHeaderRequest {
cht_root: Default::default(),
block: 1,
retry_count: None,
};
issue_request(Request::Header { request, sender: chan.0 });
assert_matches!(chan.1.try_recv(), Ok(Some(Ok(_))))
}
#[test]
fn receives_remote_changes_response() {
let mut chan = oneshot::channel();
let request = fetcher::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::new()),
key: Vec::new(),
storage_key: None,
retry_count: None,
};
issue_request(Request::Changes { request, sender: chan.0 });
assert_matches!(chan.1.try_recv(), Ok(Some(Ok(_))))
}
fn send_receive(request: Request) {
// We start a swarm on the listening side which awaits incoming requests and answers them:
let local_pset = peerset();
let local_listen_addr: libp2p::Multiaddr = libp2p::multiaddr::Protocol::Memory(rand::random()).into();
let mut local_swarm = make_swarm(true, local_pset.1, make_config());
Swarm::listen_on(&mut local_swarm, local_listen_addr.clone()).unwrap();
// We also start a swarm that makes requests and awaits responses:
let remote_pset = peerset();
let mut remote_swarm = make_swarm(true, remote_pset.1, make_config());
// We now schedule a request, dial the remote and let the two swarm work it out:
remote_swarm.request(request).unwrap();
Swarm::dial_addr(&mut remote_swarm, local_listen_addr).unwrap();
let future = {
let a = local_swarm.for_each(|_| future::ready(()));
let b = remote_swarm.for_each(|_| future::ready(()));
future::join(a, b).map(|_| ())
};
task::spawn(future);
}
#[test]
fn send_receive_call() {
let chan = oneshot::channel();
let request = fetcher::RemoteCallRequest {
block: Default::default(),
header: dummy_header(),
method: "test".into(),
call_data: vec![],
retry_count: None,
};
send_receive(Request::Call { request, sender: chan.0 });
assert_eq!(vec![42], task::block_on(chan.1).unwrap().unwrap());
// ^--- from `DummyFetchChecker::check_execution_proof`
}
#[test]
fn send_receive_read() {
let chan = oneshot::channel();
let request = fetcher::RemoteReadRequest {
header: dummy_header(),
block: Default::default(),
keys: vec![b":key".to_vec()],
retry_count: None
};
send_receive(Request::Read { request, sender: chan.0 });
assert_eq!(Some(vec![42]), task::block_on(chan.1).unwrap().unwrap().remove(&b":key"[..]).unwrap());
// ^--- from `DummyFetchChecker::check_read_proof`
}
#[test]
fn send_receive_read_child() {
let info = CHILD_INFO.info();
let chan = oneshot::channel();
let request = fetcher::RemoteReadChildRequest {
header: dummy_header(),
block: Default::default(),
storage_key: b":child_storage:sub".to_vec(),
keys: vec![b":key".to_vec()],
child_info: info.0.to_vec(),
child_type: info.1,
retry_count: None,
};
send_receive(Request::ReadChild { request, sender: chan.0 });
assert_eq!(Some(vec![42]), task::block_on(chan.1).unwrap().unwrap().remove(&b":key"[..]).unwrap());
// ^--- from `DummyFetchChecker::check_read_child_proof`
}
#[test]
fn send_receive_header() {
let _ = env_logger::try_init();
let chan = oneshot::channel();
let request = fetcher::RemoteHeaderRequest {
cht_root: Default::default(),
block: 1,
retry_count: None,
};
send_receive(Request::Header { request, sender: chan.0 });
// The remote does not know block 1:
assert_matches!(task::block_on(chan.1).unwrap(), Err(ClientError::RemoteFetchFailed));
}
#[test]
fn send_receive_changes() {
let chan = oneshot::channel();
let request = fetcher::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::new()),
key: Vec::new(),
storage_key: None,
retry_count: None,
};
send_receive(Request::Changes { request, sender: chan.0 });
assert_eq!(vec![(100, 2)], task::block_on(chan.1).unwrap().unwrap());
// ^--- from `DummyFetchChecker::check_changes_proof`
}
}