client/network: Use request response for block requests (#7478)

* client/network: Add scaffolding for finality req to use req resp
	#sc

* client/network/src/finality_requests: Remove

* client/network/src/behaviour: Pass request id down to sync

* client/network: Use request response for block requests

* client/network: Move handler logic into *_*_handler.rs

* client/network: Track ongoing finality requests in protocol.rs

* client/network: Remove commented out finalization initialization

* client/network: Add docs for request handlers

* client/network/finality_request_handler: Log errors

* client/network/block_request_handler: Log errors

* client/network: Format

* client/network: Handle block request failure

* protocols/network: Fix tests

* client/network/src/behaviour: Handle request sending errors

* client/network: Move response handling into custom method

* client/network/protocol: Handle block response errors

* client/network/protocol: Remove tracking of obsolete requests

* client/network/protocol: Remove block request start time tracking

This will be handled generically via request-responses.

* client/network/protocol: Refactor on_*_request_started

* client/network: Pass protocol config instead of protocol name

* client/network: Pass protocol config in tests

* client/network/config: Document request response configs

* client/network/src/_request_handler: Document protocol config gen

* client/network/src/protocol: Document Peer request values

* client/network: Rework request response to always use oneshot

* client/network: Unified metric reporting for all request protocols

* client/network: Move protobuf parsing into protocol.rs

* client/network/src/protocol: Return pending events after poll

* client/network: Improve error handling and documentation

* client/network/behaviour: Remove outdated error types

* Update client/network/src/block_request_handler.rs

Co-authored-by: Ashley <ashley.ruglys@gmail.com>

* Update client/network/src/finality_request_handler.rs

Co-authored-by: Ashley <ashley.ruglys@gmail.com>

* client/network/protocol: Reduce reputation on timeout

* client/network/protocol: Refine reputation changes

* client/network/block_request_handler: Set and explain queue length

* client/service: Deny block requests when light client

* client/service: Fix role matching

* client: Enforce line width

* client/network/request_responses: Fix unit tests

* client/network: Expose time to build response via metrics

* client/network/request_responses: Fix early connection closed error

* client/network/protocol: Fix line length

* client/network/protocol: Disconnect on most request failures

* client/network/protocol: Disconnect peer when oneshot is canceled

* client/network/protocol: Disconnect peer even when connection closed

* client/network/protocol: Remove debugging log line

* client/network/request_response: Use Clone::clone for error

* client/network/request_response: Remove outdated comment

With libp2p v0.33.0 libp2p-request-response properly sends inbound
failures on connections being closed.

Co-authored-by: Addie Wagenknecht <addie@nortd.com>
Co-authored-by: Ashley <ashley.ruglys@gmail.com>
This commit is contained in:
Max Inden
2021-01-05 19:20:54 +01:00
committed by GitHub
parent 92f596829d
commit 3f629f743b
17 changed files with 780 additions and 1299 deletions
+129 -47
View File
@@ -137,11 +137,17 @@ pub enum Event {
/// A request initiated using [`RequestResponsesBehaviour::send_request`] has succeeded or
/// failed.
///
/// This event is generated for statistics purposes.
RequestFinished {
/// Request that has succeeded.
request_id: RequestId,
/// Response sent by the remote or reason for failure.
result: Result<Vec<u8>, RequestFailure>,
/// Peer that we send a request to.
peer: PeerId,
/// Name of the protocol in question.
protocol: Cow<'static, str>,
/// Duration the request took.
duration: Duration,
/// Result of the request.
result: Result<(), RequestFailure>
},
}
@@ -155,8 +161,11 @@ pub struct RequestResponsesBehaviour {
(RequestResponse<GenericCodec>, Option<mpsc::Sender<IncomingRequest>>)
>,
/// Pending requests, passed down to a [`RequestResponse`] behaviour, awaiting a reply.
pending_requests: HashMap<RequestId, (Instant, oneshot::Sender<Result<Vec<u8>, RequestFailure>>)>,
/// Whenever an incoming request arrives, a `Future` is added to this list and will yield the
/// response to send back to the remote.
/// start time and the response to send back to the remote.
pending_responses: stream::FuturesUnordered<
Pin<Box<dyn Future<Output = Option<RequestProcessingOutcome>> + Send>>
>,
@@ -203,6 +212,7 @@ impl RequestResponsesBehaviour {
Ok(Self {
protocols,
pending_requests: Default::default(),
pending_responses: Default::default(),
pending_responses_arrival_time: Default::default(),
})
@@ -212,17 +222,36 @@ impl RequestResponsesBehaviour {
///
/// An error is returned if we are not connected to the target peer or if the protocol doesn't
/// match one that has been registered.
pub fn send_request(&mut self, target: &PeerId, protocol: &str, request: Vec<u8>)
-> Result<RequestId, SendRequestError>
{
pub fn send_request(
&mut self,
target: &PeerId,
protocol: &str,
request: Vec<u8>,
pending_response: oneshot::Sender<Result<Vec<u8>, RequestFailure>>,
) {
if let Some((protocol, _)) = self.protocols.get_mut(protocol) {
if protocol.is_connected(target) {
Ok(protocol.send_request(target, request))
let request_id = protocol.send_request(target, request);
self.pending_requests.insert(request_id, (Instant::now(), pending_response));
} else {
Err(SendRequestError::NotConnected)
if pending_response.send(Err(RequestFailure::NotConnected)).is_err() {
log::debug!(
target: "sub-libp2p",
"Not connected to peer {:?}. At the same time local \
node is no longer interested in the result.",
target,
);
};
}
} else {
Err(SendRequestError::UnknownProtocol)
if pending_response.send(Err(RequestFailure::UnknownProtocol)).is_err() {
log::debug!(
target: "sub-libp2p",
"Unknown protocol {:?}. At the same time local \
node is no longer interested in the result.",
protocol,
);
};
}
}
}
@@ -440,6 +469,8 @@ impl NetworkBehaviour for RequestResponsesBehaviour {
payload: request,
pending_response: tx,
});
} else {
debug_assert!(false, "Received message on outbound-only protocol.");
}
let protocol = protocol.clone();
@@ -463,29 +494,80 @@ impl NetworkBehaviour for RequestResponsesBehaviour {
// Received a response from a remote to one of our requests.
RequestResponseEvent::Message {
peer,
message: RequestResponseMessage::Response {
request_id,
response,
},
..
} => {
let out = Event::RequestFinished {
request_id,
result: response.map_err(|()| RequestFailure::Refused),
let (started, delivered) = match self.pending_requests.remove(&request_id) {
Some((started, pending_response)) => {
let delivered = pending_response.send(
response.map_err(|()| RequestFailure::Refused),
).map_err(|_| RequestFailure::Obsolete);
(started, delivered)
}
None => {
log::warn!(
target: "sub-libp2p",
"Received `RequestResponseEvent::Message` with unexpected request id {:?}",
request_id,
);
debug_assert!(false);
continue;
}
};
let out = Event::RequestFinished {
peer,
protocol: protocol.clone(),
duration: started.elapsed(),
result: delivered,
};
return Poll::Ready(NetworkBehaviourAction::GenerateEvent(out));
}
// One of our requests has failed.
RequestResponseEvent::OutboundFailure {
peer,
request_id,
error,
..
} => {
let started = match self.pending_requests.remove(&request_id) {
Some((started, pending_response)) => {
if pending_response.send(
Err(RequestFailure::Network(error.clone())),
).is_err() {
log::debug!(
target: "sub-libp2p",
"Request with id {:?} failed. At the same time local \
node is no longer interested in the result.",
request_id,
);
}
started
}
None => {
log::warn!(
target: "sub-libp2p",
"Received `RequestResponseEvent::Message` with unexpected request id {:?}",
request_id,
);
debug_assert!(false);
continue;
}
};
let out = Event::RequestFinished {
request_id,
peer,
protocol: protocol.clone(),
duration: started.elapsed(),
result: Err(RequestFailure::Network(error)),
};
return Poll::Ready(NetworkBehaviourAction::GenerateEvent(out));
}
@@ -529,21 +611,18 @@ pub enum RegisterError {
DuplicateProtocol(#[error(ignore)] Cow<'static, str>),
}
/// Error when sending a request.
/// Error in a request.
#[derive(Debug, derive_more::Display, derive_more::Error)]
pub enum SendRequestError {
pub enum RequestFailure {
/// We are not currently connected to the requested peer.
NotConnected,
/// Given protocol hasn't been registered.
UnknownProtocol,
}
/// Error in a request.
#[derive(Debug, derive_more::Display, derive_more::Error)]
pub enum RequestFailure {
/// Remote has closed the substream before answering, thereby signaling that it considers the
/// request as valid, but refused to answer it.
Refused,
/// The remote replied, but the local node is no longer interested in the response.
Obsolete,
/// Problem on the network.
#[display(fmt = "Problem on the network")]
Network(#[error(ignore)] OutboundFailure),
@@ -685,7 +764,7 @@ impl RequestResponseCodec for GenericCodec {
#[cfg(test)]
mod tests {
use futures::channel::mpsc;
use futures::channel::{mpsc, oneshot};
use futures::executor::LocalPool;
use futures::prelude::*;
use futures::task::Spawn;
@@ -771,31 +850,32 @@ mod tests {
// Remove and run the remaining swarm.
let (mut swarm, _) = swarms.remove(0);
pool.run_until(async move {
let mut sent_request_id = None;
let mut response_receiver = None;
loop {
match swarm.next_event().await {
SwarmEvent::ConnectionEstablished { peer_id, .. } => {
let id = swarm.send_request(
let (sender, receiver) = oneshot::channel();
swarm.send_request(
&peer_id,
protocol_name,
b"this is a request".to_vec()
).unwrap();
assert!(sent_request_id.is_none());
sent_request_id = Some(id);
b"this is a request".to_vec(),
sender,
);
assert!(response_receiver.is_none());
response_receiver = Some(receiver);
}
SwarmEvent::Behaviour(super::Event::RequestFinished {
request_id,
result,
result, ..
}) => {
assert_eq!(Some(request_id), sent_request_id);
let result = result.unwrap();
assert_eq!(result, b"this is a response");
result.unwrap();
break;
}
_ => {}
}
}
assert_eq!(response_receiver.unwrap().await.unwrap().unwrap(), b"this is a response");
});
}
@@ -875,33 +955,35 @@ mod tests {
// Remove and run the remaining swarm.
let (mut swarm, _) = swarms.remove(0);
pool.run_until(async move {
let mut sent_request_id = None;
let mut response_receiver = None;
loop {
match swarm.next_event().await {
SwarmEvent::ConnectionEstablished { peer_id, .. } => {
let id = swarm.send_request(
let (sender, receiver) = oneshot::channel();
swarm.send_request(
&peer_id,
protocol_name,
b"this is a request".to_vec()
).unwrap();
assert!(sent_request_id.is_none());
sent_request_id = Some(id);
b"this is a request".to_vec(),
sender,
);
assert!(response_receiver.is_none());
response_receiver = Some(receiver);
}
SwarmEvent::Behaviour(super::Event::RequestFinished {
request_id,
result,
result, ..
}) => {
assert_eq!(Some(request_id), sent_request_id);
match result {
Err(super::RequestFailure::Network(super::OutboundFailure::ConnectionClosed)) => {},
_ => panic!()
}
assert!(result.is_err());
break;
}
_ => {}
}
}
match response_receiver.unwrap().await.unwrap().unwrap_err() {
super::RequestFailure::Network(super::OutboundFailure::ConnectionClosed) => {},
_ => panic!()
}
});
}
}