Remove request multiplexer (#3624)

* WIP: Get rid of request multiplexer.

* WIP

* Receiver for handling of incoming requests.

* Get rid of useless `Fault` abstraction.

The things the type system let us do are not worth getting abstracted in
its own type. Instead error handling is going to be merely a pattern.

* Make most things compile again.

* Port availability distribution away from request multiplexer.

* Formatting.

* Port dispute distribution over.

* Fixup statement distribution.

* Handle request directly in collator protocol.

+ Only allow fatal errors at top level.

* Use direct request channel for availability recovery.

* Finally get rid of request multiplexer

Fixes #2842 and paves the way for more back pressure possibilities.

* Fix overseer and statement distribution tests.

* Fix collator protocol and network bridge tests.

* Fix tests in availability recovery.

* Fix availability distribution tests.

* Fix dispute distribution tests.

* Add missing dependency

* Typos.

* Review remarks.

* More remarks.
This commit is contained in:
Robert Klotzner
2021-08-12 13:11:36 +02:00
committed by GitHub
parent ecf71233c3
commit 55154a8d37
51 changed files with 1509 additions and 1746 deletions
@@ -19,32 +19,25 @@
use thiserror::Error;
use polkadot_node_subsystem_util::{runtime, unwrap_non_fatal, Fault};
use polkadot_node_subsystem_util::runtime;
use polkadot_subsystem::SubsystemError;
use crate::{sender, LOG_TARGET};
#[derive(Debug, Error)]
#[derive(Debug, Error, derive_more::From)]
#[error(transparent)]
pub struct Error(pub Fault<NonFatal, Fatal>);
impl From<NonFatal> for Error {
fn from(e: NonFatal) -> Self {
Self(Fault::from_non_fatal(e))
}
}
impl From<Fatal> for Error {
fn from(f: Fatal) -> Self {
Self(Fault::from_fatal(f))
}
pub enum Error {
/// Fatal errors of dispute distribution.
Fatal(Fatal),
/// Non fatal errors of dispute distribution.
NonFatal(NonFatal),
}
impl From<sender::Error> for Error {
fn from(e: sender::Error) -> Self {
match e.0 {
Fault::Fatal(f) => Self(Fault::Fatal(Fatal::Sender(f))),
Fault::Err(nf) => Self(Fault::Err(NonFatal::Sender(nf))),
fn from(o: sender::Error) -> Self {
match o {
sender::Error::Fatal(f) => Self::Fatal(Fatal::Sender(f)),
sender::Error::NonFatal(f) => Self::NonFatal(NonFatal::Sender(f)),
}
}
}
@@ -90,8 +83,12 @@ pub type FatalResult<T> = std::result::Result<T, Fatal>;
/// We basically always want to try and continue on error. This utility function is meant to
/// consume top-level errors by simply logging them
pub fn log_error(result: Result<()>, ctx: &'static str) -> std::result::Result<(), Fatal> {
if let Some(error) = unwrap_non_fatal(result.map_err(|e| e.0))? {
tracing::warn!(target: LOG_TARGET, error = ?error, ctx);
match result {
Err(Error::Fatal(f)) => Err(f),
Err(Error::NonFatal(error)) => {
tracing::warn!(target: LOG_TARGET, error = ?error, ctx);
Ok(())
},
Ok(()) => Ok(()),
}
Ok(())
}
@@ -29,6 +29,7 @@ use futures::{channel::mpsc, FutureExt, StreamExt, TryFutureExt};
use polkadot_node_network_protocol::authority_discovery::AuthorityDiscovery;
use sp_keystore::SyncCryptoStorePtr;
use polkadot_node_network_protocol::request_response::{incoming::IncomingRequestReceiver, v1};
use polkadot_node_primitives::DISPUTE_WINDOW;
use polkadot_node_subsystem_util::{runtime, runtime::RuntimeInfo};
use polkadot_subsystem::{
@@ -103,6 +104,9 @@ pub struct DisputeDistributionSubsystem<AD> {
/// Receive messages from `SendTask`.
sender_rx: mpsc::Receiver<TaskFinish>,
/// Receiver for incoming requests.
req_receiver: Option<IncomingRequestReceiver<v1::DisputeRequest>>,
/// Authority discovery service.
authority_discovery: AD,
@@ -133,14 +137,26 @@ where
AD: AuthorityDiscovery + Clone,
{
/// Create a new instance of the availability distribution.
pub fn new(keystore: SyncCryptoStorePtr, authority_discovery: AD, metrics: Metrics) -> Self {
pub fn new(
keystore: SyncCryptoStorePtr,
req_receiver: IncomingRequestReceiver<v1::DisputeRequest>,
authority_discovery: AD,
metrics: Metrics,
) -> Self {
let runtime = RuntimeInfo::new_with_config(runtime::Config {
keystore: Some(keystore),
session_cache_lru_size: DISPUTE_WINDOW as usize,
});
let (tx, sender_rx) = mpsc::channel(1);
let disputes_sender = DisputeSender::new(tx, metrics.clone());
Self { runtime, disputes_sender, sender_rx, authority_discovery, metrics }
Self {
runtime,
disputes_sender,
sender_rx,
req_receiver: Some(req_receiver),
authority_discovery,
metrics,
}
}
/// Start processing work as passed on from the Overseer.
@@ -151,6 +167,17 @@ where
+ Sync
+ Send,
{
let receiver = DisputesReceiver::new(
ctx.sender().clone(),
self.req_receiver
.take()
.expect("Must be provided on `new` and we take ownership here. qed."),
self.authority_discovery.clone(),
self.metrics.clone(),
);
ctx.spawn("disputes-receiver", receiver.run().boxed())
.map_err(Fatal::SpawnTask)?;
loop {
let message = MuxedMessage::receive(&mut ctx, &mut self.sender_rx).await;
match message {
@@ -202,18 +229,6 @@ where
match msg {
DisputeDistributionMessage::SendDispute(dispute_msg) =>
self.disputes_sender.start_sender(ctx, &mut self.runtime, dispute_msg).await?,
// This message will only arrive once:
DisputeDistributionMessage::DisputeSendingReceiver(receiver) => {
let receiver = DisputesReceiver::new(
ctx.sender().clone(),
receiver,
self.authority_discovery.clone(),
self.metrics.clone(),
);
ctx.spawn("disputes-receiver", receiver.run().boxed())
.map_err(Fatal::SpawnTask)?;
},
}
Ok(())
}
@@ -19,43 +19,48 @@
use thiserror::Error;
use polkadot_node_network_protocol::{request_response::request::ReceiveError, PeerId};
use polkadot_node_subsystem_util::{runtime, unwrap_non_fatal, Fault};
use polkadot_node_network_protocol::{request_response::incoming, PeerId};
use polkadot_node_subsystem_util::runtime;
use crate::LOG_TARGET;
#[derive(Debug, Error)]
#[derive(Debug, Error, derive_more::From)]
#[error(transparent)]
pub struct Error(pub Fault<NonFatal, Fatal>);
impl From<NonFatal> for Error {
fn from(e: NonFatal) -> Self {
Self(Fault::from_non_fatal(e))
}
}
impl From<Fatal> for Error {
fn from(f: Fatal) -> Self {
Self(Fault::from_fatal(f))
}
pub enum Error {
/// All fatal errors.
Fatal(Fatal),
/// All nonfatal/potentially recoverable errors.
NonFatal(NonFatal),
}
impl From<runtime::Error> for Error {
fn from(o: runtime::Error) -> Self {
Self(Fault::from_other(o))
match o {
runtime::Error::Fatal(f) => Self::Fatal(Fatal::Runtime(f)),
runtime::Error::NonFatal(f) => Self::NonFatal(NonFatal::Runtime(f)),
}
}
}
impl From<incoming::Error> for Error {
fn from(o: incoming::Error) -> Self {
match o {
incoming::Error::Fatal(f) => Self::Fatal(Fatal::IncomingRequest(f)),
incoming::Error::NonFatal(f) => Self::NonFatal(NonFatal::IncomingRequest(f)),
}
}
}
/// Fatal errors of this subsystem.
#[derive(Debug, Error)]
pub enum Fatal {
/// Request channel returned `None`. Likely a system shutdown.
#[error("Request channel stream finished.")]
RequestChannelFinished,
/// Errors coming from runtime::Runtime.
#[error("Error while accessing runtime information")]
Runtime(#[from] runtime::Fatal),
/// Errors coming from receiving incoming requests.
#[error("Retrieving next incoming request failed.")]
IncomingRequest(#[from] incoming::Fatal),
}
/// Non-fatal errors of this subsystem.
@@ -65,10 +70,6 @@ pub enum NonFatal {
#[error("Sending back response to peer {0} failed.")]
SendResponse(PeerId),
/// Getting request from raw request failed.
#[error("Decoding request failed.")]
FromRawRequest(#[source] ReceiveError),
/// Setting reputation for peer failed.
#[error("Changing peer's ({0}) reputation failed.")]
SetPeerReputation(PeerId),
@@ -88,20 +89,27 @@ pub enum NonFatal {
/// Errors coming from runtime::Runtime.
#[error("Error while accessing runtime information")]
Runtime(#[from] runtime::NonFatal),
/// Errors coming from receiving incoming requests.
#[error("Retrieving next incoming request failed.")]
IncomingRequest(#[from] incoming::NonFatal),
}
pub type Result<T> = std::result::Result<T, Error>;
pub type FatalResult<T> = std::result::Result<T, Fatal>;
pub type NonFatalResult<T> = std::result::Result<T, NonFatal>;
/// Utility for eating top level errors and log them.
///
/// We basically always want to try and continue on error. This utility function is meant to
/// consume top-level errors by simply logging them
/// consume top-level errors by simply logging them.
pub fn log_error(result: Result<()>) -> std::result::Result<(), Fatal> {
if let Some(error) = unwrap_non_fatal(result.map_err(|e| e.0))? {
tracing::warn!(target: LOG_TARGET, error = ?error);
match result {
Err(Error::Fatal(f)) => Err(f),
Err(Error::NonFatal(error)) => {
tracing::warn!(target: LOG_TARGET, error = ?error);
Ok(())
},
Ok(()) => Ok(()),
}
Ok(())
}
@@ -21,19 +21,20 @@ use std::{
};
use futures::{
channel::{mpsc, oneshot},
channel::oneshot,
future::{poll_fn, BoxFuture},
pin_mut,
stream::{FusedStream, FuturesUnordered, StreamExt},
FutureExt, Stream,
Future, FutureExt, Stream,
};
use lru::LruCache;
use polkadot_node_network_protocol::{
authority_discovery::AuthorityDiscovery,
request_response::{
request::{OutgoingResponse, OutgoingResponseSender},
incoming::{OutgoingResponse, OutgoingResponseSender},
v1::{DisputeRequest, DisputeResponse},
IncomingRequest,
IncomingRequest, IncomingRequestReceiver,
},
PeerId, UnifiedReputationChange as Rep,
};
@@ -50,7 +51,7 @@ use crate::{
};
mod error;
use self::error::{log_error, Fatal, FatalResult, NonFatal, NonFatalResult, Result};
use self::error::{log_error, NonFatal, NonFatalResult, Result};
const COST_INVALID_REQUEST: Rep = Rep::CostMajor("Received message could not be decoded.");
const COST_INVALID_SIGNATURE: Rep = Rep::Malicious("Signatures were invalid.");
@@ -72,7 +73,7 @@ pub struct DisputesReceiver<Sender, AD> {
sender: Sender,
/// Channel to retrieve incoming requests from.
receiver: mpsc::Receiver<sc_network::config::IncomingRequest>,
receiver: IncomingRequestReceiver<DisputeRequest>,
/// Authority discovery service:
authority_discovery: AD,
@@ -103,26 +104,27 @@ enum MuxedMessage {
ConfirmedImport(NonFatalResult<(PeerId, ImportStatementsResult)>),
/// A new request has arrived and should be handled.
NewRequest(sc_network::config::IncomingRequest),
NewRequest(IncomingRequest<DisputeRequest>),
}
impl MuxedMessage {
async fn receive(
pending_imports: &mut PendingImports,
pending_requests: &mut mpsc::Receiver<sc_network::config::IncomingRequest>,
) -> FatalResult<MuxedMessage> {
pending_requests: &mut IncomingRequestReceiver<DisputeRequest>,
) -> Result<MuxedMessage> {
poll_fn(|ctx| {
if let Poll::Ready(v) = pending_requests.poll_next_unpin(ctx) {
let r = match v {
None => Err(Fatal::RequestChannelFinished),
Some(msg) => Ok(MuxedMessage::NewRequest(msg)),
};
return Poll::Ready(r)
let next_req = pending_requests.recv(|| vec![COST_INVALID_REQUEST]);
pin_mut!(next_req);
if let Poll::Ready(r) = next_req.poll(ctx) {
return match r {
Err(e) => Poll::Ready(Err(e.into())),
Ok(v) => Poll::Ready(Ok(Self::NewRequest(v))),
}
}
// In case of Ready(None) return `Pending` below - we want to wait for the next request
// in that case.
if let Poll::Ready(Some(v)) = pending_imports.poll_next_unpin(ctx) {
return Poll::Ready(Ok(MuxedMessage::ConfirmedImport(v)))
return Poll::Ready(Ok(Self::ConfirmedImport(v)))
}
Poll::Pending
})
@@ -137,7 +139,7 @@ where
/// Create a new receiver which can be `run`.
pub fn new(
sender: Sender,
receiver: mpsc::Receiver<sc_network::config::IncomingRequest>,
receiver: IncomingRequestReceiver<DisputeRequest>,
authority_discovery: AD,
metrics: Metrics,
) -> Self {
@@ -165,17 +167,14 @@ where
loop {
match log_error(self.run_inner().await) {
Ok(()) => {},
Err(Fatal::RequestChannelFinished) => {
Err(fatal) => {
tracing::debug!(
target: LOG_TARGET,
"Incoming request stream exhausted - shutting down?"
error = ?fatal,
"Shutting down"
);
return
},
Err(err) => {
tracing::warn!(target: LOG_TARGET, ?err, "Dispute receiver died.");
return
},
}
}
}
@@ -184,7 +183,7 @@ where
async fn run_inner(&mut self) -> Result<()> {
let msg = MuxedMessage::receive(&mut self.pending_imports, &mut self.receiver).await?;
let raw = match msg {
let incoming = match msg {
// We need to clean up futures, to make sure responses are sent:
MuxedMessage::ConfirmedImport(m_bad) => {
self.ban_bad_peer(m_bad)?;
@@ -195,14 +194,14 @@ where
self.metrics.on_received_request();
let peer = raw.peer;
let peer = incoming.peer;
// Only accept messages from validators:
if self.authority_discovery.get_authority_id_by_peer_id(raw.peer).await.is_none() {
raw.pending_response
.send(sc_network::config::OutgoingResponse {
if self.authority_discovery.get_authority_id_by_peer_id(peer).await.is_none() {
incoming
.send_outgoing_response(OutgoingResponse {
result: Err(()),
reputation_changes: vec![COST_NOT_A_VALIDATOR.into_base_rep()],
reputation_changes: vec![COST_NOT_A_VALIDATOR],
sent_feedback: None,
})
.map_err(|_| NonFatal::SendResponse(peer))?;
@@ -210,10 +209,6 @@ where
return Err(NonFatal::NotAValidator(peer).into())
}
let incoming =
IncomingRequest::<DisputeRequest>::try_from_raw(raw, vec![COST_INVALID_REQUEST])
.map_err(NonFatal::FromRawRequest)?;
// Immediately drop requests from peers that already have requests in flight or have
// been banned recently (flood protection):
if self.pending_imports.peer_is_pending(&peer) || self.banned_peers.contains(&peer) {
@@ -20,33 +20,30 @@
use thiserror::Error;
use polkadot_node_primitives::disputes::DisputeMessageCheckError;
use polkadot_node_subsystem_util::{runtime, Fault};
use polkadot_node_subsystem_util::runtime;
use polkadot_subsystem::SubsystemError;
#[derive(Debug, Error)]
#[derive(Debug, Error, derive_more::From)]
#[error(transparent)]
pub struct Error(pub Fault<NonFatal, Fatal>);
impl From<NonFatal> for Error {
fn from(e: NonFatal) -> Self {
Self(Fault::from_non_fatal(e))
}
}
impl From<Fatal> for Error {
fn from(f: Fatal) -> Self {
Self(Fault::from_fatal(f))
}
pub enum Error {
/// All fatal errors.
Fatal(Fatal),
/// All nonfatal/potentially recoverable errors.
NonFatal(NonFatal),
}
impl From<runtime::Error> for Error {
fn from(o: runtime::Error) -> Self {
Self(Fault::from_other(o))
match o {
runtime::Error::Fatal(f) => Self::Fatal(Fatal::Runtime(f)),
runtime::Error::NonFatal(f) => Self::NonFatal(NonFatal::Runtime(f)),
}
}
}
/// Fatal errors of this subsystem.
#[derive(Debug, Error)]
#[error(transparent)]
pub enum Fatal {
/// Spawning a running task failed.
#[error("Spawning subsystem task failed")]
@@ -28,7 +28,12 @@ use futures::{
use futures_timer::Delay;
use parity_scale_codec::{Decode, Encode};
use polkadot_node_network_protocol::{request_response::v1::DisputeRequest, PeerId};
use sc_network::config::RequestResponseConfig;
use polkadot_node_network_protocol::{
request_response::{v1::DisputeRequest, IncomingRequest},
PeerId,
};
use sp_keyring::Sr25519Keyring;
use polkadot_node_network_protocol::{
@@ -62,8 +67,8 @@ pub mod mock;
#[test]
fn send_dispute_sends_dispute() {
let test = |mut handle: TestSubsystemContextHandle<DisputeDistributionMessage>| async move {
let (_, _) = handle_subsystem_startup(&mut handle, None).await;
let test = |mut handle: TestSubsystemContextHandle<DisputeDistributionMessage>, _req_cfg| async move {
let _ = handle_subsystem_startup(&mut handle, None).await;
let relay_parent = Hash::random();
let candidate = make_candidate_receipt(relay_parent);
@@ -110,8 +115,10 @@ fn send_dispute_sends_dispute() {
#[test]
fn received_request_triggers_import() {
let test = |mut handle: TestSubsystemContextHandle<DisputeDistributionMessage>| async move {
let (_, mut req_tx) = handle_subsystem_startup(&mut handle, None).await;
let test = |mut handle: TestSubsystemContextHandle<DisputeDistributionMessage>,
mut req_cfg: RequestResponseConfig| async move {
let req_tx = req_cfg.inbound_queue.as_mut().unwrap();
let _ = handle_subsystem_startup(&mut handle, None).await;
let relay_parent = Hash::random();
let candidate = make_candidate_receipt(relay_parent);
@@ -119,8 +126,7 @@ fn received_request_triggers_import() {
// Non validator request should get dropped:
let rx_response =
send_network_dispute_request(&mut req_tx, PeerId::random(), message.clone().into())
.await;
send_network_dispute_request(req_tx, PeerId::random(), message.clone().into()).await;
assert_matches!(
rx_response.await,
@@ -141,7 +147,7 @@ fn received_request_triggers_import() {
// subsequent requests should get dropped.
nested_network_dispute_request(
&mut handle,
&mut req_tx,
req_tx,
MOCK_AUTHORITY_DISCOVERY.get_peer_id_by_authority(Sr25519Keyring::Alice),
message.clone().into(),
ImportStatementsResult::InvalidImport,
@@ -208,7 +214,7 @@ fn received_request_triggers_import() {
// Subsequent sends from Alice should fail (peer is banned):
{
let rx_response = send_network_dispute_request(
&mut req_tx,
req_tx,
MOCK_AUTHORITY_DISCOVERY.get_peer_id_by_authority(Sr25519Keyring::Alice),
message.clone().into(),
)
@@ -229,7 +235,7 @@ fn received_request_triggers_import() {
// But should work fine for Bob:
nested_network_dispute_request(
&mut handle,
&mut req_tx,
req_tx,
MOCK_AUTHORITY_DISCOVERY.get_peer_id_by_authority(Sr25519Keyring::Bob),
message.clone().into(),
ImportStatementsResult::ValidImport,
@@ -246,11 +252,11 @@ fn received_request_triggers_import() {
#[test]
fn disputes_are_recovered_at_startup() {
let test = |mut handle: TestSubsystemContextHandle<DisputeDistributionMessage>| async move {
let test = |mut handle: TestSubsystemContextHandle<DisputeDistributionMessage>, _| async move {
let relay_parent = Hash::random();
let candidate = make_candidate_receipt(relay_parent);
let (_, _) = handle_subsystem_startup(&mut handle, Some(candidate.hash())).await;
let _ = handle_subsystem_startup(&mut handle, Some(candidate.hash())).await;
let message = make_dispute_message(candidate.clone(), ALICE_INDEX, FERDIE_INDEX).await;
// Requests needed session info:
@@ -302,8 +308,8 @@ fn disputes_are_recovered_at_startup() {
#[test]
fn send_dispute_gets_cleaned_up() {
let test = |mut handle: TestSubsystemContextHandle<DisputeDistributionMessage>| async move {
let (old_head, _) = handle_subsystem_startup(&mut handle, None).await;
let test = |mut handle: TestSubsystemContextHandle<DisputeDistributionMessage>, _| async move {
let old_head = handle_subsystem_startup(&mut handle, None).await;
let relay_parent = Hash::random();
let candidate = make_candidate_receipt(relay_parent);
@@ -367,8 +373,8 @@ fn send_dispute_gets_cleaned_up() {
#[test]
fn dispute_retries_and_works_across_session_boundaries() {
let test = |mut handle: TestSubsystemContextHandle<DisputeDistributionMessage>| async move {
let (old_head, _) = handle_subsystem_startup(&mut handle, None).await;
let test = |mut handle: TestSubsystemContextHandle<DisputeDistributionMessage>, _| async move {
let old_head = handle_subsystem_startup(&mut handle, None).await;
let relay_parent = Hash::random();
let candidate = make_candidate_receipt(relay_parent);
@@ -689,14 +695,7 @@ async fn check_sent_requests(
async fn handle_subsystem_startup(
handle: &mut TestSubsystemContextHandle<DisputeDistributionMessage>,
ongoing_dispute: Option<CandidateHash>,
) -> (Hash, mpsc::Sender<sc_network::config::IncomingRequest>) {
let (request_tx, request_rx) = mpsc::channel(5);
handle
.send(FromOverseer::Communication {
msg: DisputeDistributionMessage::DisputeSendingReceiver(request_rx),
})
.await;
) -> Hash {
let relay_parent = Hash::random();
activate_leaf(
handle,
@@ -707,7 +706,7 @@ async fn handle_subsystem_startup(
ongoing_dispute.into_iter().map(|c| (MOCK_SESSION_INDEX, c)).collect(),
)
.await;
(relay_parent, request_tx)
relay_parent
}
/// Launch subsystem and provided test function
@@ -715,14 +714,19 @@ async fn handle_subsystem_startup(
/// which simulates the overseer.
fn test_harness<TestFn, Fut>(test: TestFn)
where
TestFn: FnOnce(TestSubsystemContextHandle<DisputeDistributionMessage>) -> Fut,
TestFn: FnOnce(
TestSubsystemContextHandle<DisputeDistributionMessage>,
RequestResponseConfig,
) -> Fut,
Fut: Future<Output = ()>,
{
sp_tracing::try_init_simple();
let keystore = make_ferdie_keystore();
let (req_receiver, req_cfg) = IncomingRequest::get_config_receiver();
let subsystem = DisputeDistributionSubsystem::new(
keystore,
req_receiver,
MOCK_AUTHORITY_DISCOVERY.clone(),
Metrics::new_dummy(),
);
@@ -739,5 +743,5 @@ where
},
}
};
subsystem_test_harness(test, subsystem);
subsystem_test_harness(|handle| test(handle, req_cfg), subsystem);
}