mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 06:31:09 +00:00
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:
Generated
+25
-8
@@ -1532,13 +1532,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.14"
|
||||
version = "0.99.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cc7b9cef1e351660e5443924e4f43ab25fbbed3e9a5f052df3677deb4d6b320"
|
||||
checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version 0.3.3",
|
||||
"syn",
|
||||
]
|
||||
|
||||
@@ -2476,7 +2477,7 @@ dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"log",
|
||||
"rustc_version",
|
||||
"rustc_version 0.2.3",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
@@ -2845,7 +2846,7 @@ dependencies = [
|
||||
"itoa",
|
||||
"log",
|
||||
"net2",
|
||||
"rustc_version",
|
||||
"rustc_version 0.2.3",
|
||||
"time",
|
||||
"tokio 0.1.22",
|
||||
"tokio-buf",
|
||||
@@ -5650,7 +5651,7 @@ checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252"
|
||||
dependencies = [
|
||||
"lock_api 0.3.4",
|
||||
"parking_lot_core 0.6.2",
|
||||
"rustc_version",
|
||||
"rustc_version 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5684,7 +5685,7 @@ dependencies = [
|
||||
"cloudabi 0.0.3",
|
||||
"libc",
|
||||
"redox_syscall 0.1.56",
|
||||
"rustc_version",
|
||||
"rustc_version 0.2.3",
|
||||
"smallvec 0.6.13",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
@@ -5942,6 +5943,7 @@ name = "polkadot-availability-distribution"
|
||||
version = "0.9.9"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"derive_more",
|
||||
"futures 0.3.16",
|
||||
"futures-timer 3.0.2",
|
||||
"lru",
|
||||
@@ -6053,10 +6055,12 @@ version = "0.9.9"
|
||||
dependencies = [
|
||||
"always-assert",
|
||||
"assert_matches",
|
||||
"derive_more",
|
||||
"env_logger 0.8.4",
|
||||
"futures 0.3.16",
|
||||
"futures-timer 3.0.2",
|
||||
"log",
|
||||
"parity-scale-codec",
|
||||
"polkadot-node-network-protocol",
|
||||
"polkadot-node-primitives",
|
||||
"polkadot-node-subsystem",
|
||||
@@ -6089,6 +6093,7 @@ version = "0.9.9"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"async-trait",
|
||||
"derive_more",
|
||||
"futures 0.3.16",
|
||||
"futures-timer 3.0.2",
|
||||
"lazy_static",
|
||||
@@ -6170,7 +6175,6 @@ dependencies = [
|
||||
"sp-consensus",
|
||||
"sp-core",
|
||||
"sp-keyring",
|
||||
"strum",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -6522,6 +6526,7 @@ name = "polkadot-node-network-protocol"
|
||||
version = "0.9.9"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"derive_more",
|
||||
"futures 0.3.16",
|
||||
"parity-scale-codec",
|
||||
"polkadot-node-jaeger",
|
||||
@@ -6628,6 +6633,7 @@ version = "0.9.9"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"async-trait",
|
||||
"derive_more",
|
||||
"env_logger 0.8.4",
|
||||
"futures 0.3.16",
|
||||
"futures-timer 3.0.2",
|
||||
@@ -7023,6 +7029,7 @@ dependencies = [
|
||||
"polkadot-node-core-parachains-inherent",
|
||||
"polkadot-node-core-provisioner",
|
||||
"polkadot-node-core-runtime-api",
|
||||
"polkadot-node-network-protocol",
|
||||
"polkadot-node-primitives",
|
||||
"polkadot-node-subsystem",
|
||||
"polkadot-node-subsystem-test-helpers",
|
||||
@@ -7144,6 +7151,7 @@ version = "0.9.9"
|
||||
dependencies = [
|
||||
"arrayvec 0.5.2",
|
||||
"assert_matches",
|
||||
"derive_more",
|
||||
"futures 0.3.16",
|
||||
"futures-timer 3.0.2",
|
||||
"indexmap",
|
||||
@@ -8152,6 +8160,15 @@ dependencies = [
|
||||
"semver 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
|
||||
dependencies = [
|
||||
"semver 0.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.18.0"
|
||||
@@ -9663,7 +9680,7 @@ dependencies = [
|
||||
"rand 0.7.3",
|
||||
"rand_core 0.5.1",
|
||||
"ring",
|
||||
"rustc_version",
|
||||
"rustc_version 0.2.3",
|
||||
"sha2 0.9.2",
|
||||
"subtle 2.2.3",
|
||||
"x25519-dalek 0.6.0",
|
||||
|
||||
@@ -20,6 +20,7 @@ sp-core = { git = "https://github.com/paritytech/substrate", branch = "master",
|
||||
sp-keystore = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
thiserror = "1.0.26"
|
||||
rand = "0.8.3"
|
||||
derive_more = "0.99.11"
|
||||
lru = "0.6.6"
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -17,35 +17,31 @@
|
||||
|
||||
//! Error handling related code and Error/Result definitions.
|
||||
|
||||
use polkadot_node_network_protocol::request_response::request::RequestError;
|
||||
use polkadot_node_network_protocol::request_response::outgoing::RequestError;
|
||||
use thiserror::Error;
|
||||
|
||||
use futures::channel::oneshot;
|
||||
|
||||
use polkadot_node_subsystem_util::{runtime, unwrap_non_fatal, Fault};
|
||||
use polkadot_node_subsystem_util::runtime;
|
||||
use polkadot_subsystem::SubsystemError;
|
||||
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,15 +103,23 @@ pub enum NonFatal {
|
||||
Runtime(#[from] runtime::NonFatal),
|
||||
}
|
||||
|
||||
/// General result type for fatal/nonfatal errors.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Results which are never 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
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ use futures::{future::Either, FutureExt, StreamExt, TryFutureExt};
|
||||
|
||||
use sp_keystore::SyncCryptoStorePtr;
|
||||
|
||||
use polkadot_node_network_protocol::request_response::{v1, IncomingRequestReceiver};
|
||||
use polkadot_subsystem::{
|
||||
messages::AvailabilityDistributionMessage, overseer, FromOverseer, OverseerSignal,
|
||||
SpawnedSubsystem, SubsystemContext, SubsystemError,
|
||||
@@ -38,7 +39,7 @@ mod pov_requester;
|
||||
|
||||
/// Responding to erasure chunk requests:
|
||||
mod responder;
|
||||
use responder::{answer_chunk_request_log, answer_pov_request_log};
|
||||
use responder::{run_chunk_receiver, run_pov_receiver};
|
||||
|
||||
mod metrics;
|
||||
/// Prometheus `Metrics` for availability distribution.
|
||||
@@ -53,10 +54,20 @@ const LOG_TARGET: &'static str = "parachain::availability-distribution";
|
||||
pub struct AvailabilityDistributionSubsystem {
|
||||
/// Easy and efficient runtime access for this subsystem.
|
||||
runtime: RuntimeInfo,
|
||||
/// Receivers to receive messages from.
|
||||
recvs: IncomingRequestReceivers,
|
||||
/// Prometheus metrics.
|
||||
metrics: Metrics,
|
||||
}
|
||||
|
||||
/// Receivers to be passed into availability distribution.
|
||||
pub struct IncomingRequestReceivers {
|
||||
/// Receiver for incoming PoV requests.
|
||||
pub pov_req_receiver: IncomingRequestReceiver<v1::PoVFetchingRequest>,
|
||||
/// Receiver for incoming availability chunk requests.
|
||||
pub chunk_req_receiver: IncomingRequestReceiver<v1::ChunkFetchingRequest>,
|
||||
}
|
||||
|
||||
impl<Context> overseer::Subsystem<Context, SubsystemError> for AvailabilityDistributionSubsystem
|
||||
where
|
||||
Context: SubsystemContext<Message = AvailabilityDistributionMessage>,
|
||||
@@ -74,18 +85,41 @@ where
|
||||
|
||||
impl AvailabilityDistributionSubsystem {
|
||||
/// Create a new instance of the availability distribution.
|
||||
pub fn new(keystore: SyncCryptoStorePtr, metrics: Metrics) -> Self {
|
||||
pub fn new(
|
||||
keystore: SyncCryptoStorePtr,
|
||||
recvs: IncomingRequestReceivers,
|
||||
metrics: Metrics,
|
||||
) -> Self {
|
||||
let runtime = RuntimeInfo::new(Some(keystore));
|
||||
Self { runtime, metrics }
|
||||
Self { runtime, recvs, metrics }
|
||||
}
|
||||
|
||||
/// Start processing work as passed on from the Overseer.
|
||||
async fn run<Context>(mut self, mut ctx: Context) -> std::result::Result<(), Fatal>
|
||||
async fn run<Context>(self, mut ctx: Context) -> std::result::Result<(), Fatal>
|
||||
where
|
||||
Context: SubsystemContext<Message = AvailabilityDistributionMessage>,
|
||||
Context: overseer::SubsystemContext<Message = AvailabilityDistributionMessage>,
|
||||
{
|
||||
let mut requester = Requester::new(self.metrics.clone()).fuse();
|
||||
let Self { mut runtime, recvs, metrics } = self;
|
||||
|
||||
let IncomingRequestReceivers { pov_req_receiver, chunk_req_receiver } = recvs;
|
||||
let mut requester = Requester::new(metrics.clone()).fuse();
|
||||
|
||||
{
|
||||
let sender = ctx.sender().clone();
|
||||
ctx.spawn(
|
||||
"pov-receiver",
|
||||
run_pov_receiver(sender.clone(), pov_req_receiver, metrics.clone()).boxed(),
|
||||
)
|
||||
.map_err(Fatal::SpawnTask)?;
|
||||
|
||||
ctx.spawn(
|
||||
"chunk-receiver",
|
||||
run_chunk_receiver(sender, chunk_req_receiver, metrics.clone()).boxed(),
|
||||
)
|
||||
.map_err(Fatal::SpawnTask)?;
|
||||
}
|
||||
|
||||
loop {
|
||||
let action = {
|
||||
let mut subsystem_next = ctx.recv().fuse();
|
||||
@@ -110,19 +144,13 @@ impl AvailabilityDistributionSubsystem {
|
||||
log_error(
|
||||
requester
|
||||
.get_mut()
|
||||
.update_fetching_heads(&mut ctx, &mut self.runtime, update)
|
||||
.update_fetching_heads(&mut ctx, &mut runtime, update)
|
||||
.await,
|
||||
"Error in Requester::update_fetching_heads",
|
||||
)?;
|
||||
},
|
||||
FromOverseer::Signal(OverseerSignal::BlockFinalized(..)) => {},
|
||||
FromOverseer::Signal(OverseerSignal::Conclude) => return Ok(()),
|
||||
FromOverseer::Communication {
|
||||
msg: AvailabilityDistributionMessage::ChunkFetchingRequest(req),
|
||||
} => answer_chunk_request_log(&mut ctx, req, &self.metrics).await,
|
||||
FromOverseer::Communication {
|
||||
msg: AvailabilityDistributionMessage::PoVFetchingRequest(req),
|
||||
} => answer_pov_request_log(&mut ctx, req, &self.metrics).await,
|
||||
FromOverseer::Communication {
|
||||
msg:
|
||||
AvailabilityDistributionMessage::FetchPoV {
|
||||
@@ -136,7 +164,7 @@ impl AvailabilityDistributionSubsystem {
|
||||
log_error(
|
||||
pov_requester::fetch_pov(
|
||||
&mut ctx,
|
||||
&mut self.runtime,
|
||||
&mut runtime,
|
||||
relay_parent,
|
||||
from_validator,
|
||||
candidate_hash,
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
use futures::{channel::oneshot, future::BoxFuture, FutureExt};
|
||||
|
||||
use polkadot_node_network_protocol::request_response::{
|
||||
request::{RequestError, Requests},
|
||||
outgoing::{RequestError, Requests},
|
||||
v1::{PoVFetchingRequest, PoVFetchingResponse},
|
||||
OutgoingRequest, Recipient,
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ use futures::{
|
||||
|
||||
use polkadot_erasure_coding::branch_hash;
|
||||
use polkadot_node_network_protocol::request_response::{
|
||||
request::{OutgoingRequest, Recipient, RequestError, Requests},
|
||||
outgoing::{OutgoingRequest, Recipient, RequestError, Requests},
|
||||
v1::{ChunkFetchingRequest, ChunkFetchingResponse},
|
||||
};
|
||||
use polkadot_node_primitives::ErasureChunk;
|
||||
|
||||
@@ -20,28 +20,93 @@ use std::sync::Arc;
|
||||
|
||||
use futures::channel::oneshot;
|
||||
|
||||
use polkadot_node_network_protocol::request_response::{request::IncomingRequest, v1};
|
||||
use polkadot_node_network_protocol::{
|
||||
request_response::{incoming, v1, IncomingRequest, IncomingRequestReceiver},
|
||||
UnifiedReputationChange as Rep,
|
||||
};
|
||||
use polkadot_node_primitives::{AvailableData, ErasureChunk};
|
||||
use polkadot_primitives::v1::{CandidateHash, ValidatorIndex};
|
||||
use polkadot_subsystem::{jaeger, messages::AvailabilityStoreMessage, SubsystemContext};
|
||||
use polkadot_subsystem::{jaeger, messages::AvailabilityStoreMessage, SubsystemSender};
|
||||
|
||||
use crate::{
|
||||
error::{NonFatal, Result},
|
||||
error::{NonFatal, NonFatalResult, Result},
|
||||
metrics::{Metrics, FAILED, NOT_FOUND, SUCCEEDED},
|
||||
LOG_TARGET,
|
||||
};
|
||||
|
||||
const COST_INVALID_REQUEST: Rep = Rep::CostMajor("Received message could not be decoded.");
|
||||
|
||||
/// Receiver task to be forked as a separate task to handle PoV requests.
|
||||
pub async fn run_pov_receiver<Sender>(
|
||||
mut sender: Sender,
|
||||
mut receiver: IncomingRequestReceiver<v1::PoVFetchingRequest>,
|
||||
metrics: Metrics,
|
||||
) where
|
||||
Sender: SubsystemSender,
|
||||
{
|
||||
loop {
|
||||
match receiver.recv(|| vec![COST_INVALID_REQUEST]).await {
|
||||
Ok(msg) => {
|
||||
answer_pov_request_log(&mut sender, msg, &metrics).await;
|
||||
},
|
||||
Err(incoming::Error::Fatal(f)) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
error = ?f,
|
||||
"Shutting down POV receiver."
|
||||
);
|
||||
return
|
||||
},
|
||||
Err(incoming::Error::NonFatal(error)) => {
|
||||
tracing::debug!(target: LOG_TARGET, ?error, "Error decoding incoming PoV request.");
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Receiver task to be forked as a separate task to handle chunk requests.
|
||||
pub async fn run_chunk_receiver<Sender>(
|
||||
mut sender: Sender,
|
||||
mut receiver: IncomingRequestReceiver<v1::ChunkFetchingRequest>,
|
||||
metrics: Metrics,
|
||||
) where
|
||||
Sender: SubsystemSender,
|
||||
{
|
||||
loop {
|
||||
match receiver.recv(|| vec![COST_INVALID_REQUEST]).await {
|
||||
Ok(msg) => {
|
||||
answer_chunk_request_log(&mut sender, msg, &metrics).await;
|
||||
},
|
||||
Err(incoming::Error::Fatal(f)) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
error = ?f,
|
||||
"Shutting down chunk receiver."
|
||||
);
|
||||
return
|
||||
},
|
||||
Err(incoming::Error::NonFatal(error)) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
?error,
|
||||
"Error decoding incoming chunk request."
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Variant of `answer_pov_request` that does Prometheus metric and logging on errors.
|
||||
///
|
||||
/// Any errors of `answer_pov_request` will simply be logged.
|
||||
pub async fn answer_pov_request_log<Context>(
|
||||
ctx: &mut Context,
|
||||
pub async fn answer_pov_request_log<Sender>(
|
||||
sender: &mut Sender,
|
||||
req: IncomingRequest<v1::PoVFetchingRequest>,
|
||||
metrics: &Metrics,
|
||||
) where
|
||||
Context: SubsystemContext,
|
||||
Sender: SubsystemSender,
|
||||
{
|
||||
let res = answer_pov_request(ctx, req).await;
|
||||
let res = answer_pov_request(sender, req).await;
|
||||
match res {
|
||||
Ok(result) => metrics.on_served_pov(if result { SUCCEEDED } else { NOT_FOUND }),
|
||||
Err(err) => {
|
||||
@@ -58,15 +123,15 @@ pub async fn answer_pov_request_log<Context>(
|
||||
/// Variant of `answer_chunk_request` that does Prometheus metric and logging on errors.
|
||||
///
|
||||
/// Any errors of `answer_request` will simply be logged.
|
||||
pub async fn answer_chunk_request_log<Context>(
|
||||
ctx: &mut Context,
|
||||
pub async fn answer_chunk_request_log<Sender>(
|
||||
sender: &mut Sender,
|
||||
req: IncomingRequest<v1::ChunkFetchingRequest>,
|
||||
metrics: &Metrics,
|
||||
) -> ()
|
||||
where
|
||||
Context: SubsystemContext,
|
||||
Sender: SubsystemSender,
|
||||
{
|
||||
let res = answer_chunk_request(ctx, req).await;
|
||||
let res = answer_chunk_request(sender, req).await;
|
||||
match res {
|
||||
Ok(result) => metrics.on_served_chunk(if result { SUCCEEDED } else { NOT_FOUND }),
|
||||
Err(err) => {
|
||||
@@ -83,16 +148,16 @@ where
|
||||
/// Answer an incoming PoV fetch request by querying the av store.
|
||||
///
|
||||
/// Returns: `Ok(true)` if chunk was found and served.
|
||||
pub async fn answer_pov_request<Context>(
|
||||
ctx: &mut Context,
|
||||
pub async fn answer_pov_request<Sender>(
|
||||
sender: &mut Sender,
|
||||
req: IncomingRequest<v1::PoVFetchingRequest>,
|
||||
) -> Result<bool>
|
||||
where
|
||||
Context: SubsystemContext,
|
||||
Sender: SubsystemSender,
|
||||
{
|
||||
let _span = jaeger::Span::new(req.payload.candidate_hash, "answer-pov-request");
|
||||
|
||||
let av_data = query_available_data(ctx, req.payload.candidate_hash).await?;
|
||||
let av_data = query_available_data(sender, req.payload.candidate_hash).await?;
|
||||
|
||||
let result = av_data.is_some();
|
||||
|
||||
@@ -111,18 +176,18 @@ where
|
||||
/// Answer an incoming chunk request by querying the av store.
|
||||
///
|
||||
/// Returns: `Ok(true)` if chunk was found and served.
|
||||
pub async fn answer_chunk_request<Context>(
|
||||
ctx: &mut Context,
|
||||
pub async fn answer_chunk_request<Sender>(
|
||||
sender: &mut Sender,
|
||||
req: IncomingRequest<v1::ChunkFetchingRequest>,
|
||||
) -> Result<bool>
|
||||
where
|
||||
Context: SubsystemContext,
|
||||
Sender: SubsystemSender,
|
||||
{
|
||||
let span = jaeger::Span::new(req.payload.candidate_hash, "answer-chunk-request");
|
||||
|
||||
let _child_span = span.child("answer-chunk-request").with_chunk_index(req.payload.index.0);
|
||||
|
||||
let chunk = query_chunk(ctx, req.payload.candidate_hash, req.payload.index).await?;
|
||||
let chunk = query_chunk(sender, req.payload.candidate_hash, req.payload.index).await?;
|
||||
|
||||
let result = chunk.is_some();
|
||||
|
||||
@@ -145,16 +210,19 @@ where
|
||||
}
|
||||
|
||||
/// Query chunk from the availability store.
|
||||
async fn query_chunk<Context>(
|
||||
ctx: &mut Context,
|
||||
async fn query_chunk<Sender>(
|
||||
sender: &mut Sender,
|
||||
candidate_hash: CandidateHash,
|
||||
validator_index: ValidatorIndex,
|
||||
) -> Result<Option<ErasureChunk>>
|
||||
) -> NonFatalResult<Option<ErasureChunk>>
|
||||
where
|
||||
Context: SubsystemContext,
|
||||
Sender: SubsystemSender,
|
||||
{
|
||||
let (tx, rx) = oneshot::channel();
|
||||
ctx.send_message(AvailabilityStoreMessage::QueryChunk(candidate_hash, validator_index, tx))
|
||||
sender
|
||||
.send_message(
|
||||
AvailabilityStoreMessage::QueryChunk(candidate_hash, validator_index, tx).into(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let result = rx.await.map_err(|e| {
|
||||
@@ -171,15 +239,16 @@ where
|
||||
}
|
||||
|
||||
/// Query PoV from the availability store.
|
||||
async fn query_available_data<Context>(
|
||||
ctx: &mut Context,
|
||||
async fn query_available_data<Sender>(
|
||||
sender: &mut Sender,
|
||||
candidate_hash: CandidateHash,
|
||||
) -> Result<Option<AvailableData>>
|
||||
) -> NonFatalResult<Option<AvailableData>>
|
||||
where
|
||||
Context: SubsystemContext,
|
||||
Sender: SubsystemSender,
|
||||
{
|
||||
let (tx, rx) = oneshot::channel();
|
||||
ctx.send_message(AvailabilityStoreMessage::QueryAvailableData(candidate_hash, tx))
|
||||
sender
|
||||
.send_message(AvailabilityStoreMessage::QueryAvailableData(candidate_hash, tx).into())
|
||||
.await;
|
||||
|
||||
let result = rx.await.map_err(|e| NonFatal::QueryAvailableDataResponseChannel(e))?;
|
||||
|
||||
@@ -18,6 +18,7 @@ use std::collections::HashSet;
|
||||
|
||||
use futures::{executor, future, Future};
|
||||
|
||||
use polkadot_node_network_protocol::request_response::IncomingRequest;
|
||||
use polkadot_primitives::v1::CoreState;
|
||||
use sp_keystore::SyncCryptoStorePtr;
|
||||
|
||||
@@ -41,17 +42,21 @@ fn test_harness<T: Future<Output = ()>>(
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (context, virtual_overseer) = test_helpers::make_subsystem_context(pool.clone());
|
||||
|
||||
let subsystem = AvailabilityDistributionSubsystem::new(keystore, Default::default());
|
||||
{
|
||||
let subsystem = subsystem.run(context);
|
||||
let (pov_req_receiver, pov_req_cfg) = IncomingRequest::get_config_receiver();
|
||||
let (chunk_req_receiver, chunk_req_cfg) = IncomingRequest::get_config_receiver();
|
||||
let subsystem = AvailabilityDistributionSubsystem::new(
|
||||
keystore,
|
||||
IncomingRequestReceivers { pov_req_receiver, chunk_req_receiver },
|
||||
Default::default(),
|
||||
);
|
||||
let subsystem = subsystem.run(context);
|
||||
|
||||
let test_fut = test_fx(TestHarness { virtual_overseer, pool });
|
||||
let test_fut = test_fx(TestHarness { virtual_overseer, pov_req_cfg, chunk_req_cfg, pool });
|
||||
|
||||
futures::pin_mut!(test_fut);
|
||||
futures::pin_mut!(subsystem);
|
||||
futures::pin_mut!(test_fut);
|
||||
futures::pin_mut!(subsystem);
|
||||
|
||||
executor::block_on(future::join(test_fut, subsystem)).1.unwrap();
|
||||
}
|
||||
executor::block_on(future::join(test_fut, subsystem)).1.unwrap();
|
||||
}
|
||||
|
||||
/// Simple basic check, whether the subsystem works as expected.
|
||||
|
||||
@@ -30,7 +30,7 @@ use futures::{
|
||||
use futures_timer::Delay;
|
||||
|
||||
use sc_network as network;
|
||||
use sc_network::{config as netconfig, IfDisconnected};
|
||||
use sc_network::{config as netconfig, config::RequestResponseConfig, IfDisconnected};
|
||||
use sp_core::{testing::TaskExecutor, traits::SpawnNamed};
|
||||
use sp_keystore::SyncCryptoStorePtr;
|
||||
|
||||
@@ -59,6 +59,8 @@ use crate::LOG_TARGET;
|
||||
type VirtualOverseer = test_helpers::TestSubsystemContextHandle<AvailabilityDistributionMessage>;
|
||||
pub struct TestHarness {
|
||||
pub virtual_overseer: VirtualOverseer,
|
||||
pub pov_req_cfg: RequestResponseConfig,
|
||||
pub chunk_req_cfg: RequestResponseConfig,
|
||||
pub pool: TaskExecutor,
|
||||
}
|
||||
|
||||
@@ -152,9 +154,7 @@ impl TestState {
|
||||
/// Run, but fail after some timeout.
|
||||
pub async fn run(self, harness: TestHarness) {
|
||||
// Make sure test won't run forever.
|
||||
let f = self
|
||||
.run_inner(harness.pool, harness.virtual_overseer)
|
||||
.timeout(Duration::from_secs(10));
|
||||
let f = self.run_inner(harness).timeout(Duration::from_secs(10));
|
||||
assert!(f.await.is_some(), "Test ran into timeout");
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ impl TestState {
|
||||
///
|
||||
/// We try to be as agnostic about details as possible, how the subsystem achieves those goals
|
||||
/// should not be a matter to this test suite.
|
||||
async fn run_inner(mut self, executor: TaskExecutor, virtual_overseer: VirtualOverseer) {
|
||||
async fn run_inner(mut self, mut harness: TestHarness) {
|
||||
// We skip genesis here (in reality ActiveLeavesUpdate can also skip a block:
|
||||
let updates = {
|
||||
let mut advanced = self.relay_chain.iter();
|
||||
@@ -191,12 +191,12 @@ impl TestState {
|
||||
// Test will fail if this does not happen until timeout.
|
||||
let mut remaining_stores = self.valid_chunks.len();
|
||||
|
||||
let TestSubsystemContextHandle { tx, mut rx } = virtual_overseer;
|
||||
let TestSubsystemContextHandle { tx, mut rx } = harness.virtual_overseer;
|
||||
|
||||
// Spawning necessary as incoming queue can only hold a single item, we don't want to dead
|
||||
// lock ;-)
|
||||
let update_tx = tx.clone();
|
||||
executor.spawn(
|
||||
harness.pool.spawn(
|
||||
"Sending active leaves updates",
|
||||
async move {
|
||||
for update in updates {
|
||||
@@ -219,16 +219,15 @@ impl TestState {
|
||||
)) => {
|
||||
for req in reqs {
|
||||
// Forward requests:
|
||||
let in_req = to_incoming_req(&executor, req);
|
||||
|
||||
executor.spawn(
|
||||
"Request forwarding",
|
||||
overseer_send(
|
||||
tx.clone(),
|
||||
AvailabilityDistributionMessage::ChunkFetchingRequest(in_req),
|
||||
)
|
||||
.boxed(),
|
||||
);
|
||||
let in_req = to_incoming_req(&harness.pool, req);
|
||||
harness
|
||||
.chunk_req_cfg
|
||||
.inbound_queue
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send(in_req.into_raw())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
},
|
||||
AllMessages::AvailabilityStore(AvailabilityStoreMessage::QueryChunk(
|
||||
@@ -295,18 +294,6 @@ async fn overseer_signal(
|
||||
tx.send(FromOverseer::Signal(msg)).await.expect("Test subsystem no longer live");
|
||||
}
|
||||
|
||||
async fn overseer_send(
|
||||
mut tx: SingleItemSink<FromOverseer<AvailabilityDistributionMessage>>,
|
||||
msg: impl Into<AvailabilityDistributionMessage>,
|
||||
) {
|
||||
let msg = msg.into();
|
||||
tracing::trace!(target: LOG_TARGET, msg = ?msg, "sending message");
|
||||
tx.send(FromOverseer::Communication { msg })
|
||||
.await
|
||||
.expect("Test subsystem no longer live");
|
||||
tracing::trace!(target: LOG_TARGET, "sent message");
|
||||
}
|
||||
|
||||
async fn overseer_recv(rx: &mut mpsc::UnboundedReceiver<AllMessages>) -> AllMessages {
|
||||
tracing::trace!(target: LOG_TARGET, "waiting for message ...");
|
||||
rx.next().await.expect("Test subsystem no longer live")
|
||||
|
||||
@@ -26,6 +26,7 @@ use std::{
|
||||
use futures::{
|
||||
channel::oneshot,
|
||||
future::{BoxFuture, FutureExt, RemoteHandle},
|
||||
pin_mut,
|
||||
prelude::*,
|
||||
stream::FuturesUnordered,
|
||||
task::{Context, Poll},
|
||||
@@ -36,9 +37,10 @@ use rand::seq::SliceRandom;
|
||||
use polkadot_erasure_coding::{branch_hash, branches, obtain_chunks_v1, recovery_threshold};
|
||||
use polkadot_node_network_protocol::{
|
||||
request_response::{
|
||||
self as req_res, request::RequestError, OutgoingRequest, Recipient, Requests,
|
||||
self as req_res, incoming, outgoing::RequestError, v1 as request_v1,
|
||||
IncomingRequestReceiver, OutgoingRequest, Recipient, Requests,
|
||||
},
|
||||
IfDisconnected,
|
||||
IfDisconnected, UnifiedReputationChange as Rep,
|
||||
};
|
||||
use polkadot_node_primitives::{AvailableData, ErasureChunk};
|
||||
use polkadot_node_subsystem_util::request_session_info;
|
||||
@@ -68,9 +70,13 @@ const N_PARALLEL: usize = 50;
|
||||
// Size of the LRU cache where we keep recovered data.
|
||||
const LRU_SIZE: usize = 16;
|
||||
|
||||
const COST_INVALID_REQUEST: Rep = Rep::CostMajor("Peer sent unparsable request");
|
||||
|
||||
/// The Availability Recovery Subsystem.
|
||||
pub struct AvailabilityRecoverySubsystem {
|
||||
fast_path: bool,
|
||||
/// Receiver for available data requests.
|
||||
req_receiver: IncomingRequestReceiver<request_v1::AvailableDataFetchingRequest>,
|
||||
}
|
||||
|
||||
struct RequestFromBackersPhase {
|
||||
@@ -750,13 +756,17 @@ where
|
||||
|
||||
impl AvailabilityRecoverySubsystem {
|
||||
/// Create a new instance of `AvailabilityRecoverySubsystem` which starts with a fast path to request data from backers.
|
||||
pub fn with_fast_path() -> Self {
|
||||
Self { fast_path: true }
|
||||
pub fn with_fast_path(
|
||||
req_receiver: IncomingRequestReceiver<request_v1::AvailableDataFetchingRequest>,
|
||||
) -> Self {
|
||||
Self { fast_path: true, req_receiver }
|
||||
}
|
||||
|
||||
/// Create a new instance of `AvailabilityRecoverySubsystem` which requests only chunks
|
||||
pub fn with_chunks_only() -> Self {
|
||||
Self { fast_path: false }
|
||||
pub fn with_chunks_only(
|
||||
req_receiver: IncomingRequestReceiver<request_v1::AvailableDataFetchingRequest>,
|
||||
) -> Self {
|
||||
Self { fast_path: false, req_receiver }
|
||||
}
|
||||
|
||||
async fn run<Context>(self, mut ctx: Context) -> SubsystemResult<()>
|
||||
@@ -765,8 +775,11 @@ impl AvailabilityRecoverySubsystem {
|
||||
Context: overseer::SubsystemContext<Message = AvailabilityRecoveryMessage>,
|
||||
{
|
||||
let mut state = State::default();
|
||||
let Self { fast_path, mut req_receiver } = self;
|
||||
|
||||
loop {
|
||||
let recv_req = req_receiver.recv(|| vec![COST_INVALID_REQUEST]).fuse();
|
||||
pin_mut!(recv_req);
|
||||
futures::select! {
|
||||
v = ctx.recv().fuse() => {
|
||||
match v? {
|
||||
@@ -789,7 +802,7 @@ impl AvailabilityRecoverySubsystem {
|
||||
&mut ctx,
|
||||
receipt,
|
||||
session_index,
|
||||
maybe_backing_group.filter(|_| self.fast_path),
|
||||
maybe_backing_group.filter(|_| fast_path),
|
||||
response_sender,
|
||||
).await {
|
||||
tracing::warn!(
|
||||
@@ -799,24 +812,37 @@ impl AvailabilityRecoverySubsystem {
|
||||
);
|
||||
}
|
||||
}
|
||||
AvailabilityRecoveryMessage::AvailableDataFetchingRequest(req) => {
|
||||
match query_full_data(&mut ctx, req.payload.candidate_hash).await {
|
||||
Ok(res) => {
|
||||
let _ = req.send_response(res.into());
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
err = ?e,
|
||||
"Failed to query available data.",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
in_req = recv_req => {
|
||||
match in_req {
|
||||
Ok(req) => {
|
||||
match query_full_data(&mut ctx, req.payload.candidate_hash).await {
|
||||
Ok(res) => {
|
||||
let _ = req.send_response(res.into());
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
err = ?e,
|
||||
"Failed to query available data.",
|
||||
);
|
||||
|
||||
let _ = req.send_response(None.into());
|
||||
}
|
||||
}
|
||||
let _ = req.send_response(None.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(incoming::Error::Fatal(f)) => return Err(SubsystemError::with_origin("availability-recovery", f)),
|
||||
Err(incoming::Error::NonFatal(err)) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
?err,
|
||||
"Decoding incoming request failed"
|
||||
);
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
output = state.interactions.select_next_some() => {
|
||||
|
||||
@@ -21,9 +21,12 @@ use futures::{executor, future};
|
||||
use futures_timer::Delay;
|
||||
|
||||
use parity_scale_codec::Encode;
|
||||
use polkadot_node_network_protocol::request_response::IncomingRequest;
|
||||
|
||||
use super::*;
|
||||
|
||||
use sc_network::config::RequestResponseConfig;
|
||||
|
||||
use polkadot_erasure_coding::{branches, obtain_chunks_v1 as obtain_chunks};
|
||||
use polkadot_node_primitives::{BlockData, PoV};
|
||||
use polkadot_node_subsystem_util::TimeoutExt;
|
||||
@@ -37,8 +40,8 @@ use polkadot_subsystem_testhelpers as test_helpers;
|
||||
|
||||
type VirtualOverseer = test_helpers::TestSubsystemContextHandle<AvailabilityRecoveryMessage>;
|
||||
|
||||
fn test_harness_fast_path<T: Future<Output = VirtualOverseer>>(
|
||||
test: impl FnOnce(VirtualOverseer) -> T,
|
||||
fn test_harness_fast_path<T: Future<Output = (VirtualOverseer, RequestResponseConfig)>>(
|
||||
test: impl FnOnce(VirtualOverseer, RequestResponseConfig) -> T,
|
||||
) {
|
||||
let _ = env_logger::builder()
|
||||
.is_test(true)
|
||||
@@ -49,27 +52,29 @@ fn test_harness_fast_path<T: Future<Output = VirtualOverseer>>(
|
||||
|
||||
let (context, virtual_overseer) = test_helpers::make_subsystem_context(pool.clone());
|
||||
|
||||
let subsystem = AvailabilityRecoverySubsystem::with_fast_path();
|
||||
let subsystem = subsystem.run(context);
|
||||
let (collation_req_receiver, req_cfg) = IncomingRequest::get_config_receiver();
|
||||
let subsystem = AvailabilityRecoverySubsystem::with_fast_path(collation_req_receiver);
|
||||
let subsystem = async {
|
||||
subsystem.run(context).await.unwrap();
|
||||
};
|
||||
|
||||
let test_fut = test(virtual_overseer);
|
||||
let test_fut = test(virtual_overseer, req_cfg);
|
||||
|
||||
futures::pin_mut!(test_fut);
|
||||
futures::pin_mut!(subsystem);
|
||||
|
||||
executor::block_on(future::join(
|
||||
async move {
|
||||
let mut overseer = test_fut.await;
|
||||
let (mut overseer, _req_cfg) = test_fut.await;
|
||||
overseer_signal(&mut overseer, OverseerSignal::Conclude).await;
|
||||
},
|
||||
subsystem,
|
||||
))
|
||||
.1
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn test_harness_chunks_only<T: Future<Output = VirtualOverseer>>(
|
||||
test: impl FnOnce(VirtualOverseer) -> T,
|
||||
fn test_harness_chunks_only<T: Future<Output = (VirtualOverseer, RequestResponseConfig)>>(
|
||||
test: impl FnOnce(VirtualOverseer, RequestResponseConfig) -> T,
|
||||
) {
|
||||
let _ = env_logger::builder()
|
||||
.is_test(true)
|
||||
@@ -80,17 +85,18 @@ fn test_harness_chunks_only<T: Future<Output = VirtualOverseer>>(
|
||||
|
||||
let (context, virtual_overseer) = test_helpers::make_subsystem_context(pool.clone());
|
||||
|
||||
let subsystem = AvailabilityRecoverySubsystem::with_chunks_only();
|
||||
let (collation_req_receiver, req_cfg) = IncomingRequest::get_config_receiver();
|
||||
let subsystem = AvailabilityRecoverySubsystem::with_chunks_only(collation_req_receiver);
|
||||
let subsystem = subsystem.run(context);
|
||||
|
||||
let test_fut = test(virtual_overseer);
|
||||
let test_fut = test(virtual_overseer, req_cfg);
|
||||
|
||||
futures::pin_mut!(test_fut);
|
||||
futures::pin_mut!(subsystem);
|
||||
|
||||
executor::block_on(future::join(
|
||||
async move {
|
||||
let mut overseer = test_fut.await;
|
||||
let (mut overseer, _req_cfg) = test_fut.await;
|
||||
overseer_signal(&mut overseer, OverseerSignal::Conclude).await;
|
||||
},
|
||||
subsystem,
|
||||
@@ -432,7 +438,7 @@ impl Default for TestState {
|
||||
fn availability_is_recovered_from_chunks_if_no_group_provided() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
test_harness_fast_path(|mut virtual_overseer| async move {
|
||||
test_harness_fast_path(|mut virtual_overseer, req_cfg| async move {
|
||||
overseer_signal(
|
||||
&mut virtual_overseer,
|
||||
OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf {
|
||||
@@ -510,7 +516,7 @@ fn availability_is_recovered_from_chunks_if_no_group_provided() {
|
||||
|
||||
// A request times out with `Unavailable` error.
|
||||
assert_eq!(rx.await.unwrap().unwrap_err(), RecoveryError::Unavailable);
|
||||
virtual_overseer
|
||||
(virtual_overseer, req_cfg)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -518,7 +524,7 @@ fn availability_is_recovered_from_chunks_if_no_group_provided() {
|
||||
fn availability_is_recovered_from_chunks_even_if_backing_group_supplied_if_chunks_only() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
test_harness_chunks_only(|mut virtual_overseer| async move {
|
||||
test_harness_chunks_only(|mut virtual_overseer, req_cfg| async move {
|
||||
overseer_signal(
|
||||
&mut virtual_overseer,
|
||||
OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf {
|
||||
@@ -596,7 +602,7 @@ fn availability_is_recovered_from_chunks_even_if_backing_group_supplied_if_chunk
|
||||
|
||||
// A request times out with `Unavailable` error.
|
||||
assert_eq!(rx.await.unwrap().unwrap_err(), RecoveryError::Unavailable);
|
||||
virtual_overseer
|
||||
(virtual_overseer, req_cfg)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -604,7 +610,7 @@ fn availability_is_recovered_from_chunks_even_if_backing_group_supplied_if_chunk
|
||||
fn bad_merkle_path_leads_to_recovery_error() {
|
||||
let mut test_state = TestState::default();
|
||||
|
||||
test_harness_fast_path(|mut virtual_overseer| async move {
|
||||
test_harness_fast_path(|mut virtual_overseer, req_cfg| async move {
|
||||
overseer_signal(
|
||||
&mut virtual_overseer,
|
||||
OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf {
|
||||
@@ -654,7 +660,7 @@ fn bad_merkle_path_leads_to_recovery_error() {
|
||||
|
||||
// A request times out with `Unavailable` error.
|
||||
assert_eq!(rx.await.unwrap().unwrap_err(), RecoveryError::Unavailable);
|
||||
virtual_overseer
|
||||
(virtual_overseer, req_cfg)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -662,7 +668,7 @@ fn bad_merkle_path_leads_to_recovery_error() {
|
||||
fn wrong_chunk_index_leads_to_recovery_error() {
|
||||
let mut test_state = TestState::default();
|
||||
|
||||
test_harness_fast_path(|mut virtual_overseer| async move {
|
||||
test_harness_fast_path(|mut virtual_overseer, req_cfg| async move {
|
||||
overseer_signal(
|
||||
&mut virtual_overseer,
|
||||
OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf {
|
||||
@@ -711,7 +717,7 @@ fn wrong_chunk_index_leads_to_recovery_error() {
|
||||
|
||||
// A request times out with `Unavailable` error as there are no good peers.
|
||||
assert_eq!(rx.await.unwrap().unwrap_err(), RecoveryError::Unavailable);
|
||||
virtual_overseer
|
||||
(virtual_overseer, req_cfg)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -719,7 +725,7 @@ fn wrong_chunk_index_leads_to_recovery_error() {
|
||||
fn invalid_erasure_coding_leads_to_invalid_error() {
|
||||
let mut test_state = TestState::default();
|
||||
|
||||
test_harness_fast_path(|mut virtual_overseer| async move {
|
||||
test_harness_fast_path(|mut virtual_overseer, req_cfg| async move {
|
||||
let pov = PoV { block_data: BlockData(vec![69; 64]) };
|
||||
|
||||
let (bad_chunks, bad_erasure_root) = derive_erasure_chunks_with_proofs_and_root(
|
||||
@@ -776,7 +782,7 @@ fn invalid_erasure_coding_leads_to_invalid_error() {
|
||||
|
||||
// f+1 'valid' chunks can't produce correct data.
|
||||
assert_eq!(rx.await.unwrap().unwrap_err(), RecoveryError::Invalid);
|
||||
virtual_overseer
|
||||
(virtual_overseer, req_cfg)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -784,7 +790,7 @@ fn invalid_erasure_coding_leads_to_invalid_error() {
|
||||
fn fast_path_backing_group_recovers() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
test_harness_fast_path(|mut virtual_overseer| async move {
|
||||
test_harness_fast_path(|mut virtual_overseer, req_cfg| async move {
|
||||
overseer_signal(
|
||||
&mut virtual_overseer,
|
||||
OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf {
|
||||
@@ -826,7 +832,7 @@ fn fast_path_backing_group_recovers() {
|
||||
|
||||
// Recovered data should match the original one.
|
||||
assert_eq!(rx.await.unwrap().unwrap(), test_state.available_data);
|
||||
virtual_overseer
|
||||
(virtual_overseer, req_cfg)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -834,7 +840,7 @@ fn fast_path_backing_group_recovers() {
|
||||
fn no_answers_in_fast_path_causes_chunk_requests() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
test_harness_fast_path(|mut virtual_overseer| async move {
|
||||
test_harness_fast_path(|mut virtual_overseer, req_cfg| async move {
|
||||
overseer_signal(
|
||||
&mut virtual_overseer,
|
||||
OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf {
|
||||
@@ -888,7 +894,7 @@ fn no_answers_in_fast_path_causes_chunk_requests() {
|
||||
|
||||
// Recovered data should match the original one.
|
||||
assert_eq!(rx.await.unwrap().unwrap(), test_state.available_data);
|
||||
virtual_overseer
|
||||
(virtual_overseer, req_cfg)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -896,7 +902,7 @@ fn no_answers_in_fast_path_causes_chunk_requests() {
|
||||
fn task_canceled_when_receivers_dropped() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
test_harness_chunks_only(|mut virtual_overseer| async move {
|
||||
test_harness_chunks_only(|mut virtual_overseer, req_cfg| async move {
|
||||
overseer_signal(
|
||||
&mut virtual_overseer,
|
||||
OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf {
|
||||
@@ -925,7 +931,7 @@ fn task_canceled_when_receivers_dropped() {
|
||||
|
||||
for _ in 0..test_state.validators.len() {
|
||||
match virtual_overseer.recv().timeout(TIMEOUT).await {
|
||||
None => return virtual_overseer,
|
||||
None => return (virtual_overseer, req_cfg),
|
||||
Some(_) => continue,
|
||||
}
|
||||
}
|
||||
@@ -938,7 +944,7 @@ fn task_canceled_when_receivers_dropped() {
|
||||
fn chunks_retry_until_all_nodes_respond() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
test_harness_chunks_only(|mut virtual_overseer| async move {
|
||||
test_harness_chunks_only(|mut virtual_overseer, req_cfg| async move {
|
||||
overseer_signal(
|
||||
&mut virtual_overseer,
|
||||
OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf {
|
||||
@@ -992,7 +998,7 @@ fn chunks_retry_until_all_nodes_respond() {
|
||||
|
||||
// Recovered data should match the original one.
|
||||
assert_eq!(rx.await.unwrap().unwrap_err(), RecoveryError::Unavailable);
|
||||
virtual_overseer
|
||||
(virtual_overseer, req_cfg)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1000,7 +1006,7 @@ fn chunks_retry_until_all_nodes_respond() {
|
||||
fn returns_early_if_we_have_the_data() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
test_harness_chunks_only(|mut virtual_overseer| async move {
|
||||
test_harness_chunks_only(|mut virtual_overseer, req_cfg| async move {
|
||||
overseer_signal(
|
||||
&mut virtual_overseer,
|
||||
OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf {
|
||||
@@ -1029,7 +1035,7 @@ fn returns_early_if_we_have_the_data() {
|
||||
test_state.respond_to_available_data_query(&mut virtual_overseer, true).await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), test_state.available_data);
|
||||
virtual_overseer
|
||||
(virtual_overseer, req_cfg)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1037,7 +1043,7 @@ fn returns_early_if_we_have_the_data() {
|
||||
fn does_not_query_local_validator() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
test_harness_chunks_only(|mut virtual_overseer| async move {
|
||||
test_harness_chunks_only(|mut virtual_overseer, req_cfg| async move {
|
||||
overseer_signal(
|
||||
&mut virtual_overseer,
|
||||
OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf {
|
||||
@@ -1088,6 +1094,6 @@ fn does_not_query_local_validator() {
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), test_state.available_data);
|
||||
virtual_overseer
|
||||
(virtual_overseer, req_cfg)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ polkadot-subsystem = { package = "polkadot-node-subsystem", path = "../../subsys
|
||||
polkadot-overseer = { path = "../../overseer" }
|
||||
polkadot-node-network-protocol = { path = "../protocol" }
|
||||
polkadot-node-subsystem-util = { path = "../../subsystem-util"}
|
||||
strum = "0.20.0"
|
||||
parking_lot = "0.11.1"
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
use futures::{prelude::*, stream::BoxStream};
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
use parking_lot::Mutex;
|
||||
use polkadot_subsystem::messages::DisputeDistributionMessage;
|
||||
use sc_network::Event as NetworkEvent;
|
||||
use sp_consensus::SyncOracle;
|
||||
|
||||
@@ -35,10 +34,7 @@ use polkadot_overseer::gen::{OverseerError, Subsystem};
|
||||
use polkadot_primitives::v1::{BlockNumber, Hash};
|
||||
use polkadot_subsystem::{
|
||||
errors::{SubsystemError, SubsystemResult},
|
||||
messages::{
|
||||
AllMessages, CollatorProtocolMessage, NetworkBridgeEvent, NetworkBridgeMessage,
|
||||
StatementDistributionMessage,
|
||||
},
|
||||
messages::{AllMessages, CollatorProtocolMessage, NetworkBridgeEvent, NetworkBridgeMessage},
|
||||
overseer, ActivatedLeaf, ActiveLeavesUpdate, FromOverseer, OverseerSignal, SpawnedSubsystem,
|
||||
SubsystemContext, SubsystemSender,
|
||||
};
|
||||
@@ -61,10 +57,6 @@ mod validator_discovery;
|
||||
mod network;
|
||||
use network::{send_message, Network};
|
||||
|
||||
/// Request multiplexer for combining the multiple request sources into a single `Stream` of `AllMessages`.
|
||||
mod multiplexer;
|
||||
pub use multiplexer::RequestMultiplexer;
|
||||
|
||||
use crate::network::get_peer_id_by_authority_id;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -276,7 +268,6 @@ pub struct NetworkBridge<N, AD> {
|
||||
/// `Network` trait implementing type.
|
||||
network_service: N,
|
||||
authority_discovery_service: AD,
|
||||
request_multiplexer: RequestMultiplexer,
|
||||
sync_oracle: Box<dyn SyncOracle + Send>,
|
||||
metrics: Metrics,
|
||||
}
|
||||
@@ -289,17 +280,10 @@ impl<N, AD> NetworkBridge<N, AD> {
|
||||
pub fn new(
|
||||
network_service: N,
|
||||
authority_discovery_service: AD,
|
||||
request_multiplexer: RequestMultiplexer,
|
||||
sync_oracle: Box<dyn SyncOracle + Send>,
|
||||
metrics: Metrics,
|
||||
) -> Self {
|
||||
NetworkBridge {
|
||||
network_service,
|
||||
authority_discovery_service,
|
||||
request_multiplexer,
|
||||
sync_oracle,
|
||||
metrics,
|
||||
}
|
||||
NetworkBridge { network_service, authority_discovery_service, sync_oracle, metrics }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,8 +319,6 @@ enum UnexpectedAbort {
|
||||
SubsystemError(SubsystemError),
|
||||
/// The stream of incoming events concluded.
|
||||
EventStreamConcluded,
|
||||
/// The stream of incoming requests concluded.
|
||||
RequestStreamConcluded,
|
||||
}
|
||||
|
||||
impl From<SubsystemError> for UnexpectedAbort {
|
||||
@@ -610,247 +592,226 @@ async fn handle_network_messages<AD: validator_discovery::AuthorityDiscovery>(
|
||||
mut network_service: impl Network,
|
||||
network_stream: BoxStream<'static, NetworkEvent>,
|
||||
mut authority_discovery_service: AD,
|
||||
mut request_multiplexer: RequestMultiplexer,
|
||||
metrics: Metrics,
|
||||
shared: Shared,
|
||||
) -> Result<(), UnexpectedAbort> {
|
||||
let mut network_stream = network_stream.fuse();
|
||||
loop {
|
||||
futures::select! {
|
||||
network_event = network_stream.next() => match network_event {
|
||||
None => return Err(UnexpectedAbort::EventStreamConcluded),
|
||||
Some(NetworkEvent::Dht(_))
|
||||
| Some(NetworkEvent::SyncConnected { .. })
|
||||
| Some(NetworkEvent::SyncDisconnected { .. }) => {}
|
||||
Some(NetworkEvent::NotificationStreamOpened { remote: peer, protocol, role, .. }) => {
|
||||
let role = ObservedRole::from(role);
|
||||
let peer_set = match PeerSet::try_from_protocol_name(&protocol) {
|
||||
None => continue,
|
||||
Some(peer_set) => peer_set,
|
||||
match network_stream.next().await {
|
||||
None => return Err(UnexpectedAbort::EventStreamConcluded),
|
||||
Some(NetworkEvent::Dht(_)) |
|
||||
Some(NetworkEvent::SyncConnected { .. }) |
|
||||
Some(NetworkEvent::SyncDisconnected { .. }) => {},
|
||||
Some(NetworkEvent::NotificationStreamOpened {
|
||||
remote: peer, protocol, role, ..
|
||||
}) => {
|
||||
let role = ObservedRole::from(role);
|
||||
let peer_set = match PeerSet::try_from_protocol_name(&protocol) {
|
||||
None => continue,
|
||||
Some(peer_set) => peer_set,
|
||||
};
|
||||
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
action = "PeerConnected",
|
||||
peer_set = ?peer_set,
|
||||
peer = ?peer,
|
||||
role = ?role
|
||||
);
|
||||
|
||||
let local_view = {
|
||||
let mut shared = shared.0.lock();
|
||||
let peer_map = match peer_set {
|
||||
PeerSet::Validation => &mut shared.validation_peers,
|
||||
PeerSet::Collation => &mut shared.collation_peers,
|
||||
};
|
||||
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
action = "PeerConnected",
|
||||
peer_set = ?peer_set,
|
||||
peer = ?peer,
|
||||
role = ?role
|
||||
);
|
||||
match peer_map.entry(peer.clone()) {
|
||||
hash_map::Entry::Occupied(_) => continue,
|
||||
hash_map::Entry::Vacant(vacant) => {
|
||||
vacant.insert(PeerData { view: View::default() });
|
||||
},
|
||||
}
|
||||
|
||||
let local_view = {
|
||||
let mut shared = shared.0.lock();
|
||||
let peer_map = match peer_set {
|
||||
PeerSet::Validation => &mut shared.validation_peers,
|
||||
PeerSet::Collation => &mut shared.collation_peers,
|
||||
};
|
||||
metrics.on_peer_connected(peer_set);
|
||||
metrics.note_peer_count(peer_set, peer_map.len());
|
||||
|
||||
match peer_map.entry(peer.clone()) {
|
||||
hash_map::Entry::Occupied(_) => continue,
|
||||
hash_map::Entry::Vacant(vacant) => {
|
||||
vacant.insert(PeerData { view: View::default() });
|
||||
}
|
||||
}
|
||||
shared.local_view.clone().unwrap_or(View::default())
|
||||
};
|
||||
|
||||
metrics.on_peer_connected(peer_set);
|
||||
metrics.note_peer_count(peer_set, peer_map.len());
|
||||
let maybe_authority =
|
||||
authority_discovery_service.get_authority_id_by_peer_id(peer).await;
|
||||
|
||||
shared.local_view.clone().unwrap_or(View::default())
|
||||
match peer_set {
|
||||
PeerSet::Validation => {
|
||||
dispatch_validation_events_to_all(
|
||||
vec![
|
||||
NetworkBridgeEvent::PeerConnected(
|
||||
peer.clone(),
|
||||
role,
|
||||
maybe_authority,
|
||||
),
|
||||
NetworkBridgeEvent::PeerViewChange(peer.clone(), View::default()),
|
||||
],
|
||||
&mut sender,
|
||||
)
|
||||
.await;
|
||||
|
||||
send_message(
|
||||
&mut network_service,
|
||||
vec![peer],
|
||||
PeerSet::Validation,
|
||||
WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(local_view),
|
||||
&metrics,
|
||||
);
|
||||
},
|
||||
PeerSet::Collation => {
|
||||
dispatch_collation_events_to_all(
|
||||
vec![
|
||||
NetworkBridgeEvent::PeerConnected(
|
||||
peer.clone(),
|
||||
role,
|
||||
maybe_authority,
|
||||
),
|
||||
NetworkBridgeEvent::PeerViewChange(peer.clone(), View::default()),
|
||||
],
|
||||
&mut sender,
|
||||
)
|
||||
.await;
|
||||
|
||||
send_message(
|
||||
&mut network_service,
|
||||
vec![peer],
|
||||
PeerSet::Collation,
|
||||
WireMessage::<protocol_v1::CollationProtocol>::ViewUpdate(local_view),
|
||||
&metrics,
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
Some(NetworkEvent::NotificationStreamClosed { remote: peer, protocol }) => {
|
||||
let peer_set = match PeerSet::try_from_protocol_name(&protocol) {
|
||||
None => continue,
|
||||
Some(peer_set) => peer_set,
|
||||
};
|
||||
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
action = "PeerDisconnected",
|
||||
peer_set = ?peer_set,
|
||||
peer = ?peer
|
||||
);
|
||||
|
||||
let was_connected = {
|
||||
let mut shared = shared.0.lock();
|
||||
let peer_map = match peer_set {
|
||||
PeerSet::Validation => &mut shared.validation_peers,
|
||||
PeerSet::Collation => &mut shared.collation_peers,
|
||||
};
|
||||
|
||||
let maybe_authority =
|
||||
authority_discovery_service
|
||||
.get_authority_id_by_peer_id(peer).await;
|
||||
let w = peer_map.remove(&peer).is_some();
|
||||
|
||||
metrics.on_peer_disconnected(peer_set);
|
||||
metrics.note_peer_count(peer_set, peer_map.len());
|
||||
|
||||
w
|
||||
};
|
||||
|
||||
if was_connected {
|
||||
match peer_set {
|
||||
PeerSet::Validation => {
|
||||
dispatch_validation_events_to_all(
|
||||
vec![
|
||||
NetworkBridgeEvent::PeerConnected(peer.clone(), role, maybe_authority),
|
||||
NetworkBridgeEvent::PeerViewChange(
|
||||
peer.clone(),
|
||||
View::default(),
|
||||
),
|
||||
],
|
||||
&mut sender,
|
||||
).await;
|
||||
|
||||
send_message(
|
||||
&mut network_service,
|
||||
vec![peer],
|
||||
PeerSet::Validation,
|
||||
WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(
|
||||
local_view,
|
||||
),
|
||||
&metrics,
|
||||
);
|
||||
}
|
||||
PeerSet::Collation => {
|
||||
dispatch_collation_events_to_all(
|
||||
vec![
|
||||
NetworkBridgeEvent::PeerConnected(peer.clone(), role, maybe_authority),
|
||||
NetworkBridgeEvent::PeerViewChange(
|
||||
peer.clone(),
|
||||
View::default(),
|
||||
),
|
||||
],
|
||||
&mut sender,
|
||||
).await;
|
||||
|
||||
send_message(
|
||||
&mut network_service,
|
||||
vec![peer],
|
||||
PeerSet::Collation,
|
||||
WireMessage::<protocol_v1::CollationProtocol>::ViewUpdate(
|
||||
local_view,
|
||||
),
|
||||
&metrics,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(NetworkEvent::NotificationStreamClosed { remote: peer, protocol }) => {
|
||||
let peer_set = match PeerSet::try_from_protocol_name(&protocol) {
|
||||
None => continue,
|
||||
Some(peer_set) => peer_set,
|
||||
};
|
||||
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
action = "PeerDisconnected",
|
||||
peer_set = ?peer_set,
|
||||
peer = ?peer
|
||||
);
|
||||
|
||||
let was_connected = {
|
||||
let mut shared = shared.0.lock();
|
||||
let peer_map = match peer_set {
|
||||
PeerSet::Validation => &mut shared.validation_peers,
|
||||
PeerSet::Collation => &mut shared.collation_peers,
|
||||
};
|
||||
|
||||
let w = peer_map.remove(&peer).is_some();
|
||||
|
||||
metrics.on_peer_disconnected(peer_set);
|
||||
metrics.note_peer_count(peer_set, peer_map.len());
|
||||
|
||||
w
|
||||
};
|
||||
|
||||
if was_connected {
|
||||
match peer_set {
|
||||
PeerSet::Validation => dispatch_validation_event_to_all(
|
||||
PeerSet::Validation =>
|
||||
dispatch_validation_event_to_all(
|
||||
NetworkBridgeEvent::PeerDisconnected(peer),
|
||||
&mut sender,
|
||||
).await,
|
||||
PeerSet::Collation => dispatch_collation_event_to_all(
|
||||
)
|
||||
.await,
|
||||
PeerSet::Collation =>
|
||||
dispatch_collation_event_to_all(
|
||||
NetworkBridgeEvent::PeerDisconnected(peer),
|
||||
&mut sender,
|
||||
).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(NetworkEvent::NotificationsReceived { remote, messages }) => {
|
||||
let v_messages: Result<Vec<_>, _> = messages
|
||||
.iter()
|
||||
.filter(|(protocol, _)| {
|
||||
protocol == &PeerSet::Validation.into_protocol_name()
|
||||
})
|
||||
.map(|(_, msg_bytes)| {
|
||||
WireMessage::decode(&mut msg_bytes.as_ref())
|
||||
.map(|m| (m, msg_bytes.len()))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let v_messages = match v_messages {
|
||||
Err(_) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
action = "ReportPeer"
|
||||
);
|
||||
|
||||
network_service.report_peer(remote, MALFORMED_MESSAGE_COST);
|
||||
continue;
|
||||
}
|
||||
Ok(v) => v,
|
||||
};
|
||||
|
||||
let c_messages: Result<Vec<_>, _> = messages
|
||||
.iter()
|
||||
.filter(|(protocol, _)| {
|
||||
protocol == &PeerSet::Collation.into_protocol_name()
|
||||
})
|
||||
.map(|(_, msg_bytes)| {
|
||||
WireMessage::decode(&mut msg_bytes.as_ref())
|
||||
.map(|m| (m, msg_bytes.len()))
|
||||
})
|
||||
.collect();
|
||||
|
||||
match c_messages {
|
||||
Err(_) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
action = "ReportPeer"
|
||||
);
|
||||
|
||||
network_service.report_peer(remote, MALFORMED_MESSAGE_COST);
|
||||
continue;
|
||||
}
|
||||
Ok(c_messages) => {
|
||||
if v_messages.is_empty() && c_messages.is_empty() {
|
||||
continue;
|
||||
} else {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
action = "PeerMessages",
|
||||
peer = ?remote,
|
||||
num_validation_messages = %v_messages.len(),
|
||||
num_collation_messages = %c_messages.len()
|
||||
);
|
||||
|
||||
if !v_messages.is_empty() {
|
||||
let (events, reports) = handle_peer_messages(
|
||||
remote.clone(),
|
||||
PeerSet::Validation,
|
||||
&mut shared.0.lock().validation_peers,
|
||||
v_messages,
|
||||
&metrics,
|
||||
);
|
||||
|
||||
for report in reports {
|
||||
network_service.report_peer(remote.clone(), report);
|
||||
}
|
||||
|
||||
dispatch_validation_events_to_all(events, &mut sender).await;
|
||||
}
|
||||
|
||||
if !c_messages.is_empty() {
|
||||
let (events, reports) = handle_peer_messages(
|
||||
remote.clone(),
|
||||
PeerSet::Collation,
|
||||
&mut shared.0.lock().collation_peers,
|
||||
c_messages,
|
||||
&metrics,
|
||||
);
|
||||
|
||||
for report in reports {
|
||||
network_service.report_peer(remote.clone(), report);
|
||||
}
|
||||
|
||||
|
||||
dispatch_collation_events_to_all(events, &mut sender).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.await,
|
||||
}
|
||||
}
|
||||
},
|
||||
req_res_event = request_multiplexer.next() => match req_res_event {
|
||||
None => return Err(UnexpectedAbort::RequestStreamConcluded),
|
||||
Some(Err(err)) => {
|
||||
network_service.report_peer(err.peer, MALFORMED_MESSAGE_COST);
|
||||
}
|
||||
Some(Ok(msg)) => {
|
||||
sender.send_message(msg).await;
|
||||
Some(NetworkEvent::NotificationsReceived { remote, messages }) => {
|
||||
let v_messages: Result<Vec<_>, _> = messages
|
||||
.iter()
|
||||
.filter(|(protocol, _)| protocol == &PeerSet::Validation.into_protocol_name())
|
||||
.map(|(_, msg_bytes)| {
|
||||
WireMessage::decode(&mut msg_bytes.as_ref()).map(|m| (m, msg_bytes.len()))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let v_messages = match v_messages {
|
||||
Err(_) => {
|
||||
tracing::debug!(target: LOG_TARGET, action = "ReportPeer");
|
||||
|
||||
network_service.report_peer(remote, MALFORMED_MESSAGE_COST);
|
||||
continue
|
||||
},
|
||||
Ok(v) => v,
|
||||
};
|
||||
|
||||
let c_messages: Result<Vec<_>, _> = messages
|
||||
.iter()
|
||||
.filter(|(protocol, _)| protocol == &PeerSet::Collation.into_protocol_name())
|
||||
.map(|(_, msg_bytes)| {
|
||||
WireMessage::decode(&mut msg_bytes.as_ref()).map(|m| (m, msg_bytes.len()))
|
||||
})
|
||||
.collect();
|
||||
|
||||
match c_messages {
|
||||
Err(_) => {
|
||||
tracing::debug!(target: LOG_TARGET, action = "ReportPeer");
|
||||
|
||||
network_service.report_peer(remote, MALFORMED_MESSAGE_COST);
|
||||
continue
|
||||
},
|
||||
Ok(c_messages) =>
|
||||
if v_messages.is_empty() && c_messages.is_empty() {
|
||||
continue
|
||||
} else {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
action = "PeerMessages",
|
||||
peer = ?remote,
|
||||
num_validation_messages = %v_messages.len(),
|
||||
num_collation_messages = %c_messages.len()
|
||||
);
|
||||
|
||||
if !v_messages.is_empty() {
|
||||
let (events, reports) = handle_peer_messages(
|
||||
remote.clone(),
|
||||
PeerSet::Validation,
|
||||
&mut shared.0.lock().validation_peers,
|
||||
v_messages,
|
||||
&metrics,
|
||||
);
|
||||
|
||||
for report in reports {
|
||||
network_service.report_peer(remote.clone(), report);
|
||||
}
|
||||
|
||||
dispatch_validation_events_to_all(events, &mut sender).await;
|
||||
}
|
||||
|
||||
if !c_messages.is_empty() {
|
||||
let (events, reports) = handle_peer_messages(
|
||||
remote.clone(),
|
||||
PeerSet::Collation,
|
||||
&mut shared.0.lock().collation_peers,
|
||||
c_messages,
|
||||
&metrics,
|
||||
);
|
||||
|
||||
for report in reports {
|
||||
network_service.report_peer(remote.clone(), report);
|
||||
}
|
||||
|
||||
dispatch_collation_events_to_all(events, &mut sender).await;
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -881,28 +842,14 @@ where
|
||||
{
|
||||
let shared = Shared::default();
|
||||
|
||||
let NetworkBridge {
|
||||
network_service,
|
||||
mut request_multiplexer,
|
||||
authority_discovery_service,
|
||||
metrics,
|
||||
sync_oracle,
|
||||
} = bridge;
|
||||
|
||||
let statement_receiver = request_multiplexer
|
||||
.get_statement_fetching()
|
||||
.expect("Gets initialized, must be `Some` on startup. qed.");
|
||||
|
||||
let dispute_receiver = request_multiplexer
|
||||
.get_dispute_sending()
|
||||
.expect("Gets initialized, must be `Some` on startup. qed.");
|
||||
let NetworkBridge { network_service, authority_discovery_service, metrics, sync_oracle } =
|
||||
bridge;
|
||||
|
||||
let (remote, network_event_handler) = handle_network_messages(
|
||||
ctx.sender().clone(),
|
||||
network_service.clone(),
|
||||
network_stream,
|
||||
authority_discovery_service.clone(),
|
||||
request_multiplexer,
|
||||
metrics.clone(),
|
||||
shared.clone(),
|
||||
)
|
||||
@@ -910,11 +857,6 @@ where
|
||||
|
||||
ctx.spawn("network-bridge-network-worker", Box::pin(remote))?;
|
||||
|
||||
ctx.send_message(DisputeDistributionMessage::DisputeSendingReceiver(dispute_receiver))
|
||||
.await;
|
||||
ctx.send_message(StatementDistributionMessage::StatementFetchingReceiver(statement_receiver))
|
||||
.await;
|
||||
|
||||
let subsystem_event_handler = handle_subsystem_messages(
|
||||
ctx,
|
||||
network_service,
|
||||
@@ -951,13 +893,6 @@ where
|
||||
);
|
||||
Err(SubsystemError::Context("Incoming network event stream concluded.".to_string()))
|
||||
},
|
||||
Err(UnexpectedAbort::RequestStreamConcluded) => {
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
"Shutting down Network Bridge: underlying request stream concluded"
|
||||
);
|
||||
Err(SubsystemError::Context("Incoming network request stream concluded".to_string()))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{pin::Pin, unreachable};
|
||||
|
||||
use futures::{
|
||||
channel::mpsc,
|
||||
stream::{FusedStream, Stream},
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use parity_scale_codec::{Decode, Error as DecodingError};
|
||||
|
||||
use sc_network::{config as network, PeerId};
|
||||
|
||||
use polkadot_node_network_protocol::request_response::{
|
||||
request::IncomingRequest, v1, Protocol, RequestResponseConfig,
|
||||
};
|
||||
use polkadot_overseer::AllMessages;
|
||||
|
||||
/// Multiplex incoming network requests.
|
||||
///
|
||||
/// This multiplexer consumes all request streams and makes them a `Stream` of a single message
|
||||
/// type, useful for the network bridge to send them via the `Overseer` to other subsystems.
|
||||
///
|
||||
/// The resulting stream will end once any of its input ends.
|
||||
///
|
||||
// TODO: Get rid of this: <https://github.com/paritytech/polkadot/issues/2842>
|
||||
pub struct RequestMultiplexer {
|
||||
receivers: Vec<(Protocol, mpsc::Receiver<network::IncomingRequest>)>,
|
||||
statement_fetching: Option<mpsc::Receiver<network::IncomingRequest>>,
|
||||
dispute_sending: Option<mpsc::Receiver<network::IncomingRequest>>,
|
||||
next_poll: usize,
|
||||
}
|
||||
|
||||
/// Multiplexing can fail in case of invalid messages.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct RequestMultiplexError {
|
||||
/// The peer that sent the invalid message.
|
||||
pub peer: PeerId,
|
||||
/// The error that occurred.
|
||||
pub error: DecodingError,
|
||||
}
|
||||
|
||||
impl RequestMultiplexer {
|
||||
/// Create a new `RequestMultiplexer`.
|
||||
///
|
||||
/// This function uses `Protocol::get_config` for each available protocol and creates a
|
||||
/// `RequestMultiplexer` from it. The returned `RequestResponseConfig`s must be passed to the
|
||||
/// network implementation.
|
||||
pub fn new() -> (Self, Vec<RequestResponseConfig>) {
|
||||
let (mut receivers, cfgs): (Vec<_>, Vec<_>) = Protocol::iter()
|
||||
.map(|p| {
|
||||
let (rx, cfg) = p.get_config();
|
||||
((p, rx), cfg)
|
||||
})
|
||||
.unzip();
|
||||
|
||||
// Ok this code is ugly as hell, it is also a hack, see https://github.com/paritytech/polkadot/issues/2842.
|
||||
// But it works and is executed on startup so, if anything is wrong here it will be noticed immediately.
|
||||
let index = receivers
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(
|
||||
|(i, (p, _))| if let Protocol::StatementFetching = p { Some(i) } else { None },
|
||||
)
|
||||
.expect("Statement fetching must be registered. qed.");
|
||||
let statement_fetching = Some(receivers.remove(index).1);
|
||||
|
||||
let index = receivers
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(i, (p, _))| if let Protocol::DisputeSending = p { Some(i) } else { None })
|
||||
.expect("Dispute sending must be registered. qed.");
|
||||
let dispute_sending = Some(receivers.remove(index).1);
|
||||
|
||||
(Self { receivers, statement_fetching, dispute_sending, next_poll: 0 }, cfgs)
|
||||
}
|
||||
|
||||
/// Get the receiver for handling statement fetching requests.
|
||||
///
|
||||
/// This function will only return `Some` once.
|
||||
pub fn get_statement_fetching(&mut self) -> Option<mpsc::Receiver<network::IncomingRequest>> {
|
||||
std::mem::take(&mut self.statement_fetching)
|
||||
}
|
||||
|
||||
/// Get the receiver for handling dispute sending requests.
|
||||
///
|
||||
/// This function will only return `Some` once.
|
||||
pub fn get_dispute_sending(&mut self) -> Option<mpsc::Receiver<network::IncomingRequest>> {
|
||||
std::mem::take(&mut self.dispute_sending)
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for RequestMultiplexer {
|
||||
type Item = Result<AllMessages, RequestMultiplexError>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let len = self.receivers.len();
|
||||
let mut count = len;
|
||||
let mut i = self.next_poll;
|
||||
let mut result = Poll::Ready(None);
|
||||
// Poll streams in round robin fashion:
|
||||
while count > 0 {
|
||||
// % safe, because count initialized to len, loop would not be entered if 0, also
|
||||
// length of receivers is fixed.
|
||||
let (p, rx): &mut (_, _) = &mut self.receivers[i % len];
|
||||
// Avoid panic:
|
||||
if rx.is_terminated() {
|
||||
// Early return, we don't want to update next_poll.
|
||||
return Poll::Ready(None)
|
||||
}
|
||||
i += 1;
|
||||
count -= 1;
|
||||
match Pin::new(rx).poll_next(cx) {
|
||||
Poll::Pending => result = Poll::Pending,
|
||||
// We are done, once a single receiver is done.
|
||||
Poll::Ready(None) => return Poll::Ready(None),
|
||||
Poll::Ready(Some(v)) => {
|
||||
result = Poll::Ready(Some(multiplex_single(*p, v)));
|
||||
break
|
||||
},
|
||||
}
|
||||
}
|
||||
self.next_poll = i;
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedStream for RequestMultiplexer {
|
||||
fn is_terminated(&self) -> bool {
|
||||
let len = self.receivers.len();
|
||||
if len == 0 {
|
||||
return true
|
||||
}
|
||||
let (_, rx) = &self.receivers[self.next_poll % len];
|
||||
rx.is_terminated()
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a single raw incoming request into a `MultiplexMessage`.
|
||||
fn multiplex_single(
|
||||
p: Protocol,
|
||||
network::IncomingRequest { payload, peer, pending_response }: network::IncomingRequest,
|
||||
) -> Result<AllMessages, RequestMultiplexError> {
|
||||
let r = match p {
|
||||
Protocol::ChunkFetching => AllMessages::from(IncomingRequest::new(
|
||||
peer,
|
||||
decode_with_peer::<v1::ChunkFetchingRequest>(peer, payload)?,
|
||||
pending_response,
|
||||
)),
|
||||
Protocol::CollationFetching => AllMessages::from(IncomingRequest::new(
|
||||
peer,
|
||||
decode_with_peer::<v1::CollationFetchingRequest>(peer, payload)?,
|
||||
pending_response,
|
||||
)),
|
||||
Protocol::PoVFetching => AllMessages::from(IncomingRequest::new(
|
||||
peer,
|
||||
decode_with_peer::<v1::PoVFetchingRequest>(peer, payload)?,
|
||||
pending_response,
|
||||
)),
|
||||
Protocol::AvailableDataFetching => AllMessages::from(IncomingRequest::new(
|
||||
peer,
|
||||
decode_with_peer::<v1::AvailableDataFetchingRequest>(peer, payload)?,
|
||||
pending_response,
|
||||
)),
|
||||
Protocol::StatementFetching => {
|
||||
unreachable!("Statement fetching requests are handled directly. qed.");
|
||||
},
|
||||
Protocol::DisputeSending => {
|
||||
unreachable!("Dispute sending request are handled directly. qed.");
|
||||
},
|
||||
};
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
fn decode_with_peer<Req: Decode>(
|
||||
peer: PeerId,
|
||||
payload: Vec<u8>,
|
||||
) -> Result<Req, RequestMultiplexError> {
|
||||
Req::decode(&mut payload.as_ref()).map_err(|error| RequestMultiplexError { peer, error })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use futures::{prelude::*, stream::FusedStream};
|
||||
|
||||
use super::RequestMultiplexer;
|
||||
#[test]
|
||||
fn check_exhaustion_safety() {
|
||||
// Create and end streams:
|
||||
fn drop_configs() -> RequestMultiplexer {
|
||||
let (multiplexer, _) = RequestMultiplexer::new();
|
||||
multiplexer
|
||||
}
|
||||
let multiplexer = drop_configs();
|
||||
futures::executor::block_on(async move {
|
||||
let mut f = multiplexer;
|
||||
assert!(f.next().await.is_none());
|
||||
assert!(f.is_terminated());
|
||||
assert!(f.next().await.is_none());
|
||||
assert!(f.is_terminated());
|
||||
assert!(f.next().await.is_none());
|
||||
assert!(f.is_terminated());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ use std::{
|
||||
|
||||
use sc_network::{Event as NetworkEvent, IfDisconnected};
|
||||
|
||||
use polkadot_node_network_protocol::{request_response::request::Requests, view, ObservedRole};
|
||||
use polkadot_node_network_protocol::{request_response::outgoing::Requests, view, ObservedRole};
|
||||
use polkadot_node_subsystem_test_helpers::{
|
||||
SingleItemSink, SingleItemStream, TestSubsystemContextHandle,
|
||||
};
|
||||
@@ -41,7 +41,7 @@ use polkadot_subsystem::{
|
||||
},
|
||||
ActiveLeavesUpdate, FromOverseer, LeafStatus, OverseerSignal,
|
||||
};
|
||||
use sc_network::{config::RequestResponseConfig, Multiaddr};
|
||||
use sc_network::Multiaddr;
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
|
||||
use crate::{network::Network, validator_discovery::AuthorityDiscovery, Rep};
|
||||
@@ -61,7 +61,6 @@ pub enum NetworkAction {
|
||||
struct TestNetwork {
|
||||
net_events: Arc<Mutex<Option<SingleItemStream<NetworkEvent>>>>,
|
||||
action_tx: Arc<Mutex<metered::UnboundedMeteredSender<NetworkAction>>>,
|
||||
_req_configs: Vec<RequestResponseConfig>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -74,9 +73,7 @@ struct TestNetworkHandle {
|
||||
net_tx: SingleItemSink<NetworkEvent>,
|
||||
}
|
||||
|
||||
fn new_test_network(
|
||||
req_configs: Vec<RequestResponseConfig>,
|
||||
) -> (TestNetwork, TestNetworkHandle, TestAuthorityDiscovery) {
|
||||
fn new_test_network() -> (TestNetwork, TestNetworkHandle, TestAuthorityDiscovery) {
|
||||
let (net_tx, net_rx) = polkadot_node_subsystem_test_helpers::single_item_sink();
|
||||
let (action_tx, action_rx) = metered::unbounded();
|
||||
|
||||
@@ -84,7 +81,6 @@ fn new_test_network(
|
||||
TestNetwork {
|
||||
net_events: Arc::new(Mutex::new(Some(net_rx))),
|
||||
action_tx: Arc::new(Mutex::new(action_tx)),
|
||||
_req_configs: req_configs,
|
||||
},
|
||||
TestNetworkHandle { action_rx, net_tx },
|
||||
TestAuthorityDiscovery,
|
||||
@@ -285,8 +281,7 @@ fn test_harness<T: Future<Output = VirtualOverseer>>(
|
||||
test: impl FnOnce(TestHarness) -> T,
|
||||
) {
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (request_multiplexer, req_configs) = RequestMultiplexer::new();
|
||||
let (mut network, network_handle, discovery) = new_test_network(req_configs);
|
||||
let (mut network, network_handle, discovery) = new_test_network();
|
||||
let (context, virtual_overseer) =
|
||||
polkadot_node_subsystem_test_helpers::make_subsystem_context(pool);
|
||||
let network_stream = network.event_stream();
|
||||
@@ -294,7 +289,6 @@ fn test_harness<T: Future<Output = VirtualOverseer>>(
|
||||
let bridge = NetworkBridge {
|
||||
network_service: network,
|
||||
authority_discovery_service: discovery,
|
||||
request_multiplexer,
|
||||
metrics: Metrics(None),
|
||||
sync_oracle,
|
||||
};
|
||||
@@ -642,17 +636,6 @@ fn peer_view_updates_sent_via_overseer() {
|
||||
|
||||
let view = view![Hash::repeat_byte(1)];
|
||||
|
||||
assert_matches!(
|
||||
virtual_overseer.recv().await,
|
||||
AllMessages::DisputeDistribution(DisputeDistributionMessage::DisputeSendingReceiver(_))
|
||||
);
|
||||
assert_matches!(
|
||||
virtual_overseer.recv().await,
|
||||
AllMessages::StatementDistribution(
|
||||
StatementDistributionMessage::StatementFetchingReceiver(_)
|
||||
)
|
||||
);
|
||||
|
||||
// bridge will inform about all connected peers.
|
||||
{
|
||||
assert_sends_validation_event_to_all(
|
||||
@@ -696,17 +679,6 @@ fn peer_messages_sent_via_overseer() {
|
||||
.connect_peer(peer.clone(), PeerSet::Validation, ObservedRole::Full)
|
||||
.await;
|
||||
|
||||
assert_matches!(
|
||||
virtual_overseer.recv().await,
|
||||
AllMessages::DisputeDistribution(DisputeDistributionMessage::DisputeSendingReceiver(_))
|
||||
);
|
||||
assert_matches!(
|
||||
virtual_overseer.recv().await,
|
||||
AllMessages::StatementDistribution(
|
||||
StatementDistributionMessage::StatementFetchingReceiver(_)
|
||||
)
|
||||
);
|
||||
|
||||
// bridge will inform about all connected peers.
|
||||
{
|
||||
assert_sends_validation_event_to_all(
|
||||
@@ -770,17 +742,6 @@ fn peer_disconnect_from_just_one_peerset() {
|
||||
|
||||
let peer = PeerId::random();
|
||||
|
||||
assert_matches!(
|
||||
virtual_overseer.recv().await,
|
||||
AllMessages::DisputeDistribution(DisputeDistributionMessage::DisputeSendingReceiver(_))
|
||||
);
|
||||
assert_matches!(
|
||||
virtual_overseer.recv().await,
|
||||
AllMessages::StatementDistribution(
|
||||
StatementDistributionMessage::StatementFetchingReceiver(_)
|
||||
)
|
||||
);
|
||||
|
||||
network_handle
|
||||
.connect_peer(peer.clone(), PeerSet::Validation, ObservedRole::Full)
|
||||
.await;
|
||||
@@ -864,17 +825,6 @@ fn relays_collation_protocol_messages() {
|
||||
let peer_a = PeerId::random();
|
||||
let peer_b = PeerId::random();
|
||||
|
||||
assert_matches!(
|
||||
virtual_overseer.recv().await,
|
||||
AllMessages::DisputeDistribution(DisputeDistributionMessage::DisputeSendingReceiver(_))
|
||||
);
|
||||
assert_matches!(
|
||||
virtual_overseer.recv().await,
|
||||
AllMessages::StatementDistribution(
|
||||
StatementDistributionMessage::StatementFetchingReceiver(_)
|
||||
)
|
||||
);
|
||||
|
||||
network_handle
|
||||
.connect_peer(peer_a.clone(), PeerSet::Validation, ObservedRole::Full)
|
||||
.await;
|
||||
@@ -975,17 +925,6 @@ fn different_views_on_different_peer_sets() {
|
||||
.connect_peer(peer.clone(), PeerSet::Collation, ObservedRole::Full)
|
||||
.await;
|
||||
|
||||
assert_matches!(
|
||||
virtual_overseer.recv().await,
|
||||
AllMessages::DisputeDistribution(DisputeDistributionMessage::DisputeSendingReceiver(_))
|
||||
);
|
||||
assert_matches!(
|
||||
virtual_overseer.recv().await,
|
||||
AllMessages::StatementDistribution(
|
||||
StatementDistributionMessage::StatementFetchingReceiver(_)
|
||||
)
|
||||
);
|
||||
|
||||
// bridge will inform about all connected peers.
|
||||
{
|
||||
assert_sends_validation_event_to_all(
|
||||
@@ -1149,17 +1088,6 @@ fn send_messages_to_peers() {
|
||||
.connect_peer(peer.clone(), PeerSet::Collation, ObservedRole::Full)
|
||||
.await;
|
||||
|
||||
assert_matches!(
|
||||
virtual_overseer.recv().await,
|
||||
AllMessages::DisputeDistribution(DisputeDistributionMessage::DisputeSendingReceiver(_))
|
||||
);
|
||||
assert_matches!(
|
||||
virtual_overseer.recv().await,
|
||||
AllMessages::StatementDistribution(
|
||||
StatementDistributionMessage::StatementFetchingReceiver(_)
|
||||
)
|
||||
);
|
||||
|
||||
// bridge will inform about all connected peers.
|
||||
{
|
||||
assert_sends_validation_event_to_all(
|
||||
@@ -1328,17 +1256,6 @@ fn our_view_updates_decreasing_order_and_limited_to_max() {
|
||||
.await;
|
||||
}
|
||||
|
||||
assert_matches!(
|
||||
virtual_overseer.recv().await,
|
||||
AllMessages::DisputeDistribution(DisputeDistributionMessage::DisputeSendingReceiver(_))
|
||||
);
|
||||
assert_matches!(
|
||||
virtual_overseer.recv().await,
|
||||
AllMessages::StatementDistribution(
|
||||
StatementDistributionMessage::StatementFetchingReceiver(_)
|
||||
)
|
||||
);
|
||||
|
||||
let our_views = (1..=MAX_VIEW_HEADS).rev().map(|start| {
|
||||
OurView::new(
|
||||
(start..=MAX_VIEW_HEADS)
|
||||
|
||||
@@ -125,7 +125,7 @@ mod tests {
|
||||
|
||||
use async_trait::async_trait;
|
||||
use futures::stream::BoxStream;
|
||||
use polkadot_node_network_protocol::{request_response::request::Requests, PeerId};
|
||||
use polkadot_node_network_protocol::{request_response::outgoing::Requests, PeerId};
|
||||
use sc_network::{Event as NetworkEvent, IfDisconnected};
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
|
||||
@@ -6,6 +6,7 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
always-assert = "0.1.2"
|
||||
derive_more = "0.99.14"
|
||||
futures = "0.3.15"
|
||||
futures-timer = "3"
|
||||
thiserror = "1.0.26"
|
||||
@@ -29,5 +30,6 @@ assert_matches = "1.4.0"
|
||||
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", features = ["std"] }
|
||||
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sc-network = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
parity-scale-codec = { version = "2.0.0", features = ["std"] }
|
||||
|
||||
polkadot-subsystem-testhelpers = { package = "polkadot-node-subsystem-test-helpers", path = "../../subsystem-test-helpers" }
|
||||
|
||||
@@ -20,15 +20,17 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use futures::{channel::oneshot, select, stream::FuturesUnordered, Future, FutureExt, StreamExt};
|
||||
use futures::{
|
||||
channel::oneshot, pin_mut, select, stream::FuturesUnordered, Future, FutureExt, StreamExt,
|
||||
};
|
||||
use sp_core::Pair;
|
||||
|
||||
use polkadot_node_network_protocol::{
|
||||
peer_set::PeerSet,
|
||||
request_response::{
|
||||
request::OutgoingResponse,
|
||||
v1::{CollationFetchingRequest, CollationFetchingResponse},
|
||||
IncomingRequest,
|
||||
incoming::{self, OutgoingResponse},
|
||||
v1::{self as request_v1, CollationFetchingRequest, CollationFetchingResponse},
|
||||
IncomingRequest, IncomingRequestReceiver,
|
||||
},
|
||||
v1 as protocol_v1, OurView, PeerId, UnifiedReputationChange as Rep, View,
|
||||
};
|
||||
@@ -49,11 +51,12 @@ use polkadot_subsystem::{
|
||||
};
|
||||
|
||||
use super::{Result, LOG_TARGET};
|
||||
use crate::error::{log_error, Fatal, NonFatal};
|
||||
use crate::error::{log_error, Fatal, FatalResult, NonFatal};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
const COST_INVALID_REQUEST: Rep = Rep::CostMajor("Peer sent unparsable request");
|
||||
const COST_UNEXPECTED_MESSAGE: Rep = Rep::CostMinor("An unexpected message");
|
||||
const COST_APPARENT_FLOOD: Rep =
|
||||
Rep::CostMinor("Message received when previous one was still being processed");
|
||||
@@ -684,74 +687,6 @@ where
|
||||
);
|
||||
}
|
||||
},
|
||||
CollationFetchingRequest(incoming) => {
|
||||
let _span = state
|
||||
.span_per_relay_parent
|
||||
.get(&incoming.payload.relay_parent)
|
||||
.map(|s| s.child("request-collation"));
|
||||
match state.collating_on {
|
||||
Some(our_para_id) =>
|
||||
if our_para_id == incoming.payload.para_id {
|
||||
let (receipt, pov) = if let Some(collation) =
|
||||
state.collations.get_mut(&incoming.payload.relay_parent)
|
||||
{
|
||||
collation.status.advance_to_requested();
|
||||
(collation.receipt.clone(), collation.pov.clone())
|
||||
} else {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
relay_parent = %incoming.payload.relay_parent,
|
||||
"received a `RequestCollation` for a relay parent we don't have collation stored.",
|
||||
);
|
||||
|
||||
return Ok(())
|
||||
};
|
||||
|
||||
state.metrics.on_collation_sent_requested();
|
||||
|
||||
let _span = _span.as_ref().map(|s| s.child("sending"));
|
||||
|
||||
let waiting = state
|
||||
.waiting_collation_fetches
|
||||
.entry(incoming.payload.relay_parent)
|
||||
.or_default();
|
||||
|
||||
if !waiting.waiting_peers.insert(incoming.peer) {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Dropping incoming request as peer has a request in flight already."
|
||||
);
|
||||
ctx.send_message(NetworkBridgeMessage::ReportPeer(
|
||||
incoming.peer,
|
||||
COST_APPARENT_FLOOD,
|
||||
))
|
||||
.await;
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
if waiting.collation_fetch_active {
|
||||
waiting.waiting.push_back(incoming);
|
||||
} else {
|
||||
waiting.collation_fetch_active = true;
|
||||
send_collation(state, incoming, receipt, pov).await;
|
||||
}
|
||||
} else {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
for_para_id = %incoming.payload.para_id,
|
||||
our_para_id = %our_para_id,
|
||||
"received a `CollationFetchingRequest` for unexpected para_id",
|
||||
);
|
||||
},
|
||||
None => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
for_para_id = %incoming.payload.para_id,
|
||||
"received a `RequestCollation` while not collating on any para",
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
@@ -875,6 +810,80 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Process an incoming network request for a collation.
|
||||
async fn handle_incoming_request<Context>(
|
||||
ctx: &mut Context,
|
||||
state: &mut State,
|
||||
req: IncomingRequest<request_v1::CollationFetchingRequest>,
|
||||
) -> Result<()>
|
||||
where
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
{
|
||||
let _span = state
|
||||
.span_per_relay_parent
|
||||
.get(&req.payload.relay_parent)
|
||||
.map(|s| s.child("request-collation"));
|
||||
|
||||
match state.collating_on {
|
||||
Some(our_para_id) if our_para_id == req.payload.para_id => {
|
||||
let (receipt, pov) =
|
||||
if let Some(collation) = state.collations.get_mut(&req.payload.relay_parent) {
|
||||
collation.status.advance_to_requested();
|
||||
(collation.receipt.clone(), collation.pov.clone())
|
||||
} else {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
relay_parent = %req.payload.relay_parent,
|
||||
"received a `RequestCollation` for a relay parent we don't have collation stored.",
|
||||
);
|
||||
|
||||
return Ok(())
|
||||
};
|
||||
|
||||
state.metrics.on_collation_sent_requested();
|
||||
|
||||
let _span = _span.as_ref().map(|s| s.child("sending"));
|
||||
|
||||
let waiting =
|
||||
state.waiting_collation_fetches.entry(req.payload.relay_parent).or_default();
|
||||
|
||||
if !waiting.waiting_peers.insert(req.peer) {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Dropping incoming request as peer has a request in flight already."
|
||||
);
|
||||
ctx.send_message(NetworkBridgeMessage::ReportPeer(req.peer, COST_APPARENT_FLOOD))
|
||||
.await;
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
if waiting.collation_fetch_active {
|
||||
waiting.waiting.push_back(req);
|
||||
} else {
|
||||
waiting.collation_fetch_active = true;
|
||||
send_collation(state, req, receipt, pov).await;
|
||||
}
|
||||
},
|
||||
Some(our_para_id) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
for_para_id = %req.payload.para_id,
|
||||
our_para_id = %our_para_id,
|
||||
"received a `CollationFetchingRequest` for unexpected para_id",
|
||||
);
|
||||
},
|
||||
None => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
for_para_id = %req.payload.para_id,
|
||||
"received a `RequestCollation` while not collating on any para",
|
||||
);
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Our view has changed.
|
||||
async fn handle_peer_view_change<Context>(
|
||||
ctx: &mut Context,
|
||||
@@ -994,8 +1003,9 @@ pub(crate) async fn run<Context>(
|
||||
mut ctx: Context,
|
||||
local_peer_id: PeerId,
|
||||
collator_pair: CollatorPair,
|
||||
mut req_receiver: IncomingRequestReceiver<request_v1::CollationFetchingRequest>,
|
||||
metrics: Metrics,
|
||||
) -> Result<()>
|
||||
) -> FatalResult<()>
|
||||
where
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
@@ -1006,6 +1016,8 @@ where
|
||||
let mut runtime = RuntimeInfo::new(None);
|
||||
|
||||
loop {
|
||||
let recv_req = req_receiver.recv(|| vec![COST_INVALID_REQUEST]).fuse();
|
||||
pin_mut!(recv_req);
|
||||
select! {
|
||||
msg = ctx.recv().fuse() => match msg.map_err(Fatal::SubsystemReceive)? {
|
||||
FromOverseer::Communication { msg } => {
|
||||
@@ -1039,6 +1051,25 @@ where
|
||||
send_collation(&mut state, next, receipt, pov).await;
|
||||
}
|
||||
}
|
||||
in_req = recv_req => {
|
||||
match in_req {
|
||||
Ok(req) => {
|
||||
log_error(
|
||||
handle_incoming_request(&mut ctx, &mut state, req).await,
|
||||
"Handling incoming request"
|
||||
)?;
|
||||
}
|
||||
Err(incoming::Error::Fatal(f)) => return Err(f.into()),
|
||||
Err(incoming::Error::NonFatal(err)) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
?err,
|
||||
"Decoding incoming request failed"
|
||||
);
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,14 +19,17 @@ use super::*;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
use futures::{executor, future, Future};
|
||||
use futures::{executor, future, Future, SinkExt};
|
||||
use futures_timer::Delay;
|
||||
|
||||
use sp_core::{crypto::Pair, Decode};
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
|
||||
use sc_network::config::IncomingRequest as RawIncomingRequest;
|
||||
use sp_core::crypto::Pair;
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
use sp_runtime::traits::AppVerify;
|
||||
|
||||
use polkadot_node_network_protocol::{our_view, request_response::request::IncomingRequest, view};
|
||||
use polkadot_node_network_protocol::{our_view, request_response::IncomingRequest, view};
|
||||
use polkadot_node_primitives::BlockData;
|
||||
use polkadot_node_subsystem_util::TimeoutExt;
|
||||
use polkadot_primitives::v1::{
|
||||
@@ -194,9 +197,10 @@ type VirtualOverseer = test_helpers::TestSubsystemContextHandle<CollatorProtocol
|
||||
|
||||
struct TestHarness {
|
||||
virtual_overseer: VirtualOverseer,
|
||||
req_cfg: sc_network::config::RequestResponseConfig,
|
||||
}
|
||||
|
||||
fn test_harness<T: Future<Output = VirtualOverseer>>(
|
||||
fn test_harness<T: Future<Output = TestHarness>>(
|
||||
local_peer_id: PeerId,
|
||||
collator_pair: CollatorPair,
|
||||
test: impl FnOnce(TestHarness) -> T,
|
||||
@@ -211,22 +215,26 @@ fn test_harness<T: Future<Output = VirtualOverseer>>(
|
||||
|
||||
let (context, virtual_overseer) = test_helpers::make_subsystem_context(pool.clone());
|
||||
|
||||
let subsystem = run(context, local_peer_id, collator_pair, Metrics::default());
|
||||
let (collation_req_receiver, req_cfg) = IncomingRequest::get_config_receiver();
|
||||
let subsystem = async {
|
||||
run(context, local_peer_id, collator_pair, collation_req_receiver, Default::default())
|
||||
.await
|
||||
.unwrap();
|
||||
};
|
||||
|
||||
let test_fut = test(TestHarness { virtual_overseer });
|
||||
let test_fut = test(TestHarness { virtual_overseer, req_cfg });
|
||||
|
||||
futures::pin_mut!(test_fut);
|
||||
futures::pin_mut!(subsystem);
|
||||
|
||||
executor::block_on(future::join(
|
||||
async move {
|
||||
let mut overseer = test_fut.await;
|
||||
overseer_signal(&mut overseer, OverseerSignal::Conclude).await;
|
||||
let mut test_harness = test_fut.await;
|
||||
overseer_signal(&mut test_harness.virtual_overseer, OverseerSignal::Conclude).await;
|
||||
},
|
||||
subsystem,
|
||||
))
|
||||
.1
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
const TIMEOUT: Duration = Duration::from_millis(100);
|
||||
@@ -506,6 +514,7 @@ fn advertise_and_send_collation() {
|
||||
|
||||
test_harness(local_peer_id, collator_pair, |test_harness| async move {
|
||||
let mut virtual_overseer = test_harness.virtual_overseer;
|
||||
let mut req_cfg = test_harness.req_cfg;
|
||||
|
||||
setup_system(&mut virtual_overseer, &test_state).await;
|
||||
|
||||
@@ -537,34 +546,41 @@ fn advertise_and_send_collation() {
|
||||
expect_advertise_collation_msg(&mut virtual_overseer, &peer, test_state.relay_parent).await;
|
||||
|
||||
// Request a collation.
|
||||
let (tx, rx) = oneshot::channel();
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::CollationFetchingRequest(IncomingRequest::new(
|
||||
let (pending_response, rx) = oneshot::channel();
|
||||
req_cfg
|
||||
.inbound_queue
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send(RawIncomingRequest {
|
||||
peer,
|
||||
CollationFetchingRequest {
|
||||
payload: CollationFetchingRequest {
|
||||
relay_parent: test_state.relay_parent,
|
||||
para_id: test_state.para_id,
|
||||
},
|
||||
tx,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
.encode(),
|
||||
pending_response,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
// Second request by same validator should get dropped and peer reported:
|
||||
{
|
||||
let (tx, rx) = oneshot::channel();
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::CollationFetchingRequest(IncomingRequest::new(
|
||||
let (pending_response, rx) = oneshot::channel();
|
||||
|
||||
req_cfg
|
||||
.inbound_queue
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send(RawIncomingRequest {
|
||||
peer,
|
||||
CollationFetchingRequest {
|
||||
payload: CollationFetchingRequest {
|
||||
relay_parent: test_state.relay_parent,
|
||||
para_id: test_state.para_id,
|
||||
},
|
||||
tx,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
.encode(),
|
||||
pending_response,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_matches!(
|
||||
overseer_recv(&mut virtual_overseer).await,
|
||||
AllMessages::NetworkBridge(NetworkBridgeMessage::ReportPeer(bad_peer, _)) => {
|
||||
@@ -598,19 +614,23 @@ fn advertise_and_send_collation() {
|
||||
let peer = test_state.validator_peer_id[2].clone();
|
||||
|
||||
// Re-request a collation.
|
||||
let (tx, rx) = oneshot::channel();
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::CollationFetchingRequest(IncomingRequest::new(
|
||||
let (pending_response, rx) = oneshot::channel();
|
||||
|
||||
req_cfg
|
||||
.inbound_queue
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send(RawIncomingRequest {
|
||||
peer,
|
||||
CollationFetchingRequest {
|
||||
payload: CollationFetchingRequest {
|
||||
relay_parent: old_relay_parent,
|
||||
para_id: test_state.para_id,
|
||||
},
|
||||
tx,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
.encode(),
|
||||
pending_response,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
// Re-requesting collation should fail:
|
||||
rx.await.unwrap_err();
|
||||
|
||||
@@ -629,7 +649,7 @@ fn advertise_and_send_collation() {
|
||||
.await;
|
||||
|
||||
expect_advertise_collation_msg(&mut virtual_overseer, &peer, test_state.relay_parent).await;
|
||||
virtual_overseer
|
||||
TestHarness { virtual_overseer, req_cfg }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -662,18 +682,16 @@ fn collators_declare_to_connected_peers() {
|
||||
let local_peer_id = test_state.local_peer_id.clone();
|
||||
let collator_pair = test_state.collator_pair.clone();
|
||||
|
||||
test_harness(local_peer_id, collator_pair, |test_harness| async move {
|
||||
let mut virtual_overseer = test_harness.virtual_overseer;
|
||||
|
||||
test_harness(local_peer_id, collator_pair, |mut test_harness| async move {
|
||||
let peer = test_state.validator_peer_id[0].clone();
|
||||
let validator_id = test_state.current_group_validator_authority_ids()[0].clone();
|
||||
|
||||
setup_system(&mut virtual_overseer, &test_state).await;
|
||||
setup_system(&mut test_harness.virtual_overseer, &test_state).await;
|
||||
|
||||
// A validator connected to us
|
||||
connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id)).await;
|
||||
expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await;
|
||||
virtual_overseer
|
||||
connect_peer(&mut test_harness.virtual_overseer, peer.clone(), Some(validator_id)).await;
|
||||
expect_declare_msg(&mut test_harness.virtual_overseer, &test_state, &peer).await;
|
||||
test_harness
|
||||
})
|
||||
}
|
||||
|
||||
@@ -683,8 +701,8 @@ fn collations_are_only_advertised_to_validators_with_correct_view() {
|
||||
let local_peer_id = test_state.local_peer_id.clone();
|
||||
let collator_pair = test_state.collator_pair.clone();
|
||||
|
||||
test_harness(local_peer_id, collator_pair, |test_harness| async move {
|
||||
let mut virtual_overseer = test_harness.virtual_overseer;
|
||||
test_harness(local_peer_id, collator_pair, |mut test_harness| async move {
|
||||
let virtual_overseer = &mut test_harness.virtual_overseer;
|
||||
|
||||
let peer = test_state.current_group_validator_peer_ids()[0].clone();
|
||||
let validator_id = test_state.current_group_validator_authority_ids()[0].clone();
|
||||
@@ -692,31 +710,30 @@ fn collations_are_only_advertised_to_validators_with_correct_view() {
|
||||
let peer2 = test_state.current_group_validator_peer_ids()[1].clone();
|
||||
let validator_id2 = test_state.current_group_validator_authority_ids()[1].clone();
|
||||
|
||||
setup_system(&mut virtual_overseer, &test_state).await;
|
||||
setup_system(virtual_overseer, &test_state).await;
|
||||
|
||||
// A validator connected to us
|
||||
connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id)).await;
|
||||
connect_peer(virtual_overseer, peer.clone(), Some(validator_id)).await;
|
||||
|
||||
// Connect the second validator
|
||||
connect_peer(&mut virtual_overseer, peer2.clone(), Some(validator_id2)).await;
|
||||
connect_peer(virtual_overseer, peer2.clone(), Some(validator_id2)).await;
|
||||
|
||||
expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await;
|
||||
expect_declare_msg(&mut virtual_overseer, &test_state, &peer2).await;
|
||||
expect_declare_msg(virtual_overseer, &test_state, &peer).await;
|
||||
expect_declare_msg(virtual_overseer, &test_state, &peer2).await;
|
||||
|
||||
// And let it tell us that it is has the same view.
|
||||
send_peer_view_change(&mut virtual_overseer, &peer2, vec![test_state.relay_parent]).await;
|
||||
send_peer_view_change(virtual_overseer, &peer2, vec![test_state.relay_parent]).await;
|
||||
|
||||
distribute_collation(&mut virtual_overseer, &test_state, true).await;
|
||||
distribute_collation(virtual_overseer, &test_state, true).await;
|
||||
|
||||
expect_advertise_collation_msg(&mut virtual_overseer, &peer2, test_state.relay_parent)
|
||||
.await;
|
||||
expect_advertise_collation_msg(virtual_overseer, &peer2, test_state.relay_parent).await;
|
||||
|
||||
// The other validator announces that it changed its view.
|
||||
send_peer_view_change(&mut virtual_overseer, &peer, vec![test_state.relay_parent]).await;
|
||||
send_peer_view_change(virtual_overseer, &peer, vec![test_state.relay_parent]).await;
|
||||
|
||||
// After changing the view we should receive the advertisement
|
||||
expect_advertise_collation_msg(&mut virtual_overseer, &peer, test_state.relay_parent).await;
|
||||
virtual_overseer
|
||||
expect_advertise_collation_msg(virtual_overseer, &peer, test_state.relay_parent).await;
|
||||
test_harness
|
||||
})
|
||||
}
|
||||
|
||||
@@ -726,8 +743,8 @@ fn collate_on_two_different_relay_chain_blocks() {
|
||||
let local_peer_id = test_state.local_peer_id.clone();
|
||||
let collator_pair = test_state.collator_pair.clone();
|
||||
|
||||
test_harness(local_peer_id, collator_pair, |test_harness| async move {
|
||||
let mut virtual_overseer = test_harness.virtual_overseer;
|
||||
test_harness(local_peer_id, collator_pair, |mut test_harness| async move {
|
||||
let virtual_overseer = &mut test_harness.virtual_overseer;
|
||||
|
||||
let peer = test_state.current_group_validator_peer_ids()[0].clone();
|
||||
let validator_id = test_state.current_group_validator_authority_ids()[0].clone();
|
||||
@@ -735,34 +752,33 @@ fn collate_on_two_different_relay_chain_blocks() {
|
||||
let peer2 = test_state.current_group_validator_peer_ids()[1].clone();
|
||||
let validator_id2 = test_state.current_group_validator_authority_ids()[1].clone();
|
||||
|
||||
setup_system(&mut virtual_overseer, &test_state).await;
|
||||
setup_system(virtual_overseer, &test_state).await;
|
||||
|
||||
// A validator connected to us
|
||||
connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id)).await;
|
||||
connect_peer(virtual_overseer, peer.clone(), Some(validator_id)).await;
|
||||
|
||||
// Connect the second validator
|
||||
connect_peer(&mut virtual_overseer, peer2.clone(), Some(validator_id2)).await;
|
||||
connect_peer(virtual_overseer, peer2.clone(), Some(validator_id2)).await;
|
||||
|
||||
expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await;
|
||||
expect_declare_msg(&mut virtual_overseer, &test_state, &peer2).await;
|
||||
expect_declare_msg(virtual_overseer, &test_state, &peer).await;
|
||||
expect_declare_msg(virtual_overseer, &test_state, &peer2).await;
|
||||
|
||||
distribute_collation(&mut virtual_overseer, &test_state, true).await;
|
||||
distribute_collation(virtual_overseer, &test_state, true).await;
|
||||
|
||||
let old_relay_parent = test_state.relay_parent;
|
||||
|
||||
// Advance to a new round, while informing the subsystem that the old and the new relay parent are active.
|
||||
test_state.advance_to_new_round(&mut virtual_overseer, true).await;
|
||||
test_state.advance_to_new_round(virtual_overseer, true).await;
|
||||
|
||||
distribute_collation(&mut virtual_overseer, &test_state, true).await;
|
||||
distribute_collation(virtual_overseer, &test_state, true).await;
|
||||
|
||||
send_peer_view_change(&mut virtual_overseer, &peer, vec![old_relay_parent]).await;
|
||||
expect_advertise_collation_msg(&mut virtual_overseer, &peer, old_relay_parent).await;
|
||||
send_peer_view_change(virtual_overseer, &peer, vec![old_relay_parent]).await;
|
||||
expect_advertise_collation_msg(virtual_overseer, &peer, old_relay_parent).await;
|
||||
|
||||
send_peer_view_change(&mut virtual_overseer, &peer2, vec![test_state.relay_parent]).await;
|
||||
send_peer_view_change(virtual_overseer, &peer2, vec![test_state.relay_parent]).await;
|
||||
|
||||
expect_advertise_collation_msg(&mut virtual_overseer, &peer2, test_state.relay_parent)
|
||||
.await;
|
||||
virtual_overseer
|
||||
expect_advertise_collation_msg(virtual_overseer, &peer2, test_state.relay_parent).await;
|
||||
test_harness
|
||||
})
|
||||
}
|
||||
|
||||
@@ -772,32 +788,32 @@ fn validator_reconnect_does_not_advertise_a_second_time() {
|
||||
let local_peer_id = test_state.local_peer_id.clone();
|
||||
let collator_pair = test_state.collator_pair.clone();
|
||||
|
||||
test_harness(local_peer_id, collator_pair, |test_harness| async move {
|
||||
let mut virtual_overseer = test_harness.virtual_overseer;
|
||||
test_harness(local_peer_id, collator_pair, |mut test_harness| async move {
|
||||
let virtual_overseer = &mut test_harness.virtual_overseer;
|
||||
|
||||
let peer = test_state.current_group_validator_peer_ids()[0].clone();
|
||||
let validator_id = test_state.current_group_validator_authority_ids()[0].clone();
|
||||
|
||||
setup_system(&mut virtual_overseer, &test_state).await;
|
||||
setup_system(virtual_overseer, &test_state).await;
|
||||
|
||||
// A validator connected to us
|
||||
connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id.clone())).await;
|
||||
expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await;
|
||||
connect_peer(virtual_overseer, peer.clone(), Some(validator_id.clone())).await;
|
||||
expect_declare_msg(virtual_overseer, &test_state, &peer).await;
|
||||
|
||||
distribute_collation(&mut virtual_overseer, &test_state, true).await;
|
||||
distribute_collation(virtual_overseer, &test_state, true).await;
|
||||
|
||||
send_peer_view_change(&mut virtual_overseer, &peer, vec![test_state.relay_parent]).await;
|
||||
expect_advertise_collation_msg(&mut virtual_overseer, &peer, test_state.relay_parent).await;
|
||||
send_peer_view_change(virtual_overseer, &peer, vec![test_state.relay_parent]).await;
|
||||
expect_advertise_collation_msg(virtual_overseer, &peer, test_state.relay_parent).await;
|
||||
|
||||
// Disconnect and reconnect directly
|
||||
disconnect_peer(&mut virtual_overseer, peer.clone()).await;
|
||||
connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id)).await;
|
||||
expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await;
|
||||
disconnect_peer(virtual_overseer, peer.clone()).await;
|
||||
connect_peer(virtual_overseer, peer.clone(), Some(validator_id)).await;
|
||||
expect_declare_msg(virtual_overseer, &test_state, &peer).await;
|
||||
|
||||
send_peer_view_change(&mut virtual_overseer, &peer, vec![test_state.relay_parent]).await;
|
||||
send_peer_view_change(virtual_overseer, &peer, vec![test_state.relay_parent]).await;
|
||||
|
||||
assert!(overseer_recv_with_timeout(&mut virtual_overseer, TIMEOUT).await.is_none());
|
||||
virtual_overseer
|
||||
assert!(overseer_recv_with_timeout(virtual_overseer, TIMEOUT).await.is_none());
|
||||
test_harness
|
||||
})
|
||||
}
|
||||
|
||||
@@ -808,20 +824,20 @@ fn collators_reject_declare_messages() {
|
||||
let collator_pair = test_state.collator_pair.clone();
|
||||
let collator_pair2 = CollatorPair::generate().0;
|
||||
|
||||
test_harness(local_peer_id, collator_pair, |test_harness| async move {
|
||||
let mut virtual_overseer = test_harness.virtual_overseer;
|
||||
test_harness(local_peer_id, collator_pair, |mut test_harness| async move {
|
||||
let virtual_overseer = &mut test_harness.virtual_overseer;
|
||||
|
||||
let peer = test_state.current_group_validator_peer_ids()[0].clone();
|
||||
let validator_id = test_state.current_group_validator_authority_ids()[0].clone();
|
||||
|
||||
setup_system(&mut virtual_overseer, &test_state).await;
|
||||
setup_system(virtual_overseer, &test_state).await;
|
||||
|
||||
// A validator connected to us
|
||||
connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id)).await;
|
||||
expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await;
|
||||
connect_peer(virtual_overseer, peer.clone(), Some(validator_id)).await;
|
||||
expect_declare_msg(virtual_overseer, &test_state, &peer).await;
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerMessage(
|
||||
peer.clone(),
|
||||
protocol_v1::CollatorProtocolMessage::Declare(
|
||||
@@ -834,13 +850,13 @@ fn collators_reject_declare_messages() {
|
||||
.await;
|
||||
|
||||
assert_matches!(
|
||||
overseer_recv(&mut virtual_overseer).await,
|
||||
overseer_recv(virtual_overseer).await,
|
||||
AllMessages::NetworkBridge(NetworkBridgeMessage::DisconnectPeer(
|
||||
p,
|
||||
PeerSet::Collation,
|
||||
)) if p == peer
|
||||
);
|
||||
virtual_overseer
|
||||
test_harness
|
||||
})
|
||||
}
|
||||
|
||||
@@ -862,67 +878,61 @@ where
|
||||
let local_peer_id = test_state.local_peer_id.clone();
|
||||
let collator_pair = test_state.collator_pair.clone();
|
||||
|
||||
test_harness(local_peer_id, collator_pair, |test_harness| async move {
|
||||
let mut virtual_overseer = test_harness.virtual_overseer;
|
||||
test_harness(local_peer_id, collator_pair, |mut test_harness| async move {
|
||||
let virtual_overseer = &mut test_harness.virtual_overseer;
|
||||
let req_cfg = &mut test_harness.req_cfg;
|
||||
|
||||
setup_system(&mut virtual_overseer, &test_state).await;
|
||||
setup_system(virtual_overseer, &test_state).await;
|
||||
|
||||
let DistributeCollation { candidate, pov_block } =
|
||||
distribute_collation(&mut virtual_overseer, &test_state, true).await;
|
||||
distribute_collation(virtual_overseer, &test_state, true).await;
|
||||
|
||||
for (val, peer) in test_state
|
||||
.current_group_validator_authority_ids()
|
||||
.into_iter()
|
||||
.zip(test_state.current_group_validator_peer_ids())
|
||||
{
|
||||
connect_peer(&mut virtual_overseer, peer.clone(), Some(val.clone())).await;
|
||||
connect_peer(virtual_overseer, peer.clone(), Some(val.clone())).await;
|
||||
}
|
||||
|
||||
// We declare to the connected validators that we are a collator.
|
||||
// We need to catch all `Declare` messages to the validators we've
|
||||
// previosly connected to.
|
||||
for peer_id in test_state.current_group_validator_peer_ids() {
|
||||
expect_declare_msg(&mut virtual_overseer, &test_state, &peer_id).await;
|
||||
expect_declare_msg(virtual_overseer, &test_state, &peer_id).await;
|
||||
}
|
||||
|
||||
let validator_0 = test_state.current_group_validator_peer_ids()[0].clone();
|
||||
let validator_1 = test_state.current_group_validator_peer_ids()[1].clone();
|
||||
|
||||
// Send info about peer's view.
|
||||
send_peer_view_change(&mut virtual_overseer, &validator_0, vec![test_state.relay_parent])
|
||||
.await;
|
||||
send_peer_view_change(&mut virtual_overseer, &validator_1, vec![test_state.relay_parent])
|
||||
.await;
|
||||
send_peer_view_change(virtual_overseer, &validator_0, vec![test_state.relay_parent]).await;
|
||||
send_peer_view_change(virtual_overseer, &validator_1, vec![test_state.relay_parent]).await;
|
||||
|
||||
// The peer is interested in a leaf that we have a collation for;
|
||||
// advertise it.
|
||||
expect_advertise_collation_msg(
|
||||
&mut virtual_overseer,
|
||||
&validator_0,
|
||||
test_state.relay_parent,
|
||||
)
|
||||
.await;
|
||||
expect_advertise_collation_msg(
|
||||
&mut virtual_overseer,
|
||||
&validator_1,
|
||||
test_state.relay_parent,
|
||||
)
|
||||
.await;
|
||||
expect_advertise_collation_msg(virtual_overseer, &validator_0, test_state.relay_parent)
|
||||
.await;
|
||||
expect_advertise_collation_msg(virtual_overseer, &validator_1, test_state.relay_parent)
|
||||
.await;
|
||||
|
||||
// Request a collation.
|
||||
let (tx, rx) = oneshot::channel();
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::CollationFetchingRequest(IncomingRequest::new(
|
||||
validator_0,
|
||||
CollationFetchingRequest {
|
||||
let (pending_response, rx) = oneshot::channel();
|
||||
req_cfg
|
||||
.inbound_queue
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send(RawIncomingRequest {
|
||||
peer: validator_0,
|
||||
payload: CollationFetchingRequest {
|
||||
relay_parent: test_state.relay_parent,
|
||||
para_id: test_state.para_id,
|
||||
},
|
||||
tx,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
.encode(),
|
||||
pending_response,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Keep the feedback channel alive because we need to use it to inform about the finished transfer.
|
||||
let feedback_tx = assert_matches!(
|
||||
@@ -942,19 +952,22 @@ where
|
||||
);
|
||||
|
||||
// Let the second validator request the collation.
|
||||
let (tx, rx) = oneshot::channel();
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::CollationFetchingRequest(IncomingRequest::new(
|
||||
validator_1,
|
||||
CollationFetchingRequest {
|
||||
let (pending_response, rx) = oneshot::channel();
|
||||
req_cfg
|
||||
.inbound_queue
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send(RawIncomingRequest {
|
||||
peer: validator_1,
|
||||
payload: CollationFetchingRequest {
|
||||
relay_parent: test_state.relay_parent,
|
||||
para_id: test_state.para_id,
|
||||
},
|
||||
tx,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
.encode(),
|
||||
pending_response,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let rx = handle_first_response(rx, feedback_tx).await;
|
||||
|
||||
@@ -975,6 +988,6 @@ where
|
||||
}
|
||||
);
|
||||
|
||||
virtual_overseer
|
||||
test_harness
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,40 +17,45 @@
|
||||
|
||||
//! Error handling related code and Error/Result definitions.
|
||||
|
||||
use polkadot_node_primitives::UncheckedSignedFullStatement;
|
||||
use polkadot_subsystem::errors::SubsystemError;
|
||||
use thiserror::Error;
|
||||
|
||||
use polkadot_node_subsystem_util::{runtime, unwrap_non_fatal, Fault};
|
||||
use polkadot_node_network_protocol::request_response::incoming;
|
||||
use polkadot_node_primitives::UncheckedSignedFullStatement;
|
||||
use polkadot_node_subsystem_util::runtime;
|
||||
use polkadot_subsystem::errors::SubsystemError;
|
||||
|
||||
use crate::LOG_TARGET;
|
||||
|
||||
/// General result.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Result for fatal only failures.
|
||||
/// Result with only fatal errors.
|
||||
pub type FatalResult<T> = std::result::Result<T, Fatal>;
|
||||
|
||||
/// Errors for statement distribution.
|
||||
#[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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,18 +69,26 @@ pub enum Fatal {
|
||||
/// 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),
|
||||
}
|
||||
|
||||
/// Errors for fetching of runtime information.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum NonFatal {
|
||||
/// Signature was invalid on received statement.
|
||||
#[error("CollationSeconded contained statement with invalid signature.")]
|
||||
#[error("CollationSeconded contained statement with invalid signature")]
|
||||
InvalidStatementSignature(UncheckedSignedFullStatement),
|
||||
|
||||
/// 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),
|
||||
}
|
||||
|
||||
/// Utility for eating top level errors and log them.
|
||||
@@ -83,8 +96,12 @@ pub enum NonFatal {
|
||||
/// 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) -> FatalResult<()> {
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -26,7 +26,10 @@ use futures::{FutureExt, TryFutureExt};
|
||||
|
||||
use sp_keystore::SyncCryptoStorePtr;
|
||||
|
||||
use polkadot_node_network_protocol::{PeerId, UnifiedReputationChange as Rep};
|
||||
use polkadot_node_network_protocol::{
|
||||
request_response::{v1 as request_v1, IncomingRequestReceiver},
|
||||
PeerId, UnifiedReputationChange as Rep,
|
||||
};
|
||||
use polkadot_primitives::v1::CollatorPair;
|
||||
|
||||
use polkadot_subsystem::{
|
||||
@@ -36,7 +39,7 @@ use polkadot_subsystem::{
|
||||
};
|
||||
|
||||
mod error;
|
||||
use error::Result;
|
||||
use error::{FatalResult, Result};
|
||||
|
||||
mod collator_side;
|
||||
mod validator_side;
|
||||
@@ -73,7 +76,12 @@ pub enum ProtocolSide {
|
||||
metrics: validator_side::Metrics,
|
||||
},
|
||||
/// Collators operate on a parachain.
|
||||
Collator(PeerId, CollatorPair, collator_side::Metrics),
|
||||
Collator(
|
||||
PeerId,
|
||||
CollatorPair,
|
||||
IncomingRequestReceiver<request_v1::CollationFetchingRequest>,
|
||||
collator_side::Metrics,
|
||||
),
|
||||
}
|
||||
|
||||
/// The collator protocol subsystem.
|
||||
@@ -90,7 +98,7 @@ impl CollatorProtocolSubsystem {
|
||||
Self { protocol_side }
|
||||
}
|
||||
|
||||
async fn run<Context>(self, ctx: Context) -> Result<()>
|
||||
async fn run<Context>(self, ctx: Context) -> FatalResult<()>
|
||||
where
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
@@ -98,8 +106,8 @@ impl CollatorProtocolSubsystem {
|
||||
match self.protocol_side {
|
||||
ProtocolSide::Validator { keystore, eviction_policy, metrics } =>
|
||||
validator_side::run(ctx, keystore, eviction_policy, metrics).await,
|
||||
ProtocolSide::Collator(local_peer_id, collator_pair, metrics) =>
|
||||
collator_side::run(ctx, local_peer_id, collator_pair, metrics).await,
|
||||
ProtocolSide::Collator(local_peer_id, collator_pair, req_receiver, metrics) =>
|
||||
collator_side::run(ctx, local_peer_id, collator_pair, req_receiver, metrics).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ use polkadot_node_network_protocol::{
|
||||
peer_set::PeerSet,
|
||||
request_response as req_res,
|
||||
request_response::{
|
||||
request::{Recipient, RequestError},
|
||||
outgoing::{Recipient, RequestError},
|
||||
v1::{CollationFetchingRequest, CollationFetchingResponse},
|
||||
OutgoingRequest, Requests,
|
||||
},
|
||||
@@ -54,6 +54,8 @@ use polkadot_subsystem::{
|
||||
overseer, FromOverseer, OverseerSignal, PerLeafSpan, SubsystemContext, SubsystemSender,
|
||||
};
|
||||
|
||||
use crate::error::FatalResult;
|
||||
|
||||
use super::{modify_reputation, Result, LOG_TARGET};
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1079,12 +1081,6 @@ async fn process_msg<Context>(
|
||||
);
|
||||
}
|
||||
},
|
||||
CollationFetchingRequest(_) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"CollationFetchingRequest message is not expected on the validator side of the protocol",
|
||||
);
|
||||
},
|
||||
Seconded(parent, stmt) => {
|
||||
if let Some(collation_event) = state.pending_candidates.remove(&parent) {
|
||||
let (collator_id, pending_collation) = collation_event;
|
||||
@@ -1146,7 +1142,7 @@ pub(crate) async fn run<Context>(
|
||||
keystore: SyncCryptoStorePtr,
|
||||
eviction_policy: crate::CollatorEvictionPolicy,
|
||||
metrics: Metrics,
|
||||
) -> Result<()>
|
||||
) -> FatalResult<()>
|
||||
where
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
|
||||
@@ -7,6 +7,7 @@ edition = "2018"
|
||||
[dependencies]
|
||||
futures = "0.3.15"
|
||||
tracing = "0.1.26"
|
||||
derive_more = "0.99.14"
|
||||
parity-scale-codec = { version = "2.0.0", features = ["std"] }
|
||||
polkadot-primitives = { path = "../../../primitives" }
|
||||
polkadot-erasure-coding = { path = "../../../erasure-coding" }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -14,5 +14,6 @@ parity-scale-codec = { version = "2.0.0", default-features = false, features = [
|
||||
sc-network = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sc-authority-discovery = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
strum = { version = "0.20", features = ["derive"] }
|
||||
derive_more = "0.99.11"
|
||||
futures = "0.3.15"
|
||||
thiserror = "1.0.26"
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Error handling related code and Error/Result definitions.
|
||||
|
||||
use sc_network::PeerId;
|
||||
use thiserror::Error;
|
||||
|
||||
use parity_scale_codec::Error as DecodingError;
|
||||
|
||||
/// Errors that happen during reception/decoding of incoming requests.
|
||||
#[derive(Debug, Error, derive_more::From)]
|
||||
#[error(transparent)]
|
||||
pub enum Error {
|
||||
/// All fatal errors.
|
||||
Fatal(Fatal),
|
||||
/// All nonfatal/potentially recoverable errors.
|
||||
NonFatal(NonFatal),
|
||||
}
|
||||
|
||||
/// Fatal errors when receiving incoming requests.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Fatal {
|
||||
/// Incoming request stream exhausted. Should only happen on shutdown.
|
||||
#[error("Incoming request channel got closed.")]
|
||||
RequestChannelExhausted,
|
||||
}
|
||||
|
||||
/// Non-fatal errors when receiving incoming requests.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum NonFatal {
|
||||
/// Decoding failed, we were able to change the peer's reputation accordingly.
|
||||
#[error("Decoding request failed for peer {0}.")]
|
||||
DecodingError(PeerId, #[source] DecodingError),
|
||||
|
||||
/// Decoding failed, but sending reputation change failed.
|
||||
#[error("Decoding request failed for peer {0}, and changing reputation failed.")]
|
||||
DecodingErrorNoReputationChange(PeerId, #[source] DecodingError),
|
||||
}
|
||||
|
||||
/// General result based on above `Error`.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -0,0 +1,232 @@
|
||||
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
StreamExt,
|
||||
};
|
||||
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
|
||||
use sc_network::{config as netconfig, config::RequestResponseConfig, PeerId};
|
||||
|
||||
use super::IsRequest;
|
||||
use crate::UnifiedReputationChange;
|
||||
|
||||
mod error;
|
||||
pub use error::{Error, Fatal, NonFatal, Result};
|
||||
|
||||
/// A request coming in, including a sender for sending responses.
|
||||
///
|
||||
/// Typed `IncomingRequest`s, see `IncomingRequest::get_config_receiver` and substrate
|
||||
/// `NetworkConfiguration` for more information.
|
||||
#[derive(Debug)]
|
||||
pub struct IncomingRequest<Req> {
|
||||
/// `PeerId` of sending peer.
|
||||
pub peer: PeerId,
|
||||
/// The sent request.
|
||||
pub payload: Req,
|
||||
/// Sender for sending response back.
|
||||
pub pending_response: OutgoingResponseSender<Req>,
|
||||
}
|
||||
|
||||
impl<Req> IncomingRequest<Req>
|
||||
where
|
||||
Req: IsRequest + Decode + Encode,
|
||||
Req::Response: Encode,
|
||||
{
|
||||
/// Create configuration for `NetworkConfiguration::request_response_porotocols` and a
|
||||
/// corresponding typed receiver.
|
||||
///
|
||||
/// This Register that config with substrate networking and receive incoming requests via the
|
||||
/// returned `IncomingRequestReceiver`.
|
||||
pub fn get_config_receiver() -> (IncomingRequestReceiver<Req>, RequestResponseConfig) {
|
||||
let (raw, cfg) = Req::PROTOCOL.get_config();
|
||||
(IncomingRequestReceiver { raw, phantom: PhantomData {} }, cfg)
|
||||
}
|
||||
|
||||
/// Create new `IncomingRequest`.
|
||||
pub fn new(
|
||||
peer: PeerId,
|
||||
payload: Req,
|
||||
pending_response: oneshot::Sender<netconfig::OutgoingResponse>,
|
||||
) -> Self {
|
||||
Self {
|
||||
peer,
|
||||
payload,
|
||||
pending_response: OutgoingResponseSender { pending_response, phantom: PhantomData {} },
|
||||
}
|
||||
}
|
||||
|
||||
/// Try building from raw substrate request.
|
||||
///
|
||||
/// This function will fail if the request cannot be decoded and will apply passed in
|
||||
/// reputation changes in that case.
|
||||
///
|
||||
/// Params:
|
||||
/// - The raw request to decode
|
||||
/// - Reputation changes to apply for the peer in case decoding fails.
|
||||
fn try_from_raw(
|
||||
raw: sc_network::config::IncomingRequest,
|
||||
reputation_changes: Vec<UnifiedReputationChange>,
|
||||
) -> std::result::Result<Self, NonFatal> {
|
||||
let sc_network::config::IncomingRequest { payload, peer, pending_response } = raw;
|
||||
let payload = match Req::decode(&mut payload.as_ref()) {
|
||||
Ok(payload) => payload,
|
||||
Err(err) => {
|
||||
let reputation_changes =
|
||||
reputation_changes.into_iter().map(|r| r.into_base_rep()).collect();
|
||||
let response = sc_network::config::OutgoingResponse {
|
||||
result: Err(()),
|
||||
reputation_changes,
|
||||
sent_feedback: None,
|
||||
};
|
||||
|
||||
if let Err(_) = pending_response.send(response) {
|
||||
return Err(NonFatal::DecodingErrorNoReputationChange(peer, err))
|
||||
}
|
||||
return Err(NonFatal::DecodingError(peer, err))
|
||||
},
|
||||
};
|
||||
Ok(Self::new(peer, payload, pending_response))
|
||||
}
|
||||
|
||||
/// Convert into raw untyped substrate `IncomingRequest`.
|
||||
///
|
||||
/// This is mostly useful for testing.
|
||||
pub fn into_raw(self) -> sc_network::config::IncomingRequest {
|
||||
sc_network::config::IncomingRequest {
|
||||
peer: self.peer,
|
||||
payload: self.payload.encode(),
|
||||
pending_response: self.pending_response.pending_response,
|
||||
}
|
||||
}
|
||||
|
||||
/// Send the response back.
|
||||
///
|
||||
/// Calls [`OutgoingResponseSender::send_response`].
|
||||
pub fn send_response(self, resp: Req::Response) -> std::result::Result<(), Req::Response> {
|
||||
self.pending_response.send_response(resp)
|
||||
}
|
||||
|
||||
/// Send response with additional options.
|
||||
///
|
||||
/// Calls [`OutgoingResponseSender::send_outgoing_response`].
|
||||
pub fn send_outgoing_response(
|
||||
self,
|
||||
resp: OutgoingResponse<<Req as IsRequest>::Response>,
|
||||
) -> std::result::Result<(), ()> {
|
||||
self.pending_response.send_outgoing_response(resp)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sender for sending back responses on an `IncomingRequest`.
|
||||
#[derive(Debug)]
|
||||
pub struct OutgoingResponseSender<Req> {
|
||||
pending_response: oneshot::Sender<netconfig::OutgoingResponse>,
|
||||
phantom: PhantomData<Req>,
|
||||
}
|
||||
|
||||
impl<Req> OutgoingResponseSender<Req>
|
||||
where
|
||||
Req: IsRequest + Decode,
|
||||
Req::Response: Encode,
|
||||
{
|
||||
/// Send the response back.
|
||||
///
|
||||
/// On success we return `Ok(())`, on error we return the not sent `Response`.
|
||||
///
|
||||
/// `netconfig::OutgoingResponse` exposes a way of modifying the peer's reputation. If needed we
|
||||
/// can change this function to expose this feature as well.
|
||||
pub fn send_response(self, resp: Req::Response) -> std::result::Result<(), Req::Response> {
|
||||
self.pending_response
|
||||
.send(netconfig::OutgoingResponse {
|
||||
result: Ok(resp.encode()),
|
||||
reputation_changes: Vec::new(),
|
||||
sent_feedback: None,
|
||||
})
|
||||
.map_err(|_| resp)
|
||||
}
|
||||
|
||||
/// Send response with additional options.
|
||||
///
|
||||
/// This variant allows for waiting for the response to be sent out, allows for changing peer's
|
||||
/// reputation and allows for not sending a response at all (for only changing the peer's
|
||||
/// reputation).
|
||||
pub fn send_outgoing_response(
|
||||
self,
|
||||
resp: OutgoingResponse<<Req as IsRequest>::Response>,
|
||||
) -> std::result::Result<(), ()> {
|
||||
let OutgoingResponse { result, reputation_changes, sent_feedback } = resp;
|
||||
|
||||
let response = netconfig::OutgoingResponse {
|
||||
result: result.map(|v| v.encode()),
|
||||
reputation_changes: reputation_changes.into_iter().map(|c| c.into_base_rep()).collect(),
|
||||
sent_feedback,
|
||||
};
|
||||
|
||||
self.pending_response.send(response).map_err(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
/// Typed variant of [`netconfig::OutgoingResponse`].
|
||||
///
|
||||
/// Responses to `IncomingRequest`s.
|
||||
pub struct OutgoingResponse<Response> {
|
||||
/// The payload of the response.
|
||||
///
|
||||
/// `Err(())` if none is available e.g. due to an error while handling the request.
|
||||
pub result: std::result::Result<Response, ()>,
|
||||
|
||||
/// Reputation changes accrued while handling the request. To be applied to the reputation of
|
||||
/// the peer sending the request.
|
||||
pub reputation_changes: Vec<UnifiedReputationChange>,
|
||||
|
||||
/// If provided, the `oneshot::Sender` will be notified when the request has been sent to the
|
||||
/// peer.
|
||||
pub sent_feedback: Option<oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
/// Receiver for incoming requests.
|
||||
///
|
||||
/// Takes care of decoding and handling of invalid encoded requests.
|
||||
pub struct IncomingRequestReceiver<Req> {
|
||||
raw: mpsc::Receiver<netconfig::IncomingRequest>,
|
||||
phantom: PhantomData<Req>,
|
||||
}
|
||||
|
||||
impl<Req> IncomingRequestReceiver<Req>
|
||||
where
|
||||
Req: IsRequest + Decode + Encode,
|
||||
Req::Response: Encode,
|
||||
{
|
||||
/// Try to receive the next incoming request.
|
||||
///
|
||||
/// Any received request will be decoded, on decoding errors the provided reputation changes
|
||||
/// will be applied and an error will be reported.
|
||||
pub async fn recv<F>(&mut self, reputation_changes: F) -> Result<IncomingRequest<Req>>
|
||||
where
|
||||
F: FnOnce() -> Vec<UnifiedReputationChange>,
|
||||
{
|
||||
let req = match self.raw.next().await {
|
||||
None => return Err(Fatal::RequestChannelExhausted.into()),
|
||||
Some(raw) => IncomingRequest::<Req>::try_from_raw(raw, reputation_changes())?,
|
||||
};
|
||||
Ok(req)
|
||||
}
|
||||
}
|
||||
@@ -40,11 +40,14 @@ use strum::EnumIter;
|
||||
|
||||
pub use sc_network::{config as network, config::RequestResponseConfig};
|
||||
|
||||
/// All requests that can be sent to the network bridge.
|
||||
pub mod request;
|
||||
pub use request::{
|
||||
IncomingRequest, OutgoingRequest, OutgoingResult, Recipient, Requests, ResponseSender,
|
||||
};
|
||||
/// Everything related to handling of incoming requests.
|
||||
pub mod incoming;
|
||||
/// Everything related to handling of outgoing requests.
|
||||
pub mod outgoing;
|
||||
|
||||
pub use incoming::{IncomingRequest, IncomingRequestReceiver};
|
||||
|
||||
pub use outgoing::{OutgoingRequest, OutgoingResult, Recipient, Requests, ResponseSender};
|
||||
|
||||
///// Multiplexer for incoming requests.
|
||||
// pub mod multiplexer;
|
||||
@@ -248,3 +251,12 @@ impl Protocol {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Common properties of any `Request`.
|
||||
pub trait IsRequest {
|
||||
/// Each request has a corresponding `Response`.
|
||||
type Response;
|
||||
|
||||
/// What protocol this `Request` implements.
|
||||
const PROTOCOL: Protocol;
|
||||
}
|
||||
|
||||
+40
-213
@@ -14,32 +14,17 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use futures::{channel::oneshot, prelude::Future};
|
||||
use thiserror::Error;
|
||||
|
||||
use parity_scale_codec::{Decode, Encode, Error as DecodingError};
|
||||
|
||||
use sc_network as network;
|
||||
use sc_network::{config as netconfig, PeerId};
|
||||
use thiserror::Error;
|
||||
use sc_network::PeerId;
|
||||
|
||||
use polkadot_primitives::v1::AuthorityDiscoveryId;
|
||||
|
||||
use crate::UnifiedReputationChange;
|
||||
|
||||
use super::{v1, Protocol};
|
||||
|
||||
/// Used by the network to send us a response to a request.
|
||||
pub type ResponseSender = oneshot::Sender<Result<Vec<u8>, network::RequestFailure>>;
|
||||
|
||||
/// Common properties of any `Request`.
|
||||
pub trait IsRequest {
|
||||
/// Each request has a corresponding `Response`.
|
||||
type Response;
|
||||
|
||||
/// What protocol this `Request` implements.
|
||||
const PROTOCOL: Protocol;
|
||||
}
|
||||
use super::{v1, IsRequest, Protocol};
|
||||
|
||||
/// All requests that can be sent to the network bridge via `NetworkBridgeMessage::SendRequest`.
|
||||
#[derive(Debug)]
|
||||
@@ -90,13 +75,23 @@ impl Requests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Potential recipients of an outgoing request.
|
||||
#[derive(Debug, Eq, Hash, PartialEq, Clone)]
|
||||
pub enum Recipient {
|
||||
/// Recipient is a regular peer and we know its peer id.
|
||||
Peer(PeerId),
|
||||
/// Recipient is a validator, we address it via this `AuthorityDiscoveryId`.
|
||||
Authority(AuthorityDiscoveryId),
|
||||
/// Used by the network to send us a response to a request.
|
||||
pub type ResponseSender = oneshot::Sender<Result<Vec<u8>, network::RequestFailure>>;
|
||||
|
||||
/// Any error that can occur when sending a request.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RequestError {
|
||||
/// Response could not be decoded.
|
||||
#[error("Response could not be decoded")]
|
||||
InvalidResponse(#[source] DecodingError),
|
||||
|
||||
/// Some error in substrate/libp2p happened.
|
||||
#[error("Some network error occurred")]
|
||||
NetworkError(#[source] network::RequestFailure),
|
||||
|
||||
/// Response got canceled by networking.
|
||||
#[error("Response channel got canceled")]
|
||||
Canceled(#[source] oneshot::Canceled),
|
||||
}
|
||||
|
||||
/// A request to be sent to the network bridge, including a sender for sending responses/failures.
|
||||
@@ -119,32 +114,13 @@ pub struct OutgoingRequest<Req> {
|
||||
pub pending_response: ResponseSender,
|
||||
}
|
||||
|
||||
/// Any error that can occur when sending a request.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RequestError {
|
||||
/// Response could not be decoded.
|
||||
#[error("Response could not be decoded")]
|
||||
InvalidResponse(#[source] DecodingError),
|
||||
|
||||
/// Some error in substrate/libp2p happened.
|
||||
#[error("Some network error occurred")]
|
||||
NetworkError(#[source] network::RequestFailure),
|
||||
|
||||
/// Response got canceled by networking.
|
||||
#[error("Response channel got canceled")]
|
||||
Canceled(#[source] oneshot::Canceled),
|
||||
}
|
||||
|
||||
/// Things that can go wrong when decoding an incoming request.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ReceiveError {
|
||||
/// Decoding failed, we were able to change the peer's reputation accordingly.
|
||||
#[error("Decoding request failed for peer {0}.")]
|
||||
DecodingError(PeerId, #[source] DecodingError),
|
||||
|
||||
/// Decoding failed, but sending reputation change failed.
|
||||
#[error("Decoding request failed for peer {0}, and changing reputation failed.")]
|
||||
DecodingErrorNoReputationChange(PeerId, #[source] DecodingError),
|
||||
/// Potential recipients of an outgoing request.
|
||||
#[derive(Debug, Eq, Hash, PartialEq, Clone)]
|
||||
pub enum Recipient {
|
||||
/// Recipient is a regular peer and we know its peer id.
|
||||
Peer(PeerId),
|
||||
/// Recipient is a validator, we address it via this `AuthorityDiscoveryId`.
|
||||
Authority(AuthorityDiscoveryId),
|
||||
}
|
||||
|
||||
/// Responses received for an `OutgoingRequest`.
|
||||
@@ -179,6 +155,18 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Future for actually receiving a typed response for an `OutgoingRequest`.
|
||||
async fn receive_response<Req>(
|
||||
rec: oneshot::Receiver<Result<Vec<u8>, network::RequestFailure>>,
|
||||
) -> OutgoingResult<Req::Response>
|
||||
where
|
||||
Req: IsRequest,
|
||||
Req::Response: Decode,
|
||||
{
|
||||
let raw = rec.await??;
|
||||
Ok(Decode::decode(&mut raw.as_ref())?)
|
||||
}
|
||||
|
||||
impl From<DecodingError> for RequestError {
|
||||
fn from(err: DecodingError) -> Self {
|
||||
Self::InvalidResponse(err)
|
||||
@@ -196,164 +184,3 @@ impl From<oneshot::Canceled> for RequestError {
|
||||
Self::Canceled(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// A request coming in, including a sender for sending responses.
|
||||
///
|
||||
/// `IncomingRequest`s are produced by `RequestMultiplexer` on behalf of the network bridge.
|
||||
#[derive(Debug)]
|
||||
pub struct IncomingRequest<Req> {
|
||||
/// `PeerId` of sending peer.
|
||||
pub peer: PeerId,
|
||||
/// The sent request.
|
||||
pub payload: Req,
|
||||
/// Sender for sending response back.
|
||||
pub pending_response: OutgoingResponseSender<Req>,
|
||||
}
|
||||
|
||||
/// Sender for sending back responses on an `IncomingRequest`.
|
||||
#[derive(Debug)]
|
||||
pub struct OutgoingResponseSender<Req> {
|
||||
pending_response: oneshot::Sender<netconfig::OutgoingResponse>,
|
||||
phantom: PhantomData<Req>,
|
||||
}
|
||||
|
||||
impl<Req> OutgoingResponseSender<Req>
|
||||
where
|
||||
Req: IsRequest + Decode,
|
||||
Req::Response: Encode,
|
||||
{
|
||||
/// Send the response back.
|
||||
///
|
||||
/// On success we return `Ok(())`, on error we return the not sent `Response`.
|
||||
///
|
||||
/// `netconfig::OutgoingResponse` exposes a way of modifying the peer's reputation. If needed we
|
||||
/// can change this function to expose this feature as well.
|
||||
pub fn send_response(self, resp: Req::Response) -> Result<(), Req::Response> {
|
||||
self.pending_response
|
||||
.send(netconfig::OutgoingResponse {
|
||||
result: Ok(resp.encode()),
|
||||
reputation_changes: Vec::new(),
|
||||
sent_feedback: None,
|
||||
})
|
||||
.map_err(|_| resp)
|
||||
}
|
||||
|
||||
/// Send response with additional options.
|
||||
///
|
||||
/// This variant allows for waiting for the response to be sent out, allows for changing peer's
|
||||
/// reputation and allows for not sending a response at all (for only changing the peer's
|
||||
/// reputation).
|
||||
pub fn send_outgoing_response(
|
||||
self,
|
||||
resp: OutgoingResponse<<Req as IsRequest>::Response>,
|
||||
) -> Result<(), ()> {
|
||||
let OutgoingResponse { result, reputation_changes, sent_feedback } = resp;
|
||||
|
||||
let response = netconfig::OutgoingResponse {
|
||||
result: result.map(|v| v.encode()),
|
||||
reputation_changes: reputation_changes.into_iter().map(|c| c.into_base_rep()).collect(),
|
||||
sent_feedback,
|
||||
};
|
||||
|
||||
self.pending_response.send(response).map_err(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
/// Typed variant of [`netconfig::OutgoingResponse`].
|
||||
///
|
||||
/// Responses to `IncomingRequest`s.
|
||||
pub struct OutgoingResponse<Response> {
|
||||
/// The payload of the response.
|
||||
///
|
||||
/// `Err(())` if none is available e.g. due an error while handling the request.
|
||||
pub result: Result<Response, ()>,
|
||||
|
||||
/// Reputation changes accrued while handling the request. To be applied to the reputation of
|
||||
/// the peer sending the request.
|
||||
pub reputation_changes: Vec<UnifiedReputationChange>,
|
||||
|
||||
/// If provided, the `oneshot::Sender` will be notified when the request has been sent to the
|
||||
/// peer.
|
||||
pub sent_feedback: Option<oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
impl<Req> IncomingRequest<Req>
|
||||
where
|
||||
Req: IsRequest + Decode,
|
||||
Req::Response: Encode,
|
||||
{
|
||||
/// Create new `IncomingRequest`.
|
||||
pub fn new(
|
||||
peer: PeerId,
|
||||
payload: Req,
|
||||
pending_response: oneshot::Sender<netconfig::OutgoingResponse>,
|
||||
) -> Self {
|
||||
Self {
|
||||
peer,
|
||||
payload,
|
||||
pending_response: OutgoingResponseSender { pending_response, phantom: PhantomData {} },
|
||||
}
|
||||
}
|
||||
|
||||
/// Try building from raw substrate request.
|
||||
///
|
||||
/// This function will fail if the request cannot be decoded and will apply passed in
|
||||
/// reputation changes in that case.
|
||||
///
|
||||
/// Params:
|
||||
/// - The raw request to decode
|
||||
/// - Reputation changes to apply for the peer in case decoding fails.
|
||||
pub fn try_from_raw(
|
||||
raw: sc_network::config::IncomingRequest,
|
||||
reputation_changes: Vec<UnifiedReputationChange>,
|
||||
) -> Result<Self, ReceiveError> {
|
||||
let sc_network::config::IncomingRequest { payload, peer, pending_response } = raw;
|
||||
let payload = match Req::decode(&mut payload.as_ref()) {
|
||||
Ok(payload) => payload,
|
||||
Err(err) => {
|
||||
let reputation_changes =
|
||||
reputation_changes.into_iter().map(|r| r.into_base_rep()).collect();
|
||||
let response = sc_network::config::OutgoingResponse {
|
||||
result: Err(()),
|
||||
reputation_changes,
|
||||
sent_feedback: None,
|
||||
};
|
||||
|
||||
if let Err(_) = pending_response.send(response) {
|
||||
return Err(ReceiveError::DecodingErrorNoReputationChange(peer, err))
|
||||
}
|
||||
return Err(ReceiveError::DecodingError(peer, err))
|
||||
},
|
||||
};
|
||||
Ok(Self::new(peer, payload, pending_response))
|
||||
}
|
||||
|
||||
/// Send the response back.
|
||||
///
|
||||
/// Calls [`OutgoingResponseSender::send_response`].
|
||||
pub fn send_response(self, resp: Req::Response) -> Result<(), Req::Response> {
|
||||
self.pending_response.send_response(resp)
|
||||
}
|
||||
|
||||
/// Send response with additional options.
|
||||
///
|
||||
/// Calls [`OutgoingResponseSender::send_outgoing_response`].
|
||||
pub fn send_outgoing_response(
|
||||
self,
|
||||
resp: OutgoingResponse<<Req as IsRequest>::Response>,
|
||||
) -> Result<(), ()> {
|
||||
self.pending_response.send_outgoing_response(resp)
|
||||
}
|
||||
}
|
||||
|
||||
/// Future for actually receiving a typed response for an `OutgoingRequest`.
|
||||
async fn receive_response<Req>(
|
||||
rec: oneshot::Receiver<Result<Vec<u8>, network::RequestFailure>>,
|
||||
) -> OutgoingResult<Req::Response>
|
||||
where
|
||||
Req: IsRequest,
|
||||
Req::Response: Decode,
|
||||
{
|
||||
let raw = rec.await??;
|
||||
Ok(Decode::decode(&mut raw.as_ref())?)
|
||||
}
|
||||
@@ -25,7 +25,7 @@ use polkadot_primitives::v1::{
|
||||
CandidateHash, CandidateReceipt, CommittedCandidateReceipt, Hash, Id as ParaId, ValidatorIndex,
|
||||
};
|
||||
|
||||
use super::{request::IsRequest, Protocol};
|
||||
use super::{IsRequest, Protocol};
|
||||
|
||||
/// Request an availability chunk.
|
||||
#[derive(Debug, Copy, Clone, Encode, Decode)]
|
||||
|
||||
@@ -11,7 +11,6 @@ tracing = "0.1.26"
|
||||
polkadot-primitives = { path = "../../../primitives" }
|
||||
sp-staking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
sp-keystore = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sc-network = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
polkadot-subsystem = { package = "polkadot-node-subsystem", path = "../../subsystem" }
|
||||
polkadot-node-primitives = { path = "../../primitives" }
|
||||
polkadot-node-subsystem-util = { path = "../../subsystem-util" }
|
||||
@@ -19,6 +18,7 @@ polkadot-node-network-protocol = { path = "../../network/protocol" }
|
||||
arrayvec = "0.5.2"
|
||||
indexmap = "1.7.0"
|
||||
parity-scale-codec = { version = "2.0.0", default-features = false, features = ["derive"] }
|
||||
derive_more = "0.99.11"
|
||||
thiserror = "1.0.26"
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -30,4 +30,5 @@ sp-application-crypto = { git = "https://github.com/paritytech/substrate", branc
|
||||
sp-keystore = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-tracing = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sc-keystore = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sc-network = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
futures-timer = "3.0.2"
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
//! Error handling related code and Error/Result definitions.
|
||||
|
||||
use polkadot_node_network_protocol::PeerId;
|
||||
use polkadot_node_subsystem_util::runtime;
|
||||
use polkadot_primitives::v1::{CandidateHash, Hash};
|
||||
use polkadot_subsystem::SubsystemError;
|
||||
use thiserror::Error;
|
||||
|
||||
use polkadot_node_subsystem_util::{runtime, unwrap_non_fatal, Fault};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::LOG_TARGET;
|
||||
|
||||
@@ -34,29 +34,25 @@ pub type NonFatalResult<T> = std::result::Result<T, NonFatal>;
|
||||
pub type FatalResult<T> = std::result::Result<T, Fatal>;
|
||||
|
||||
/// Errors for statement distribution.
|
||||
#[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<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 runtime errors.
|
||||
/// Fatal errors.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Fatal {
|
||||
/// Requester channel is never closed.
|
||||
@@ -112,9 +108,13 @@ pub enum NonFatal {
|
||||
///
|
||||
/// 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) -> FatalResult<()> {
|
||||
if let Some(error) = unwrap_non_fatal(result.map_err(|e| e.0))? {
|
||||
tracing::debug!(target: LOG_TARGET, error = ?error, ctx)
|
||||
pub fn log_error(result: Result<()>, ctx: &'static str) -> std::result::Result<(), Fatal> {
|
||||
match result {
|
||||
Err(Error::Fatal(f)) => Err(f),
|
||||
Err(Error::NonFatal(error)) => {
|
||||
tracing::warn!(target: LOG_TARGET, error = ?error, ctx);
|
||||
Ok(())
|
||||
},
|
||||
Ok(()) => Ok(()),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ use parity_scale_codec::Encode;
|
||||
|
||||
use polkadot_node_network_protocol::{
|
||||
peer_set::{IsAuthority, PeerSet},
|
||||
request_response::{v1 as request_v1, IncomingRequestReceiver},
|
||||
v1::{self as protocol_v1, StatementMetadata},
|
||||
IfDisconnected, PeerId, UnifiedReputationChange as Rep, View,
|
||||
};
|
||||
@@ -57,7 +58,7 @@ use futures::{
|
||||
};
|
||||
use indexmap::{map::Entry as IEntry, IndexMap};
|
||||
use sp_keystore::SyncCryptoStorePtr;
|
||||
use util::{runtime::RuntimeInfo, Fault};
|
||||
use util::runtime::RuntimeInfo;
|
||||
|
||||
use std::collections::{hash_map::Entry, HashMap, HashSet};
|
||||
|
||||
@@ -106,6 +107,8 @@ const MAX_LARGE_STATEMENTS_PER_SENDER: usize = 20;
|
||||
pub struct StatementDistribution {
|
||||
/// Pointer to a keystore, which is required for determining this nodes validator index.
|
||||
keystore: SyncCryptoStorePtr,
|
||||
/// Receiver for incoming large statement requests.
|
||||
req_receiver: Option<IncomingRequestReceiver<request_v1::StatementFetchingRequest>>,
|
||||
// Prometheus metrics
|
||||
metrics: Metrics,
|
||||
}
|
||||
@@ -130,8 +133,12 @@ where
|
||||
|
||||
impl StatementDistribution {
|
||||
/// Create a new Statement Distribution Subsystem
|
||||
pub fn new(keystore: SyncCryptoStorePtr, metrics: Metrics) -> StatementDistribution {
|
||||
StatementDistribution { keystore, metrics }
|
||||
pub fn new(
|
||||
keystore: SyncCryptoStorePtr,
|
||||
req_receiver: IncomingRequestReceiver<request_v1::StatementFetchingRequest>,
|
||||
metrics: Metrics,
|
||||
) -> StatementDistribution {
|
||||
StatementDistribution { keystore, req_receiver: Some(req_receiver), metrics }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1526,7 +1533,7 @@ async fn handle_network_update(
|
||||
|
||||
impl StatementDistribution {
|
||||
async fn run(
|
||||
self,
|
||||
mut self,
|
||||
mut ctx: (impl SubsystemContext<Message = StatementDistributionMessage>
|
||||
+ overseer::SubsystemContext<Message = StatementDistributionMessage>),
|
||||
) -> std::result::Result<(), Fatal> {
|
||||
@@ -1542,6 +1549,16 @@ impl StatementDistribution {
|
||||
// Sender/Receiver for getting news from our responder task.
|
||||
let (res_sender, mut res_receiver) = mpsc::channel(1);
|
||||
|
||||
ctx.spawn(
|
||||
"large-statement-responder",
|
||||
respond(
|
||||
self.req_receiver.take().expect("Mandatory argument to new. qed"),
|
||||
res_sender.clone(),
|
||||
)
|
||||
.boxed(),
|
||||
)
|
||||
.map_err(Fatal::SpawnTask)?;
|
||||
|
||||
loop {
|
||||
let message =
|
||||
MuxedMessage::receive(&mut ctx, &mut req_receiver, &mut res_receiver).await;
|
||||
@@ -1556,16 +1573,14 @@ impl StatementDistribution {
|
||||
&mut authorities,
|
||||
&mut active_heads,
|
||||
&req_sender,
|
||||
&res_sender,
|
||||
result?,
|
||||
)
|
||||
.await;
|
||||
match result {
|
||||
Ok(true) => break,
|
||||
Ok(false) => {},
|
||||
Err(Error(Fault::Fatal(f))) => return Err(f),
|
||||
Err(Error(Fault::Err(error))) =>
|
||||
tracing::debug!(target: LOG_TARGET, ?error),
|
||||
Err(Error::Fatal(f)) => return Err(f),
|
||||
Err(Error::NonFatal(error)) => tracing::debug!(target: LOG_TARGET, ?error),
|
||||
}
|
||||
},
|
||||
MuxedMessage::Requester(result) => {
|
||||
@@ -1749,7 +1764,6 @@ impl StatementDistribution {
|
||||
authorities: &mut HashMap<AuthorityDiscoveryId, PeerId>,
|
||||
active_heads: &mut HashMap<Hash, ActiveHeadData>,
|
||||
req_sender: &mpsc::Sender<RequesterMessage>,
|
||||
res_sender: &mpsc::Sender<ResponderMessage>,
|
||||
message: FromOverseer<StatementDistributionMessage>,
|
||||
) -> Result<bool> {
|
||||
let metrics = &self.metrics;
|
||||
@@ -1868,13 +1882,6 @@ impl StatementDistribution {
|
||||
)
|
||||
.await;
|
||||
},
|
||||
StatementDistributionMessage::StatementFetchingReceiver(receiver) => {
|
||||
ctx.spawn(
|
||||
"large-statement-responder",
|
||||
respond(receiver, res_sender.clone()).boxed(),
|
||||
)
|
||||
.map_err(Fatal::SpawnTask)?;
|
||||
},
|
||||
},
|
||||
}
|
||||
Ok(false)
|
||||
|
||||
@@ -22,9 +22,9 @@ use futures::{
|
||||
|
||||
use polkadot_node_network_protocol::{
|
||||
request_response::{
|
||||
request::OutgoingResponse,
|
||||
incoming::{self, OutgoingResponse},
|
||||
v1::{StatementFetchingRequest, StatementFetchingResponse},
|
||||
IncomingRequest, MAX_PARALLEL_STATEMENT_REQUESTS,
|
||||
IncomingRequestReceiver, MAX_PARALLEL_STATEMENT_REQUESTS,
|
||||
},
|
||||
PeerId, UnifiedReputationChange as Rep,
|
||||
};
|
||||
@@ -51,7 +51,7 @@ pub enum ResponderMessage {
|
||||
/// `CommittedCandidateReceipt` from peers, whether this can be used to re-assemble one ore
|
||||
/// many `SignedFullStatement`s needs to be verified by the caller.
|
||||
pub async fn respond(
|
||||
mut receiver: mpsc::Receiver<sc_network::config::IncomingRequest>,
|
||||
mut receiver: IncomingRequestReceiver<StatementFetchingRequest>,
|
||||
mut sender: mpsc::Sender<ResponderMessage>,
|
||||
) {
|
||||
let mut pending_out = FuturesUnordered::new();
|
||||
@@ -74,23 +74,16 @@ pub async fn respond(
|
||||
pending_out.next().await;
|
||||
}
|
||||
|
||||
let raw = match receiver.next().await {
|
||||
None => {
|
||||
tracing::debug!(target: LOG_TARGET, "Shutting down request responder");
|
||||
let req = match receiver.recv(|| vec![COST_INVALID_REQUEST]).await {
|
||||
Err(incoming::Error::Fatal(f)) => {
|
||||
tracing::debug!(target: LOG_TARGET, error = ?f, "Shutting down request responder");
|
||||
return
|
||||
},
|
||||
Some(v) => v,
|
||||
};
|
||||
|
||||
let req = match IncomingRequest::<StatementFetchingRequest>::try_from_raw(
|
||||
raw,
|
||||
vec![COST_INVALID_REQUEST],
|
||||
) {
|
||||
Err(err) => {
|
||||
Err(incoming::Error::NonFatal(err)) => {
|
||||
tracing::debug!(target: LOG_TARGET, ?err, "Decoding request failed");
|
||||
continue
|
||||
},
|
||||
Ok(payload) => payload,
|
||||
Ok(v) => v,
|
||||
};
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
@@ -22,7 +22,7 @@ use parity_scale_codec::{Decode, Encode};
|
||||
use polkadot_node_network_protocol::{
|
||||
request_response::{
|
||||
v1::{StatementFetchingRequest, StatementFetchingResponse},
|
||||
Recipient, Requests,
|
||||
IncomingRequest, Recipient, Requests,
|
||||
},
|
||||
view, ObservedRole,
|
||||
};
|
||||
@@ -699,11 +699,14 @@ fn receiving_from_one_sends_to_another_and_to_candidate_backing() {
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (ctx, mut handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool);
|
||||
|
||||
let (statement_req_receiver, _) = IncomingRequest::get_config_receiver();
|
||||
|
||||
let bg = async move {
|
||||
let s = StatementDistribution {
|
||||
metrics: Default::default(),
|
||||
keystore: Arc::new(LocalKeystore::in_memory()),
|
||||
};
|
||||
let s = StatementDistribution::new(
|
||||
Arc::new(LocalKeystore::in_memory()),
|
||||
statement_req_receiver,
|
||||
Default::default(),
|
||||
);
|
||||
s.run(ctx).await.unwrap();
|
||||
};
|
||||
|
||||
@@ -888,21 +891,18 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing(
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (ctx, mut handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool);
|
||||
|
||||
let (statement_req_receiver, mut req_cfg) = IncomingRequest::get_config_receiver();
|
||||
|
||||
let bg = async move {
|
||||
let s =
|
||||
StatementDistribution { metrics: Default::default(), keystore: make_ferdie_keystore() };
|
||||
let s = StatementDistribution::new(
|
||||
make_ferdie_keystore(),
|
||||
statement_req_receiver,
|
||||
Default::default(),
|
||||
);
|
||||
s.run(ctx).await.unwrap();
|
||||
};
|
||||
|
||||
let (mut tx_reqs, rx_reqs) = mpsc::channel(1);
|
||||
|
||||
let test_fut = async move {
|
||||
handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: StatementDistributionMessage::StatementFetchingReceiver(rx_reqs),
|
||||
})
|
||||
.await;
|
||||
|
||||
// register our active heads.
|
||||
handle
|
||||
.send(FromOverseer::Signal(OverseerSignal::ActiveLeaves(
|
||||
@@ -1290,7 +1290,7 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing(
|
||||
payload: inner_req.encode(),
|
||||
pending_response,
|
||||
};
|
||||
tx_reqs.send(req).await.unwrap();
|
||||
req_cfg.inbound_queue.as_mut().unwrap().send(req).await.unwrap();
|
||||
assert_matches!(
|
||||
response_rx.await.unwrap().result,
|
||||
Err(()) => {}
|
||||
@@ -1308,7 +1308,7 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing(
|
||||
payload: inner_req.encode(),
|
||||
pending_response,
|
||||
};
|
||||
tx_reqs.send(req).await.unwrap();
|
||||
req_cfg.inbound_queue.as_mut().unwrap().send(req).await.unwrap();
|
||||
assert_matches!(
|
||||
response_rx.await.unwrap().result,
|
||||
Err(()) => {}
|
||||
@@ -1325,7 +1325,7 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing(
|
||||
payload: inner_req.encode(),
|
||||
pending_response,
|
||||
};
|
||||
tx_reqs.send(req).await.unwrap();
|
||||
req_cfg.inbound_queue.as_mut().unwrap().send(req).await.unwrap();
|
||||
let StatementFetchingResponse::Statement(committed) =
|
||||
Decode::decode(&mut response_rx.await.unwrap().result.unwrap().as_ref()).unwrap();
|
||||
assert_eq!(committed, candidate);
|
||||
@@ -1390,21 +1390,18 @@ fn share_prioritizes_backing_group() {
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (ctx, mut handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool);
|
||||
|
||||
let (statement_req_receiver, mut req_cfg) = IncomingRequest::get_config_receiver();
|
||||
|
||||
let bg = async move {
|
||||
let s =
|
||||
StatementDistribution { metrics: Default::default(), keystore: make_ferdie_keystore() };
|
||||
let s = StatementDistribution::new(
|
||||
make_ferdie_keystore(),
|
||||
statement_req_receiver,
|
||||
Default::default(),
|
||||
);
|
||||
s.run(ctx).await.unwrap();
|
||||
};
|
||||
|
||||
let (mut tx_reqs, rx_reqs) = mpsc::channel(1);
|
||||
|
||||
let test_fut = async move {
|
||||
handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: StatementDistributionMessage::StatementFetchingReceiver(rx_reqs),
|
||||
})
|
||||
.await;
|
||||
|
||||
// register our active heads.
|
||||
handle
|
||||
.send(FromOverseer::Signal(OverseerSignal::ActiveLeaves(
|
||||
@@ -1632,7 +1629,7 @@ fn share_prioritizes_backing_group() {
|
||||
payload: inner_req.encode(),
|
||||
pending_response,
|
||||
};
|
||||
tx_reqs.send(req).await.unwrap();
|
||||
req_cfg.inbound_queue.as_mut().unwrap().send(req).await.unwrap();
|
||||
let StatementFetchingResponse::Statement(committed) =
|
||||
Decode::decode(&mut response_rx.await.unwrap().result.unwrap().as_ref()).unwrap();
|
||||
assert_eq!(committed, candidate);
|
||||
@@ -1679,21 +1676,17 @@ fn peer_cant_flood_with_large_statements() {
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (ctx, mut handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool);
|
||||
|
||||
let (statement_req_receiver, _) = IncomingRequest::get_config_receiver();
|
||||
let bg = async move {
|
||||
let s =
|
||||
StatementDistribution { metrics: Default::default(), keystore: make_ferdie_keystore() };
|
||||
let s = StatementDistribution::new(
|
||||
make_ferdie_keystore(),
|
||||
statement_req_receiver,
|
||||
Default::default(),
|
||||
);
|
||||
s.run(ctx).await.unwrap();
|
||||
};
|
||||
|
||||
let (_, rx_reqs) = mpsc::channel(1);
|
||||
|
||||
let test_fut = async move {
|
||||
handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: StatementDistributionMessage::StatementFetchingReceiver(rx_reqs),
|
||||
})
|
||||
.await;
|
||||
|
||||
// register our active heads.
|
||||
handle
|
||||
.send(FromOverseer::Signal(OverseerSignal::ActiveLeaves(
|
||||
|
||||
@@ -106,7 +106,7 @@ fn impl_subsystems_gen(item: TokenStream) -> Result<proc_macro2::TokenStream> {
|
||||
|
||||
if generic_types.contains(&ty_ident) {
|
||||
if let Some(previous) = duplicate_generic_detection.replace(ty_ident) {
|
||||
return Err(Error::new(previous.span(), "Generic type parameters may only be used for exactly one field, but is used more than once."))
|
||||
return Err(Error::new(previous.span(), "Generic type parameters may only be used for exactly one field, but is used more than once."));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -920,32 +920,3 @@ where
|
||||
self.spawner.spawn_blocking(name, j);
|
||||
}
|
||||
}
|
||||
|
||||
// Additional `From` implementations, in order to deal with incoming network messages.
|
||||
// Kept out of the proc macro, for sake of simplicity reduce the need to make even
|
||||
// more types to the proc macro logic.
|
||||
|
||||
use polkadot_node_network_protocol::request_response::{
|
||||
request::IncomingRequest, v1 as req_res_v1,
|
||||
};
|
||||
|
||||
impl From<IncomingRequest<req_res_v1::PoVFetchingRequest>> for AllMessages {
|
||||
fn from(req: IncomingRequest<req_res_v1::PoVFetchingRequest>) -> Self {
|
||||
From::<AvailabilityDistributionMessage>::from(From::from(req))
|
||||
}
|
||||
}
|
||||
impl From<IncomingRequest<req_res_v1::ChunkFetchingRequest>> for AllMessages {
|
||||
fn from(req: IncomingRequest<req_res_v1::ChunkFetchingRequest>) -> Self {
|
||||
From::<AvailabilityDistributionMessage>::from(From::from(req))
|
||||
}
|
||||
}
|
||||
impl From<IncomingRequest<req_res_v1::CollationFetchingRequest>> for AllMessages {
|
||||
fn from(req: IncomingRequest<req_res_v1::CollationFetchingRequest>) -> Self {
|
||||
From::<CollatorProtocolMessage>::from(From::from(req))
|
||||
}
|
||||
}
|
||||
impl From<IncomingRequest<req_res_v1::AvailableDataFetchingRequest>> for AllMessages {
|
||||
fn from(req: IncomingRequest<req_res_v1::AvailableDataFetchingRequest>) -> Self {
|
||||
From::<AvailabilityRecoveryMessage>::from(From::from(req))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,17 +14,23 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use futures::{channel::mpsc, executor, pending, pin_mut, poll, select, stream, FutureExt};
|
||||
use futures::{executor, pending, pin_mut, poll, select, stream, FutureExt};
|
||||
use std::{collections::HashMap, sync::atomic, task::Poll};
|
||||
|
||||
use polkadot_node_network_protocol::{PeerId, UnifiedReputationChange};
|
||||
use polkadot_node_primitives::{BlockData, CollationGenerationConfig, CollationResult, PoV};
|
||||
use polkadot_node_primitives::{
|
||||
BlockData, CollationGenerationConfig, CollationResult, DisputeMessage, InvalidDisputeVote, PoV,
|
||||
UncheckedDisputeMessage, ValidDisputeVote,
|
||||
};
|
||||
use polkadot_node_subsystem_types::{
|
||||
jaeger,
|
||||
messages::{NetworkBridgeEvent, RuntimeApiRequest},
|
||||
ActivatedLeaf, LeafStatus,
|
||||
};
|
||||
use polkadot_primitives::v1::{CandidateHash, CollatorPair};
|
||||
use polkadot_primitives::v1::{
|
||||
CandidateHash, CollatorPair, InvalidDisputeStatementKind, ValidDisputeStatementKind,
|
||||
ValidatorIndex,
|
||||
};
|
||||
|
||||
use crate::{self as overseer, gen::Delay, HeadSupportsParachains, Overseer};
|
||||
use metered_channel as metered;
|
||||
@@ -772,8 +778,27 @@ fn test_dispute_participation_msg() -> DisputeParticipationMessage {
|
||||
}
|
||||
|
||||
fn test_dispute_distribution_msg() -> DisputeDistributionMessage {
|
||||
let (_, receiver) = mpsc::channel(1);
|
||||
DisputeDistributionMessage::DisputeSendingReceiver(receiver)
|
||||
let dummy_dispute_message = UncheckedDisputeMessage {
|
||||
candidate_receipt: Default::default(),
|
||||
session_index: 0,
|
||||
invalid_vote: InvalidDisputeVote {
|
||||
validator_index: ValidatorIndex(0),
|
||||
signature: Default::default(),
|
||||
kind: InvalidDisputeStatementKind::Explicit,
|
||||
},
|
||||
valid_vote: ValidDisputeVote {
|
||||
validator_index: ValidatorIndex(0),
|
||||
signature: Default::default(),
|
||||
kind: ValidDisputeStatementKind::Explicit,
|
||||
},
|
||||
};
|
||||
|
||||
DisputeDistributionMessage::SendDispute(
|
||||
// We just need dummy data here:
|
||||
unsafe {
|
||||
std::mem::transmute::<UncheckedDisputeMessage, DisputeMessage>(dummy_dispute_message)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn test_chain_selection_msg() -> ChainSelectionMessage {
|
||||
|
||||
@@ -80,6 +80,7 @@ polkadot-rpc = { path = "../../rpc" }
|
||||
polkadot-subsystem = { package = "polkadot-node-subsystem", path = "../subsystem" }
|
||||
polkadot-node-subsystem-util = { path = "../subsystem-util" }
|
||||
polkadot-runtime-parachains = { path = "../../runtime/parachains" }
|
||||
polkadot-node-network-protocol = { path = "../network/protocol" }
|
||||
|
||||
# Polkadot Runtimes
|
||||
polkadot-runtime = { path = "../../runtime/polkadot" }
|
||||
|
||||
@@ -37,7 +37,6 @@ mod tests;
|
||||
#[cfg(feature = "full-node")]
|
||||
use {
|
||||
grandpa::{self, FinalityProofProvider as GrandpaFinalityProofProvider},
|
||||
polkadot_network_bridge::RequestMultiplexer,
|
||||
polkadot_node_core_approval_voting::Config as ApprovalVotingConfig,
|
||||
polkadot_node_core_av_store::Config as AvailabilityConfig,
|
||||
polkadot_node_core_av_store::Error as AvailabilityError,
|
||||
@@ -632,6 +631,8 @@ where
|
||||
Executor: NativeExecutionDispatch + 'static,
|
||||
OverseerGenerator: OverseerGen,
|
||||
{
|
||||
use polkadot_node_network_protocol::request_response::IncomingRequest;
|
||||
|
||||
let role = config.role.clone();
|
||||
let force_authoring = config.force_authoring;
|
||||
let backoff_authoring_blocks = {
|
||||
@@ -681,11 +682,18 @@ where
|
||||
config.network.extra_sets.extend(peer_sets_info(is_authority));
|
||||
}
|
||||
|
||||
let request_multiplexer = {
|
||||
let (multiplexer, configs) = RequestMultiplexer::new();
|
||||
config.network.request_response_protocols.extend(configs);
|
||||
multiplexer
|
||||
};
|
||||
let (pov_req_receiver, cfg) = IncomingRequest::get_config_receiver();
|
||||
config.network.request_response_protocols.push(cfg);
|
||||
let (chunk_req_receiver, cfg) = IncomingRequest::get_config_receiver();
|
||||
config.network.request_response_protocols.push(cfg);
|
||||
let (collation_req_receiver, cfg) = IncomingRequest::get_config_receiver();
|
||||
config.network.request_response_protocols.push(cfg);
|
||||
let (available_data_req_receiver, cfg) = IncomingRequest::get_config_receiver();
|
||||
config.network.request_response_protocols.push(cfg);
|
||||
let (statement_req_receiver, cfg) = IncomingRequest::get_config_receiver();
|
||||
config.network.request_response_protocols.push(cfg);
|
||||
let (dispute_req_receiver, cfg) = IncomingRequest::get_config_receiver();
|
||||
config.network.request_response_protocols.push(cfg);
|
||||
|
||||
let warp_sync = Arc::new(grandpa::warp_proof::NetworkProvider::new(
|
||||
backend.clone(),
|
||||
@@ -824,7 +832,12 @@ where
|
||||
parachains_db,
|
||||
network_service: network.clone(),
|
||||
authority_discovery_service,
|
||||
request_multiplexer,
|
||||
pov_req_receiver,
|
||||
chunk_req_receiver,
|
||||
collation_req_receiver,
|
||||
available_data_req_receiver,
|
||||
statement_req_receiver,
|
||||
dispute_req_receiver,
|
||||
registry: prometheus_registry.as_ref(),
|
||||
spawner,
|
||||
is_collator,
|
||||
|
||||
@@ -15,12 +15,13 @@
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::{AuthorityDiscoveryApi, Block, Error, Hash, IsCollator, Registry, SpawnNamed};
|
||||
use polkadot_network_bridge::RequestMultiplexer;
|
||||
use polkadot_availability_distribution::IncomingRequestReceivers;
|
||||
use polkadot_node_core_approval_voting::Config as ApprovalVotingConfig;
|
||||
use polkadot_node_core_av_store::Config as AvailabilityConfig;
|
||||
use polkadot_node_core_candidate_validation::Config as CandidateValidationConfig;
|
||||
use polkadot_node_core_chain_selection::Config as ChainSelectionConfig;
|
||||
use polkadot_node_core_dispute_coordinator::Config as DisputeCoordinatorConfig;
|
||||
use polkadot_node_network_protocol::request_response::{v1 as request_v1, IncomingRequestReceiver};
|
||||
use polkadot_overseer::{AllSubsystems, BlockInfo, Overseer, OverseerHandle};
|
||||
use polkadot_primitives::v1::ParachainHost;
|
||||
use sc_authority_discovery::Service as AuthorityDiscoveryService;
|
||||
@@ -72,8 +73,14 @@ where
|
||||
pub network_service: Arc<sc_network::NetworkService<Block, Hash>>,
|
||||
/// Underlying authority discovery service.
|
||||
pub authority_discovery_service: AuthorityDiscoveryService,
|
||||
/// A multiplexer to arbitrate incoming `IncomingRequest`s from the network.
|
||||
pub request_multiplexer: RequestMultiplexer,
|
||||
/// POV request receiver
|
||||
pub pov_req_receiver: IncomingRequestReceiver<request_v1::PoVFetchingRequest>,
|
||||
pub chunk_req_receiver: IncomingRequestReceiver<request_v1::ChunkFetchingRequest>,
|
||||
pub collation_req_receiver: IncomingRequestReceiver<request_v1::CollationFetchingRequest>,
|
||||
pub available_data_req_receiver:
|
||||
IncomingRequestReceiver<request_v1::AvailableDataFetchingRequest>,
|
||||
pub statement_req_receiver: IncomingRequestReceiver<request_v1::StatementFetchingRequest>,
|
||||
pub dispute_req_receiver: IncomingRequestReceiver<request_v1::DisputeRequest>,
|
||||
/// Prometheus registry, commonly used for production systems, less so for test.
|
||||
pub registry: Option<&'a Registry>,
|
||||
/// Task spawner to be used throughout the overseer and the APIs it provides.
|
||||
@@ -103,7 +110,12 @@ pub fn create_default_subsystems<'a, Spawner, RuntimeClient>(
|
||||
parachains_db,
|
||||
network_service,
|
||||
authority_discovery_service,
|
||||
request_multiplexer,
|
||||
pov_req_receiver,
|
||||
chunk_req_receiver,
|
||||
collation_req_receiver,
|
||||
available_data_req_receiver,
|
||||
statement_req_receiver,
|
||||
dispute_req_receiver,
|
||||
registry,
|
||||
spawner,
|
||||
is_collator,
|
||||
@@ -153,9 +165,12 @@ where
|
||||
let all_subsystems = AllSubsystems {
|
||||
availability_distribution: AvailabilityDistributionSubsystem::new(
|
||||
keystore.clone(),
|
||||
IncomingRequestReceivers { pov_req_receiver, chunk_req_receiver },
|
||||
Metrics::register(registry)?,
|
||||
),
|
||||
availability_recovery: AvailabilityRecoverySubsystem::with_chunks_only(),
|
||||
availability_recovery: AvailabilityRecoverySubsystem::with_chunks_only(
|
||||
available_data_req_receiver,
|
||||
),
|
||||
availability_store: AvailabilityStoreSubsystem::new(
|
||||
parachains_db.clone(),
|
||||
availability_config,
|
||||
@@ -183,6 +198,7 @@ where
|
||||
IsCollator::Yes(collator_pair) => ProtocolSide::Collator(
|
||||
network_service.local_peer_id().clone(),
|
||||
collator_pair,
|
||||
collation_req_receiver,
|
||||
Metrics::register(registry)?,
|
||||
),
|
||||
IsCollator::No => ProtocolSide::Validator {
|
||||
@@ -196,7 +212,6 @@ where
|
||||
network_bridge: NetworkBridgeSubsystem::new(
|
||||
network_service.clone(),
|
||||
authority_discovery_service.clone(),
|
||||
request_multiplexer,
|
||||
Box::new(network_service.clone()),
|
||||
Metrics::register(registry)?,
|
||||
),
|
||||
@@ -208,6 +223,7 @@ where
|
||||
),
|
||||
statement_distribution: StatementDistributionSubsystem::new(
|
||||
keystore.clone(),
|
||||
statement_req_receiver,
|
||||
Metrics::register(registry)?,
|
||||
),
|
||||
approval_distribution: ApprovalDistributionSubsystem::new(Metrics::register(registry)?),
|
||||
@@ -227,6 +243,7 @@ where
|
||||
dispute_participation: DisputeParticipationSubsystem::new(),
|
||||
dispute_distribution: DisputeDistributionSubsystem::new(
|
||||
keystore.clone(),
|
||||
dispute_req_receiver,
|
||||
authority_discovery_service.clone(),
|
||||
Metrics::register(registry)?,
|
||||
),
|
||||
|
||||
@@ -22,15 +22,14 @@
|
||||
//!
|
||||
//! Subsystems' APIs are defined separately from their implementation, leading to easier mocking.
|
||||
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use futures::channel::oneshot;
|
||||
use thiserror::Error;
|
||||
|
||||
pub use sc_network::IfDisconnected;
|
||||
|
||||
use polkadot_node_network_protocol::{
|
||||
peer_set::PeerSet,
|
||||
request_response::{request::IncomingRequest, v1 as req_res_v1, Requests},
|
||||
v1 as protocol_v1, PeerId, UnifiedReputationChange,
|
||||
peer_set::PeerSet, request_response::Requests, v1 as protocol_v1, PeerId,
|
||||
UnifiedReputationChange,
|
||||
};
|
||||
use polkadot_node_primitives::{
|
||||
approval::{BlockApprovalMeta, IndirectAssignmentCert, IndirectSignedApprovalVote},
|
||||
@@ -165,8 +164,6 @@ pub enum CollatorProtocolMessage {
|
||||
/// Get a network bridge update.
|
||||
#[from]
|
||||
NetworkBridgeUpdateV1(NetworkBridgeEvent<protocol_v1::CollatorProtocolMessage>),
|
||||
/// Incoming network request for a collation.
|
||||
CollationFetchingRequest(IncomingRequest<req_res_v1::CollationFetchingRequest>),
|
||||
/// We recommended a particular candidate to be seconded, but it was invalid; penalize the collator.
|
||||
///
|
||||
/// The hash is the relay parent.
|
||||
@@ -297,9 +294,6 @@ pub enum DisputeDistributionMessage {
|
||||
/// Tell dispute distribution to distribute an explicit dispute statement to
|
||||
/// validators.
|
||||
SendDispute(DisputeMessage),
|
||||
|
||||
/// Get receiver for receiving incoming network requests for dispute sending.
|
||||
DisputeSendingReceiver(mpsc::Receiver<sc_network::config::IncomingRequest>),
|
||||
}
|
||||
|
||||
/// Messages received by the network bridge subsystem.
|
||||
@@ -380,10 +374,6 @@ impl NetworkBridgeMessage {
|
||||
/// Availability Distribution Message.
|
||||
#[derive(Debug)]
|
||||
pub enum AvailabilityDistributionMessage {
|
||||
/// Incoming network request for an availability chunk.
|
||||
ChunkFetchingRequest(IncomingRequest<req_res_v1::ChunkFetchingRequest>),
|
||||
/// Incoming network request for a seconded PoV.
|
||||
PoVFetchingRequest(IncomingRequest<req_res_v1::PoVFetchingRequest>),
|
||||
/// Instruct availability distribution to fetch a remote PoV.
|
||||
///
|
||||
/// NOTE: The result of this fetch is not yet locally validated and could be bogus.
|
||||
@@ -413,9 +403,6 @@ pub enum AvailabilityRecoveryMessage {
|
||||
Option<GroupIndex>, // Optional backing group to request from first.
|
||||
oneshot::Sender<Result<AvailableData, crate::errors::RecoveryError>>,
|
||||
),
|
||||
/// Incoming network request for available data.
|
||||
#[from]
|
||||
AvailableDataFetchingRequest(IncomingRequest<req_res_v1::AvailableDataFetchingRequest>),
|
||||
}
|
||||
|
||||
/// Bitfield distribution message.
|
||||
@@ -666,8 +653,6 @@ pub enum StatementDistributionMessage {
|
||||
/// Event from the network bridge.
|
||||
#[from]
|
||||
NetworkBridgeUpdateV1(NetworkBridgeEvent<protocol_v1::StatementDistributionMessage>),
|
||||
/// Get receiver for receiving incoming network requests for statement fetching.
|
||||
StatementFetchingReceiver(mpsc::Receiver<sc_network::config::IncomingRequest>),
|
||||
}
|
||||
|
||||
/// This data becomes intrinsics or extrinsics which should be included in a future relay chain block.
|
||||
@@ -867,19 +852,3 @@ pub enum ApprovalDistributionMessage {
|
||||
/// Message to the Gossip Support subsystem.
|
||||
#[derive(Debug)]
|
||||
pub enum GossipSupportMessage {}
|
||||
|
||||
impl From<IncomingRequest<req_res_v1::PoVFetchingRequest>> for AvailabilityDistributionMessage {
|
||||
fn from(req: IncomingRequest<req_res_v1::PoVFetchingRequest>) -> Self {
|
||||
Self::PoVFetchingRequest(req)
|
||||
}
|
||||
}
|
||||
impl From<IncomingRequest<req_res_v1::ChunkFetchingRequest>> for AvailabilityDistributionMessage {
|
||||
fn from(req: IncomingRequest<req_res_v1::ChunkFetchingRequest>) -> Self {
|
||||
Self::ChunkFetchingRequest(req)
|
||||
}
|
||||
}
|
||||
impl From<IncomingRequest<req_res_v1::CollationFetchingRequest>> for CollatorProtocolMessage {
|
||||
fn from(req: IncomingRequest<req_res_v1::CollationFetchingRequest>) -> Self {
|
||||
Self::CollationFetchingRequest(req)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ pin-project = "1.0.7"
|
||||
rand = "0.8.3"
|
||||
thiserror = "1.0.26"
|
||||
tracing = "0.1.26"
|
||||
derive_more = "0.99.11"
|
||||
lru = "0.6.6"
|
||||
|
||||
polkadot-node-primitives = { path = "../primitives" }
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Utilities for general error handling in Polkadot.
|
||||
//!
|
||||
//! Goals:
|
||||
//!
|
||||
//! - Ergonomic API with little repetition.
|
||||
//! - Still explicitness where it matters - fatal errors should be visible and justified.
|
||||
//! - Easy recovering from non-fatal errors.
|
||||
//! - Errors start as non-fatal and can be made fatal at the level where it is really clear they
|
||||
//! are fatal. E.g. cancellation of a oneshot might be fatal in one case, but absolutely expected
|
||||
//! in another.
|
||||
//! - Good error messages. Fatal errors don't need to be properly structured (as we won't handle
|
||||
//! them), but should provide good error messages of what is going on.
|
||||
//! - Encourage many error types. One per module or even per function is totally fine - it makes
|
||||
//! error handling robust, if you only need to handle errors that can actually happen, also error
|
||||
//! messages will get better.
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Error abstraction.
|
||||
///
|
||||
/// Errors might either be fatal and should bring the subsystem down or are at least at the point
|
||||
/// of occurrence deemed potentially recoverable.
|
||||
///
|
||||
/// Upper layers might have a better view and might make a non-fatal error of a called function a
|
||||
/// fatal one. The opposite should not happen, therefore don't make an error fatal if you don't
|
||||
/// know it is in all cases.
|
||||
///
|
||||
/// Usage pattern:
|
||||
///
|
||||
/// ```
|
||||
/// use thiserror::Error;
|
||||
/// use polkadot_node_subsystem::errors::RuntimeApiError;
|
||||
/// use polkadot_primitives::v1::SessionIndex;
|
||||
/// use futures::channel::oneshot;
|
||||
/// use polkadot_node_subsystem_util::{Fault, runtime};
|
||||
///
|
||||
/// #[derive(Debug, Error)]
|
||||
/// #[error(transparent)]
|
||||
/// pub struct Error(pub Fault<NonFatal, Fatal>);
|
||||
///
|
||||
/// pub type Result<T> = std::result::Result<T, Error>;
|
||||
/// pub type NonFatalResult<T> = std::result::Result<T, NonFatal>;
|
||||
/// pub type FatalResult<T> = std::result::Result<T, Fatal>;
|
||||
///
|
||||
/// // Make an error from a `NonFatal` one.
|
||||
/// impl From<NonFatal> for Error {
|
||||
/// fn from(e: NonFatal) -> Self {
|
||||
/// Self(Fault::from_non_fatal(e))
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Make an Error from a `Fatal` one.
|
||||
/// impl From<Fatal> for Error {
|
||||
/// fn from(f: Fatal) -> Self {
|
||||
/// Self(Fault::from_fatal(f))
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Easy conversion from sub error types from other modules:
|
||||
/// impl From<runtime::Error> for Error {
|
||||
/// fn from(o: runtime::Error) -> Self {
|
||||
/// Self(Fault::from_other(o))
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Debug, Error)]
|
||||
/// pub enum Fatal {
|
||||
/// /// Really fatal stuff.
|
||||
/// #[error("Something fatal happened.")]
|
||||
/// SomeFatalError,
|
||||
/// /// Errors coming from runtime::Runtime.
|
||||
/// #[error("Error while accessing runtime information")]
|
||||
/// Runtime(#[from] runtime::Fatal),
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Debug, Error)]
|
||||
/// pub enum NonFatal {
|
||||
/// /// Some non fatal error.
|
||||
/// /// For example if we prune a block we're requesting info about.
|
||||
/// #[error("Non fatal error happened.")]
|
||||
/// SomeNonFatalError,
|
||||
///
|
||||
/// /// Errors coming from runtime::Runtime.
|
||||
/// #[error("Error while accessing runtime information")]
|
||||
/// Runtime(#[from] runtime::NonFatal),
|
||||
/// }
|
||||
/// ```
|
||||
/// Then mostly use `Error` in functions, you may also use `NonFatal` and `Fatal` directly in
|
||||
/// functions that strictly only fail non-fatal or fatal respectively, as `Fatal` and `NonFatal`
|
||||
/// can automatically converted into the above defined `Error`.
|
||||
/// ```
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Fault<E, F>
|
||||
where
|
||||
E: std::fmt::Debug + std::error::Error + 'static,
|
||||
F: std::fmt::Debug + std::error::Error + 'static,
|
||||
{
|
||||
/// Error is fatal and should be escalated up.
|
||||
///
|
||||
/// While we usually won't want to pattern match on those, a concrete descriptive enum might
|
||||
/// still be a good idea for easy auditing of what can go wrong in a module and also makes for
|
||||
/// good error messages thanks to `thiserror`.
|
||||
#[error("Fatal error occurred.")]
|
||||
Fatal(#[source] F),
|
||||
/// Error that is not fatal, at least not yet at this level of execution.
|
||||
#[error("Non fatal error occurred.")]
|
||||
Err(#[source] E),
|
||||
}
|
||||
|
||||
/// Due to typesystem constraints we cannot implement the following methods as standard
|
||||
/// `From::from` implementations. So no auto conversions by default, a simple `Result::map_err` is
|
||||
/// not too bad though.
|
||||
impl<E, F> Fault<E, F>
|
||||
where
|
||||
E: std::fmt::Debug + std::error::Error + 'static,
|
||||
F: std::fmt::Debug + std::error::Error + 'static,
|
||||
{
|
||||
/// Build an `Fault` from compatible fatal error.
|
||||
pub fn from_fatal<F1: Into<F>>(f: F1) -> Self {
|
||||
Self::Fatal(f.into())
|
||||
}
|
||||
|
||||
/// Build an `Fault` from compatible non-fatal error.
|
||||
pub fn from_non_fatal<E1: Into<E>>(e: E1) -> Self {
|
||||
Self::Err(e.into())
|
||||
}
|
||||
|
||||
/// Build an `Fault` from a compatible other `Fault`.
|
||||
pub fn from_other<E1, F1>(e: Fault<E1, F1>) -> Self
|
||||
where
|
||||
E1: Into<E> + std::fmt::Debug + std::error::Error + 'static,
|
||||
F1: Into<F> + std::fmt::Debug + std::error::Error + 'static,
|
||||
{
|
||||
match e {
|
||||
Fault::Fatal(f) => Self::from_fatal(f),
|
||||
Fault::Err(e) => Self::from_non_fatal(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unwrap non-fatal error and report fatal one.
|
||||
///
|
||||
/// This function is useful for top level error handling. Fatal errors will be extracted,
|
||||
/// non-fatal error will be returned for handling.
|
||||
///
|
||||
/// Usage:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use thiserror::Error;
|
||||
/// # use polkadot_node_subsystem_util::{Fault, unwrap_non_fatal};
|
||||
/// # use polkadot_node_subsystem::SubsystemError;
|
||||
/// # #[derive(Error, Debug)]
|
||||
/// # enum Fatal {
|
||||
/// # }
|
||||
/// # #[derive(Error, Debug)]
|
||||
/// # enum NonFatal {
|
||||
/// # }
|
||||
/// # fn computation() -> Result<(), Fault<NonFatal, Fatal>> {
|
||||
/// # panic!();
|
||||
/// # }
|
||||
/// #
|
||||
/// // Use run like so:
|
||||
/// // run(ctx)
|
||||
/// // .map_err(|e| SubsystemError::with_origin("subsystem-name", e))
|
||||
/// fn run() -> std::result::Result<(), Fatal> {
|
||||
/// loop {
|
||||
/// // ....
|
||||
/// if let Some(err) = unwrap_non_fatal(computation())? {
|
||||
/// println!("Something bad happened: {}", err);
|
||||
/// continue
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
pub fn unwrap_non_fatal<E, F>(result: Result<(), Fault<E, F>>) -> Result<Option<E>, F>
|
||||
where
|
||||
E: std::fmt::Debug + std::error::Error + 'static,
|
||||
F: std::fmt::Debug + std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
match result {
|
||||
Ok(()) => Ok(None),
|
||||
Err(Fault::Fatal(f)) => Err(f),
|
||||
Err(Fault::Err(e)) => Ok(Some(e)),
|
||||
}
|
||||
}
|
||||
@@ -74,8 +74,6 @@ pub use metered_channel as metered;
|
||||
pub use polkadot_node_network_protocol::MIN_GOSSIP_PEERS;
|
||||
|
||||
pub use determine_new_blocks::determine_new_blocks;
|
||||
/// Error classification.
|
||||
pub use error_handling::{unwrap_non_fatal, Fault};
|
||||
|
||||
/// These reexports are required so that external crates can use the `delegated_subsystem` macro properly.
|
||||
pub mod reexports {
|
||||
@@ -88,7 +86,6 @@ pub mod rolling_session_window;
|
||||
pub mod runtime;
|
||||
|
||||
mod determine_new_blocks;
|
||||
mod error_handling;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
@@ -23,23 +23,16 @@ use thiserror::Error;
|
||||
use polkadot_node_subsystem::errors::RuntimeApiError;
|
||||
use polkadot_primitives::v1::SessionIndex;
|
||||
|
||||
use crate::Fault;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Errors for `Runtime` cache.
|
||||
pub type Error = Fault<NonFatal, Fatal>;
|
||||
|
||||
impl From<NonFatal> for Error {
|
||||
fn from(e: NonFatal) -> Self {
|
||||
Self::from_non_fatal(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Fatal> for Error {
|
||||
fn from(f: Fatal) -> Self {
|
||||
Self::from_fatal(f)
|
||||
}
|
||||
#[derive(Debug, Error, derive_more::From)]
|
||||
#[error(transparent)]
|
||||
pub enum Error {
|
||||
/// All fatal errors.
|
||||
Fatal(Fatal),
|
||||
/// All nonfatal/potentially recoverable errors.
|
||||
NonFatal(NonFatal),
|
||||
}
|
||||
|
||||
/// Fatal runtime errors.
|
||||
|
||||
Reference in New Issue
Block a user