mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-08 22:57:21 +00:00
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:
@@ -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!()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user