mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 00:01:09 +00:00
refactor overseer into proc-macro based pattern (#2962)
This commit is contained in:
committed by
GitHub
parent
2510bfc5d7
commit
3c9104daff
@@ -1,79 +0,0 @@
|
||||
// Copyright 2020 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 types for the subsystem requests.
|
||||
|
||||
/// A description of an error causing the runtime API request to be unservable.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RuntimeApiError(String);
|
||||
|
||||
impl From<String> for RuntimeApiError {
|
||||
fn from(s: String) -> Self {
|
||||
RuntimeApiError(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for RuntimeApiError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RuntimeApiError {}
|
||||
|
||||
/// A description of an error causing the chain API request to be unservable.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ChainApiError {
|
||||
msg: String,
|
||||
}
|
||||
|
||||
impl From<&str> for ChainApiError {
|
||||
fn from(s: &str) -> Self {
|
||||
s.to_owned().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ChainApiError {
|
||||
fn from(msg: String) -> Self {
|
||||
Self { msg }
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for ChainApiError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
|
||||
write!(f, "{}", self.msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ChainApiError {}
|
||||
|
||||
/// An error that may happen during Availability Recovery process.
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum RecoveryError {
|
||||
/// A chunk is recovered but is invalid.
|
||||
Invalid,
|
||||
|
||||
/// A requested chunk is unavailable.
|
||||
Unavailable,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RecoveryError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
|
||||
write!(f, "{}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RecoveryError {}
|
||||
@@ -14,235 +14,28 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Subsystem trait definitions and message types.
|
||||
//! Subsystem accumulation.
|
||||
//!
|
||||
//! Node-side logic for Polkadot is mostly comprised of Subsystems, which are discrete components
|
||||
//! that communicate via message-passing. They are coordinated by an overseer, provided by a
|
||||
//! separate crate.
|
||||
//! Node-side types and generated overseer.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use std::{pin::Pin, sync::Arc, fmt};
|
||||
|
||||
use futures::prelude::*;
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use futures::future::BoxFuture;
|
||||
|
||||
use polkadot_primitives::v1::{Hash, BlockNumber};
|
||||
use async_trait::async_trait;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
pub mod errors;
|
||||
pub mod messages;
|
||||
#![deny(missing_docs)]
|
||||
#![deny(unused_crate_dependencies)]
|
||||
|
||||
pub use polkadot_node_jaeger as jaeger;
|
||||
pub use jaeger::*;
|
||||
|
||||
use self::messages::AllMessages;
|
||||
pub use polkadot_overseer::{OverseerSignal, ActiveLeavesUpdate, self as overseer};
|
||||
|
||||
/// How many slots are stack-reserved for active leaves updates
|
||||
///
|
||||
/// If there are fewer than this number of slots, then we've wasted some stack space.
|
||||
/// If there are greater than this number of slots, then we fall back to a heap vector.
|
||||
const ACTIVE_LEAVES_SMALLVEC_CAPACITY: usize = 8;
|
||||
pub use polkadot_node_subsystem_types::{
|
||||
errors::{self, *},
|
||||
ActivatedLeaf,
|
||||
LeafStatus,
|
||||
};
|
||||
|
||||
|
||||
/// The status of an activated leaf.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LeafStatus {
|
||||
/// A leaf is fresh when it's the first time the leaf has been encountered.
|
||||
/// Most leaves should be fresh.
|
||||
Fresh,
|
||||
/// A leaf is stale when it's encountered for a subsequent time. This will happen
|
||||
/// when the chain is reverted or the fork-choice rule abandons some chain.
|
||||
Stale,
|
||||
}
|
||||
|
||||
impl LeafStatus {
|
||||
/// Returns a bool indicating fresh status.
|
||||
pub fn is_fresh(&self) -> bool {
|
||||
match *self {
|
||||
LeafStatus::Fresh => true,
|
||||
LeafStatus::Stale => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a bool indicating stale status.
|
||||
pub fn is_stale(&self) -> bool {
|
||||
match *self {
|
||||
LeafStatus::Fresh => false,
|
||||
LeafStatus::Stale => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Activated leaf.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ActivatedLeaf {
|
||||
/// The block hash.
|
||||
pub hash: Hash,
|
||||
/// The block number.
|
||||
pub number: BlockNumber,
|
||||
/// The status of the leaf.
|
||||
pub status: LeafStatus,
|
||||
/// An associated [`jaeger::Span`].
|
||||
///
|
||||
/// NOTE: Each span should only be kept active as long as the leaf is considered active and should be dropped
|
||||
/// when the leaf is deactivated.
|
||||
pub span: Arc<jaeger::Span>,
|
||||
}
|
||||
|
||||
/// Changes in the set of active leaves: the parachain heads which we care to work on.
|
||||
///
|
||||
/// Note that the activated and deactivated fields indicate deltas, not complete sets.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ActiveLeavesUpdate {
|
||||
/// New relay chain blocks of interest.
|
||||
pub activated: SmallVec<[ActivatedLeaf; ACTIVE_LEAVES_SMALLVEC_CAPACITY]>,
|
||||
/// Relay chain block hashes no longer of interest.
|
||||
pub deactivated: SmallVec<[Hash; ACTIVE_LEAVES_SMALLVEC_CAPACITY]>,
|
||||
}
|
||||
|
||||
impl ActiveLeavesUpdate {
|
||||
/// Create a ActiveLeavesUpdate with a single activated hash
|
||||
pub fn start_work(activated: ActivatedLeaf) -> Self {
|
||||
Self { activated: [activated][..].into(), ..Default::default() }
|
||||
}
|
||||
|
||||
/// Create a ActiveLeavesUpdate with a single deactivated hash
|
||||
pub fn stop_work(hash: Hash) -> Self {
|
||||
Self { deactivated: [hash][..].into(), ..Default::default() }
|
||||
}
|
||||
|
||||
/// Is this update empty and doesn't contain any information?
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.activated.is_empty() && self.deactivated.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ActiveLeavesUpdate {
|
||||
/// Equality for `ActiveLeavesUpdate` doesnt imply bitwise equality.
|
||||
///
|
||||
/// Instead, it means equality when `activated` and `deactivated` are considered as sets.
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.activated.len() == other.activated.len() && self.deactivated.len() == other.deactivated.len()
|
||||
&& self.activated.iter().all(|a| other.activated.iter().any(|o| a.hash == o.hash))
|
||||
&& self.deactivated.iter().all(|a| other.deactivated.contains(a))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ActiveLeavesUpdate {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
struct Activated<'a>(&'a [ActivatedLeaf]);
|
||||
impl fmt::Debug for Activated<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_list().entries(self.0.iter().map(|e| e.hash)).finish()
|
||||
}
|
||||
}
|
||||
|
||||
f.debug_struct("ActiveLeavesUpdate")
|
||||
.field("activated", &Activated(&self.activated))
|
||||
.field("deactivated", &self.deactivated)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Signals sent by an overseer to a subsystem.
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum OverseerSignal {
|
||||
/// Subsystems should adjust their jobs to start and stop work on appropriate block hashes.
|
||||
ActiveLeaves(ActiveLeavesUpdate),
|
||||
/// `Subsystem` is informed of a finalized block by its block hash and number.
|
||||
BlockFinalized(Hash, BlockNumber),
|
||||
/// Conclude the work of the `Overseer` and all `Subsystem`s.
|
||||
Conclude,
|
||||
}
|
||||
|
||||
/// A message type that a subsystem receives from an overseer.
|
||||
/// It wraps signals from an overseer and messages that are circulating
|
||||
/// between subsystems.
|
||||
///
|
||||
/// It is generic over over the message type `M` that a particular `Subsystem` may use.
|
||||
#[derive(Debug)]
|
||||
pub enum FromOverseer<M> {
|
||||
/// Signal from the `Overseer`.
|
||||
Signal(OverseerSignal),
|
||||
|
||||
/// Some other `Subsystem`'s message.
|
||||
Communication {
|
||||
/// Contained message
|
||||
msg: M,
|
||||
},
|
||||
}
|
||||
|
||||
impl<M> From<OverseerSignal> for FromOverseer<M> {
|
||||
fn from(signal: OverseerSignal) -> Self {
|
||||
FromOverseer::Signal(signal)
|
||||
}
|
||||
}
|
||||
|
||||
/// An error type that describes faults that may happen
|
||||
///
|
||||
/// These are:
|
||||
/// * Channels being closed
|
||||
/// * Subsystems dying when they are not expected to
|
||||
/// * Subsystems not dying when they are told to die
|
||||
/// * etc.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum SubsystemError {
|
||||
#[error(transparent)]
|
||||
NotifyCancellation(#[from] oneshot::Canceled),
|
||||
|
||||
#[error(transparent)]
|
||||
QueueError(#[from] mpsc::SendError),
|
||||
|
||||
#[error("Failed to spawn a task: {0}")]
|
||||
TaskSpawn(&'static str),
|
||||
|
||||
#[error(transparent)]
|
||||
Infallible(#[from] std::convert::Infallible),
|
||||
|
||||
#[error(transparent)]
|
||||
Prometheus(#[from] substrate_prometheus_endpoint::PrometheusError),
|
||||
|
||||
#[error(transparent)]
|
||||
Jaeger(#[from] JaegerError),
|
||||
|
||||
#[error("Failed to {0}")]
|
||||
Context(String),
|
||||
|
||||
#[error("Subsystem stalled: {0}")]
|
||||
SubsystemStalled(&'static str),
|
||||
|
||||
/// Per origin (or subsystem) annotations to wrap an error.
|
||||
#[error("Error originated in {origin}")]
|
||||
FromOrigin {
|
||||
/// An additional annotation tag for the origin of `source`.
|
||||
origin: &'static str,
|
||||
/// The wrapped error. Marked as source for tracking the error chain.
|
||||
#[source] source: Box<dyn 'static + std::error::Error + Send + Sync>
|
||||
},
|
||||
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
impl SubsystemError {
|
||||
/// Adds a `str` as `origin` to the given error `err`.
|
||||
pub fn with_origin<E: 'static + Send + Sync + std::error::Error>(origin: &'static str, err: E) -> Self {
|
||||
Self::FromOrigin { origin, source: Box::new(err) }
|
||||
}
|
||||
}
|
||||
|
||||
/// An asynchronous subsystem task..
|
||||
///
|
||||
/// In essence it's just a newtype wrapping a `BoxFuture`.
|
||||
pub struct SpawnedSubsystem {
|
||||
/// Name of the subsystem being spawned.
|
||||
pub name: &'static str,
|
||||
/// The task of the subsystem being spawned.
|
||||
pub future: BoxFuture<'static, SubsystemResult<()>>,
|
||||
/// Re-export of all messages type, including the wrapper type.
|
||||
pub mod messages {
|
||||
pub use super::overseer::AllMessages;
|
||||
pub use polkadot_node_subsystem_types::messages::*;
|
||||
}
|
||||
|
||||
/// A `Result` type that wraps [`SubsystemError`].
|
||||
@@ -250,133 +43,48 @@ pub struct SpawnedSubsystem {
|
||||
/// [`SubsystemError`]: struct.SubsystemError.html
|
||||
pub type SubsystemResult<T> = Result<T, SubsystemError>;
|
||||
|
||||
/// A sender used by subsystems to communicate with other subsystems.
|
||||
///
|
||||
/// Each clone of this type may add more capacity to the bounded buffer, so clones should
|
||||
/// be used sparingly.
|
||||
#[async_trait]
|
||||
pub trait SubsystemSender: Send + Clone + 'static {
|
||||
/// Send a direct message to some other `Subsystem`, routed based on message type.
|
||||
async fn send_message(&mut self, msg: AllMessages);
|
||||
// Simplify usage without having to do large scale modifications of all
|
||||
// subsystems at once.
|
||||
|
||||
/// Send multiple direct messages to other `Subsystem`s, routed based on message type.
|
||||
async fn send_messages<T>(&mut self, msgs: T)
|
||||
where T: IntoIterator<Item = AllMessages> + Send, T::IntoIter: Send;
|
||||
|
||||
/// Send a message onto the unbounded queue of some other `Subsystem`, routed based on message
|
||||
/// type.
|
||||
///
|
||||
/// This function should be used only when there is some other bounding factor on the messages
|
||||
/// sent with it. Otherwise, it risks a memory leak.
|
||||
fn send_unbounded_message(&mut self, msg: AllMessages);
|
||||
/// Specialized message type originating from the overseer.
|
||||
pub type FromOverseer<M> = polkadot_overseer::gen::FromOverseer<M, OverseerSignal>;
|
||||
|
||||
/// Specialized subsystem instance type of subsystems consuming a particular message type.
|
||||
pub type SubsystemInstance<Message> = polkadot_overseer::gen::SubsystemInstance<Message, OverseerSignal>;
|
||||
|
||||
/// Sender trait for the `AllMessages` wrapper.
|
||||
pub trait SubsystemSender: polkadot_overseer::gen::SubsystemSender<messages::AllMessages> {
|
||||
}
|
||||
|
||||
/// A context type that is given to the [`Subsystem`] upon spawning.
|
||||
/// It can be used by [`Subsystem`] to communicate with other [`Subsystem`]s
|
||||
/// or spawn jobs.
|
||||
///
|
||||
/// [`Overseer`]: struct.Overseer.html
|
||||
/// [`SubsystemJob`]: trait.SubsystemJob.html
|
||||
#[async_trait]
|
||||
pub trait SubsystemContext: Send + Sized + 'static {
|
||||
/// The message type of this context. Subsystems launched with this context will expect
|
||||
/// to receive messages of this type.
|
||||
type Message: Send;
|
||||
|
||||
/// The message sender type of this context. Clones of the sender should be used sparingly.
|
||||
type Sender: SubsystemSender;
|
||||
|
||||
/// Try to asynchronously receive a message.
|
||||
///
|
||||
/// This has to be used with caution, if you loop over this without
|
||||
/// using `pending!()` macro you will end up with a busy loop!
|
||||
async fn try_recv(&mut self) -> Result<Option<FromOverseer<Self::Message>>, ()>;
|
||||
|
||||
/// Receive a message.
|
||||
async fn recv(&mut self) -> SubsystemResult<FromOverseer<Self::Message>>;
|
||||
|
||||
/// Spawn a child task on the executor.
|
||||
fn spawn(&mut self, name: &'static str, s: Pin<Box<dyn Future<Output = ()> + Send>>) -> SubsystemResult<()>;
|
||||
|
||||
/// Spawn a blocking child task on the executor's dedicated thread pool.
|
||||
fn spawn_blocking(
|
||||
&mut self,
|
||||
name: &'static str,
|
||||
s: Pin<Box<dyn Future<Output = ()> + Send>>,
|
||||
) -> SubsystemResult<()>;
|
||||
|
||||
/// Get a mutable reference to the sender.
|
||||
fn sender(&mut self) -> &mut Self::Sender;
|
||||
|
||||
/// Send a direct message to some other `Subsystem`, routed based on message type.
|
||||
async fn send_message(&mut self, msg: AllMessages) {
|
||||
self.sender().send_message(msg).await
|
||||
}
|
||||
|
||||
/// Send multiple direct messages to other `Subsystem`s, routed based on message type.
|
||||
async fn send_messages<T>(&mut self, msgs: T)
|
||||
where T: IntoIterator<Item = AllMessages> + Send, T::IntoIter: Send
|
||||
{
|
||||
self.sender().send_messages(msgs).await
|
||||
}
|
||||
|
||||
|
||||
/// Send a message onto the unbounded queue of some other `Subsystem`, routed based on message
|
||||
/// type.
|
||||
///
|
||||
/// This function should be used only when there is some other bounding factor on the messages
|
||||
/// sent with it. Otherwise, it risks a memory leak.
|
||||
///
|
||||
/// Generally, for this method to be used, these conditions should be met:
|
||||
/// * There is a communication cycle between subsystems
|
||||
/// * One of the parts of the cycle has a clear bound on the number of messages produced.
|
||||
fn send_unbounded_message(&mut self, msg: AllMessages) {
|
||||
self.sender().send_unbounded_message(msg)
|
||||
}
|
||||
impl<T> SubsystemSender for T where T: polkadot_overseer::gen::SubsystemSender<messages::AllMessages> {
|
||||
}
|
||||
|
||||
/// A trait that describes the [`Subsystem`]s that can run on the [`Overseer`].
|
||||
///
|
||||
/// It is generic over the message type circulating in the system.
|
||||
/// The idea that we want some type contaning persistent state that
|
||||
/// can spawn actually running subsystems when asked to.
|
||||
///
|
||||
/// [`Overseer`]: struct.Overseer.html
|
||||
/// [`Subsystem`]: trait.Subsystem.html
|
||||
pub trait Subsystem<C: SubsystemContext> {
|
||||
/// Start this `Subsystem` and return `SpawnedSubsystem`.
|
||||
fn start(self, ctx: C) -> SpawnedSubsystem;
|
||||
}
|
||||
/// Spawned subsystem.
|
||||
pub type SpawnedSubsystem = polkadot_overseer::gen::SpawnedSubsystem<SubsystemError>;
|
||||
|
||||
/// A dummy subsystem that implements [`Subsystem`] for all
|
||||
/// types of messages. Used for tests or as a placeholder.
|
||||
pub struct DummySubsystem;
|
||||
|
||||
impl<C: SubsystemContext> Subsystem<C> for DummySubsystem
|
||||
where
|
||||
C::Message: std::fmt::Debug
|
||||
/// Convenience trait specialization.
|
||||
pub trait SubsystemContext: polkadot_overseer::gen::SubsystemContext<
|
||||
Signal=OverseerSignal,
|
||||
AllMessages=messages::AllMessages,
|
||||
Error=SubsystemError,
|
||||
>
|
||||
{
|
||||
fn start(self, mut ctx: C) -> SpawnedSubsystem {
|
||||
let future = Box::pin(async move {
|
||||
loop {
|
||||
match ctx.recv().await {
|
||||
Err(_) => return Ok(()),
|
||||
Ok(FromOverseer::Signal(OverseerSignal::Conclude)) => return Ok(()),
|
||||
Ok(overseer_msg) => {
|
||||
tracing::debug!(
|
||||
target: "dummy-subsystem",
|
||||
"Discarding a message sent from overseer {:?}",
|
||||
overseer_msg
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
SpawnedSubsystem {
|
||||
name: "dummy-subsystem",
|
||||
future,
|
||||
}
|
||||
}
|
||||
/// The message type the subsystem consumes.
|
||||
type Message: std::fmt::Debug + Send + 'static;
|
||||
/// Sender type to communicate with other subsystems.
|
||||
type Sender: SubsystemSender + Send + Clone + 'static;
|
||||
}
|
||||
|
||||
impl<T> SubsystemContext for T
|
||||
where
|
||||
T: polkadot_overseer::gen::SubsystemContext<
|
||||
Signal=OverseerSignal,
|
||||
AllMessages=messages::AllMessages,
|
||||
Error=SubsystemError,
|
||||
>,
|
||||
{
|
||||
type Message = <Self as polkadot_overseer::gen::SubsystemContext>::Message;
|
||||
type Sender = <Self as polkadot_overseer::gen::SubsystemContext>::Sender;
|
||||
}
|
||||
|
||||
@@ -1,901 +0,0 @@
|
||||
// Copyright 2017-2020 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/>.
|
||||
|
||||
//! Message types for the overseer and subsystems.
|
||||
//!
|
||||
//! These messages are intended to define the protocol by which different subsystems communicate with each
|
||||
//! other and signals that they receive from an overseer to coordinate their work.
|
||||
//! This is intended for use with the `polkadot-overseer` crate.
|
||||
//!
|
||||
//! Subsystems' APIs are defined separately from their implementation, leading to easier mocking.
|
||||
|
||||
use std::{collections::{BTreeMap, HashSet}, sync::Arc};
|
||||
|
||||
use futures::channel::{mpsc, 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,
|
||||
};
|
||||
use polkadot_node_primitives::{
|
||||
approval::{BlockApprovalMeta, IndirectAssignmentCert, IndirectSignedApprovalVote},
|
||||
AvailableData, BabeEpoch, CandidateVotes, CollationGenerationConfig, ErasureChunk, PoV,
|
||||
SignedDisputeStatement, SignedFullStatement, ValidationResult, BlockWeight,
|
||||
};
|
||||
use polkadot_primitives::v1::{
|
||||
AuthorityDiscoveryId, BackedCandidate, BlockNumber, CandidateDescriptor, CandidateEvent,
|
||||
CandidateHash, CandidateIndex, CandidateReceipt, CollatorId, CommittedCandidateReceipt,
|
||||
CoreState, GroupIndex, GroupRotationInfo, Hash, Header as BlockHeader, Id as ParaId,
|
||||
InboundDownwardMessage, InboundHrmpMessage, MultiDisputeStatementSet, OccupiedCoreAssumption,
|
||||
PersistedValidationData, SessionIndex, SessionInfo, SignedAvailabilityBitfield,
|
||||
SignedAvailabilityBitfields, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex,
|
||||
ValidatorSignature,
|
||||
};
|
||||
use polkadot_procmacro_subsystem_dispatch_gen::subsystem_dispatch_gen;
|
||||
use polkadot_statement_table::v1::Misbehavior;
|
||||
|
||||
/// Network events as transmitted to other subsystems, wrapped in their message types.
|
||||
pub mod network_bridge_event;
|
||||
pub use network_bridge_event::NetworkBridgeEvent;
|
||||
|
||||
/// Subsystem messages where each message is always bound to a relay parent.
|
||||
pub trait BoundToRelayParent {
|
||||
/// Returns the relay parent this message is bound to.
|
||||
fn relay_parent(&self) -> Hash;
|
||||
}
|
||||
|
||||
/// Messages received by the Candidate Backing subsystem.
|
||||
#[derive(Debug)]
|
||||
pub enum CandidateBackingMessage {
|
||||
/// Requests a set of backable candidates that could be backed in a child of the given
|
||||
/// relay-parent, referenced by its hash.
|
||||
GetBackedCandidates(Hash, Vec<CandidateHash>, oneshot::Sender<Vec<BackedCandidate>>),
|
||||
/// Note that the Candidate Backing subsystem should second the given candidate in the context of the
|
||||
/// given relay-parent (ref. by hash). This candidate must be validated.
|
||||
Second(Hash, CandidateReceipt, PoV),
|
||||
/// Note a validator's statement about a particular candidate. Disagreements about validity must be escalated
|
||||
/// to a broader check by Misbehavior Arbitration. Agreements are simply tallied until a quorum is reached.
|
||||
Statement(Hash, SignedFullStatement),
|
||||
}
|
||||
|
||||
impl BoundToRelayParent for CandidateBackingMessage {
|
||||
fn relay_parent(&self) -> Hash {
|
||||
match self {
|
||||
Self::GetBackedCandidates(hash, _, _) => *hash,
|
||||
Self::Second(hash, _, _) => *hash,
|
||||
Self::Statement(hash, _) => *hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Blanket error for validation failing for internal reasons.
|
||||
#[derive(Debug, Error)]
|
||||
#[error("Validation failed with {0:?}")]
|
||||
pub struct ValidationFailed(pub String);
|
||||
|
||||
/// Messages received by the Validation subsystem.
|
||||
///
|
||||
/// ## Validation Requests
|
||||
///
|
||||
/// Validation requests made to the subsystem should return an error only on internal error.
|
||||
/// Otherwise, they should return either `Ok(ValidationResult::Valid(_))`
|
||||
/// or `Ok(ValidationResult::Invalid)`.
|
||||
#[derive(Debug)]
|
||||
pub enum CandidateValidationMessage {
|
||||
/// Validate a candidate with provided parameters using relay-chain state.
|
||||
///
|
||||
/// This will implicitly attempt to gather the `PersistedValidationData` and `ValidationCode`
|
||||
/// from the runtime API of the chain, based on the `relay_parent`
|
||||
/// of the `CandidateDescriptor`.
|
||||
///
|
||||
/// This will also perform checking of validation outputs against the acceptance criteria.
|
||||
///
|
||||
/// If there is no state available which can provide this data or the core for
|
||||
/// the para is not free at the relay-parent, an error is returned.
|
||||
ValidateFromChainState(
|
||||
CandidateDescriptor,
|
||||
Arc<PoV>,
|
||||
oneshot::Sender<Result<ValidationResult, ValidationFailed>>,
|
||||
),
|
||||
/// Validate a candidate with provided, exhaustive parameters for validation.
|
||||
///
|
||||
/// Explicitly provide the `PersistedValidationData` and `ValidationCode` so this can do full
|
||||
/// validation without needing to access the state of the relay-chain.
|
||||
///
|
||||
/// This request doesn't involve acceptance criteria checking, therefore only useful for the
|
||||
/// cases where the validity of the candidate is established. This is the case for the typical
|
||||
/// use-case: secondary checkers would use this request relying on the full prior checks
|
||||
/// performed by the relay-chain.
|
||||
ValidateFromExhaustive(
|
||||
PersistedValidationData,
|
||||
ValidationCode,
|
||||
CandidateDescriptor,
|
||||
Arc<PoV>,
|
||||
oneshot::Sender<Result<ValidationResult, ValidationFailed>>,
|
||||
),
|
||||
}
|
||||
|
||||
impl CandidateValidationMessage {
|
||||
/// If the current variant contains the relay parent hash, return it.
|
||||
pub fn relay_parent(&self) -> Option<Hash> {
|
||||
match self {
|
||||
Self::ValidateFromChainState(_, _, _) => None,
|
||||
Self::ValidateFromExhaustive(_, _, _, _, _) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Messages received by the Collator Protocol subsystem.
|
||||
#[derive(Debug, derive_more::From)]
|
||||
pub enum CollatorProtocolMessage {
|
||||
/// Signal to the collator protocol that it should connect to validators with the expectation
|
||||
/// of collating on the given para. This is only expected to be called once, early on, if at all,
|
||||
/// and only by the Collation Generation subsystem. As such, it will overwrite the value of
|
||||
/// the previous signal.
|
||||
///
|
||||
/// This should be sent before any `DistributeCollation` message.
|
||||
CollateOn(ParaId),
|
||||
/// Provide a collation to distribute to validators with an optional result sender.
|
||||
///
|
||||
/// The result sender should be informed when at least one parachain validator seconded the collation. It is also
|
||||
/// completely okay to just drop the sender.
|
||||
DistributeCollation(CandidateReceipt, PoV, Option<oneshot::Sender<SignedFullStatement>>),
|
||||
/// Report a collator as having provided an invalid collation. This should lead to disconnect
|
||||
/// and blacklist of the collator.
|
||||
ReportCollator(CollatorId),
|
||||
/// 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.
|
||||
Invalid(Hash, CandidateReceipt),
|
||||
/// The candidate we recommended to be seconded was validated successfully.
|
||||
///
|
||||
/// The hash is the relay parent.
|
||||
Seconded(Hash, SignedFullStatement),
|
||||
}
|
||||
|
||||
impl Default for CollatorProtocolMessage {
|
||||
fn default() -> Self {
|
||||
Self::CollateOn(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundToRelayParent for CollatorProtocolMessage {
|
||||
fn relay_parent(&self) -> Hash {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Messages received by the dispute coordinator subsystem.
|
||||
#[derive(Debug)]
|
||||
pub enum DisputeCoordinatorMessage {
|
||||
/// Import a statement by a validator about a candidate.
|
||||
///
|
||||
/// The subsystem will silently discard ancient statements or sets of only dispute-specific statements for
|
||||
/// candidates that are previously unknown to the subsystem. The former is simply because ancient
|
||||
/// data is not relevant and the latter is as a DoS prevention mechanism. Both backing and approval
|
||||
/// statements already undergo anti-DoS procedures in their respective subsystems, but statements
|
||||
/// cast specifically for disputes are not necessarily relevant to any candidate the system is
|
||||
/// already aware of and thus present a DoS vector. Our expectation is that nodes will notify each
|
||||
/// other of disputes over the network by providing (at least) 2 conflicting statements, of which one is either
|
||||
/// a backing or validation statement.
|
||||
///
|
||||
/// This does not do any checking of the message signature.
|
||||
ImportStatements {
|
||||
/// The hash of the candidate.
|
||||
candidate_hash: CandidateHash,
|
||||
/// The candidate receipt itself.
|
||||
candidate_receipt: CandidateReceipt,
|
||||
/// The session the candidate appears in.
|
||||
session: SessionIndex,
|
||||
/// Statements, with signatures checked, by validators participating in disputes.
|
||||
///
|
||||
/// The validator index passed alongside each statement should correspond to the index
|
||||
/// of the validator in the set.
|
||||
statements: Vec<(SignedDisputeStatement, ValidatorIndex)>,
|
||||
},
|
||||
/// Fetch a list of all active disputes that the coordinator is aware of.
|
||||
ActiveDisputes(oneshot::Sender<Vec<(SessionIndex, CandidateHash)>>),
|
||||
/// Get candidate votes for a candidate.
|
||||
QueryCandidateVotes(SessionIndex, CandidateHash, oneshot::Sender<Option<CandidateVotes>>),
|
||||
/// Sign and issue local dispute votes. A value of `true` indicates validity, and `false` invalidity.
|
||||
IssueLocalStatement(SessionIndex, CandidateHash, CandidateReceipt, bool),
|
||||
/// Determine the highest undisputed block within the given chain, based on where candidates
|
||||
/// were included. If even the base block should not be finalized due to a dispute,
|
||||
/// then `None` should be returned on the channel.
|
||||
///
|
||||
/// The block descriptions begin counting upwards from the block after the given `base_number`. The `base_number`
|
||||
/// is typically the number of the last finalized block but may be slightly higher. This block
|
||||
/// is inevitably going to be finalized so it is not accounted for by this function.
|
||||
DetermineUndisputedChain {
|
||||
/// The number of the lowest possible block to vote on.
|
||||
base_number: BlockNumber,
|
||||
/// Descriptions of all the blocks counting upwards from the block after the base number
|
||||
block_descriptions: Vec<(Hash, SessionIndex, Vec<CandidateHash>)>,
|
||||
/// A response channel - `None` to vote on base, `Some` to vote higher.
|
||||
tx: oneshot::Sender<Option<(BlockNumber, Hash)>>,
|
||||
}
|
||||
}
|
||||
|
||||
/// Messages received by the dispute participation subsystem.
|
||||
#[derive(Debug)]
|
||||
pub enum DisputeParticipationMessage {
|
||||
/// Validate a candidate for the purposes of participating in a dispute.
|
||||
Participate {
|
||||
/// The hash of the candidate
|
||||
candidate_hash: CandidateHash,
|
||||
/// The candidate receipt itself.
|
||||
candidate_receipt: CandidateReceipt,
|
||||
/// The session the candidate appears in.
|
||||
session: SessionIndex,
|
||||
/// The number of validators in the session.
|
||||
n_validators: u32,
|
||||
},
|
||||
}
|
||||
|
||||
/// Messages received by the network bridge subsystem.
|
||||
#[derive(Debug)]
|
||||
pub enum NetworkBridgeMessage {
|
||||
/// Report a peer for their actions.
|
||||
ReportPeer(PeerId, UnifiedReputationChange),
|
||||
|
||||
/// Disconnect a peer from the given peer-set without affecting their reputation.
|
||||
DisconnectPeer(PeerId, PeerSet),
|
||||
|
||||
/// Send a message to one or more peers on the validation peer-set.
|
||||
SendValidationMessage(Vec<PeerId>, protocol_v1::ValidationProtocol),
|
||||
|
||||
/// Send a message to one or more peers on the collation peer-set.
|
||||
SendCollationMessage(Vec<PeerId>, protocol_v1::CollationProtocol),
|
||||
|
||||
/// Send a batch of validation messages.
|
||||
///
|
||||
/// NOTE: Messages will be processed in order (at least statement distribution relies on this).
|
||||
SendValidationMessages(Vec<(Vec<PeerId>, protocol_v1::ValidationProtocol)>),
|
||||
|
||||
/// Send a batch of collation messages.
|
||||
///
|
||||
/// NOTE: Messages will be processed in order.
|
||||
SendCollationMessages(Vec<(Vec<PeerId>, protocol_v1::CollationProtocol)>),
|
||||
|
||||
/// Send requests via substrate request/response.
|
||||
/// Second parameter, tells what to do if we are not yet connected to the peer.
|
||||
SendRequests(Vec<Requests>, IfDisconnected),
|
||||
|
||||
/// Connect to peers who represent the given `validator_ids`.
|
||||
///
|
||||
/// Also ask the network to stay connected to these peers at least
|
||||
/// until a new request is issued.
|
||||
///
|
||||
/// Because it overrides the previous request, it must be ensured
|
||||
/// that `validator_ids` include all peers the subsystems
|
||||
/// are interested in (per `PeerSet`).
|
||||
///
|
||||
/// A caller can learn about validator connections by listening to the
|
||||
/// `PeerConnected` events from the network bridge.
|
||||
ConnectToValidators {
|
||||
/// Ids of the validators to connect to.
|
||||
validator_ids: Vec<AuthorityDiscoveryId>,
|
||||
/// The underlying protocol to use for this request.
|
||||
peer_set: PeerSet,
|
||||
/// Sends back the number of `AuthorityDiscoveryId`s which
|
||||
/// authority discovery has failed to resolve.
|
||||
failed: oneshot::Sender<usize>,
|
||||
},
|
||||
/// Inform the distribution subsystems about the new
|
||||
/// gossip network topology formed.
|
||||
NewGossipTopology {
|
||||
/// Ids of our neighbors in the new gossip topology.
|
||||
/// We're not necessarily connected to all of them, but we should.
|
||||
our_neighbors: HashSet<AuthorityDiscoveryId>,
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkBridgeMessage {
|
||||
/// If the current variant contains the relay parent hash, return it.
|
||||
pub fn relay_parent(&self) -> Option<Hash> {
|
||||
match self {
|
||||
Self::ReportPeer(_, _) => None,
|
||||
Self::DisconnectPeer(_, _) => None,
|
||||
Self::SendValidationMessage(_, _) => None,
|
||||
Self::SendCollationMessage(_, _) => None,
|
||||
Self::SendValidationMessages(_) => None,
|
||||
Self::SendCollationMessages(_) => None,
|
||||
Self::ConnectToValidators { .. } => None,
|
||||
Self::SendRequests { .. } => None,
|
||||
Self::NewGossipTopology { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
FetchPoV {
|
||||
/// The relay parent giving the necessary context.
|
||||
relay_parent: Hash,
|
||||
/// Validator to fetch the PoV from.
|
||||
from_validator: ValidatorIndex,
|
||||
/// Candidate hash to fetch the PoV for.
|
||||
candidate_hash: CandidateHash,
|
||||
/// Expected hash of the PoV, a PoV not matching this hash will be rejected.
|
||||
pov_hash: Hash,
|
||||
/// Sender for getting back the result of this fetch.
|
||||
///
|
||||
/// The sender will be canceled if the fetching failed for some reason.
|
||||
tx: oneshot::Sender<PoV>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Availability Recovery Message.
|
||||
#[derive(Debug, derive_more::From)]
|
||||
pub enum AvailabilityRecoveryMessage {
|
||||
/// Recover available data from validators on the network.
|
||||
RecoverAvailableData(
|
||||
CandidateReceipt,
|
||||
SessionIndex,
|
||||
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.
|
||||
#[derive(Debug, derive_more::From)]
|
||||
pub enum BitfieldDistributionMessage {
|
||||
/// Distribute a bitfield via gossip to other validators.
|
||||
DistributeBitfield(Hash, SignedAvailabilityBitfield),
|
||||
|
||||
/// Event from the network bridge.
|
||||
#[from]
|
||||
NetworkBridgeUpdateV1(NetworkBridgeEvent<protocol_v1::BitfieldDistributionMessage>),
|
||||
}
|
||||
|
||||
impl BitfieldDistributionMessage {
|
||||
/// If the current variant contains the relay parent hash, return it.
|
||||
pub fn relay_parent(&self) -> Option<Hash> {
|
||||
match self {
|
||||
Self::DistributeBitfield(hash, _) => Some(*hash),
|
||||
Self::NetworkBridgeUpdateV1(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Bitfield signing message.
|
||||
///
|
||||
/// Currently non-instantiable.
|
||||
#[derive(Debug)]
|
||||
pub enum BitfieldSigningMessage {}
|
||||
|
||||
impl BoundToRelayParent for BitfieldSigningMessage {
|
||||
fn relay_parent(&self) -> Hash {
|
||||
match *self {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Availability store subsystem message.
|
||||
#[derive(Debug)]
|
||||
pub enum AvailabilityStoreMessage {
|
||||
/// Query a `AvailableData` from the AV store.
|
||||
QueryAvailableData(CandidateHash, oneshot::Sender<Option<AvailableData>>),
|
||||
|
||||
/// Query whether a `AvailableData` exists within the AV Store.
|
||||
///
|
||||
/// This is useful in cases when existence
|
||||
/// matters, but we don't want to necessarily pass around multiple
|
||||
/// megabytes of data to get a single bit of information.
|
||||
QueryDataAvailability(CandidateHash, oneshot::Sender<bool>),
|
||||
|
||||
/// Query an `ErasureChunk` from the AV store by the candidate hash and validator index.
|
||||
QueryChunk(CandidateHash, ValidatorIndex, oneshot::Sender<Option<ErasureChunk>>),
|
||||
|
||||
/// Query all chunks that we have for the given candidate hash.
|
||||
QueryAllChunks(CandidateHash, oneshot::Sender<Vec<ErasureChunk>>),
|
||||
|
||||
/// Query whether an `ErasureChunk` exists within the AV Store.
|
||||
///
|
||||
/// This is useful in cases like bitfield signing, when existence
|
||||
/// matters, but we don't want to necessarily pass around large
|
||||
/// quantities of data to get a single bit of information.
|
||||
QueryChunkAvailability(CandidateHash, ValidatorIndex, oneshot::Sender<bool>),
|
||||
|
||||
/// Store an `ErasureChunk` in the AV store.
|
||||
///
|
||||
/// Return `Ok(())` if the store operation succeeded, `Err(())` if it failed.
|
||||
StoreChunk {
|
||||
/// A hash of the candidate this chunk belongs to.
|
||||
candidate_hash: CandidateHash,
|
||||
/// The chunk itself.
|
||||
chunk: ErasureChunk,
|
||||
/// Sending side of the channel to send result to.
|
||||
tx: oneshot::Sender<Result<(), ()>>,
|
||||
},
|
||||
|
||||
/// Store a `AvailableData` in the AV store.
|
||||
/// If `ValidatorIndex` is present store corresponding chunk also.
|
||||
///
|
||||
/// Return `Ok(())` if the store operation succeeded, `Err(())` if it failed.
|
||||
StoreAvailableData(CandidateHash, Option<ValidatorIndex>, u32, AvailableData, oneshot::Sender<Result<(), ()>>),
|
||||
}
|
||||
|
||||
impl AvailabilityStoreMessage {
|
||||
/// In fact, none of the AvailabilityStore messages assume a particular relay parent.
|
||||
pub fn relay_parent(&self) -> Option<Hash> {
|
||||
match self {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A response channel for the result of a chain API request.
|
||||
pub type ChainApiResponseChannel<T> = oneshot::Sender<Result<T, crate::errors::ChainApiError>>;
|
||||
|
||||
/// Chain API request subsystem message.
|
||||
#[derive(Debug)]
|
||||
pub enum ChainApiMessage {
|
||||
/// Request the block number by hash.
|
||||
/// Returns `None` if a block with the given hash is not present in the db.
|
||||
BlockNumber(Hash, ChainApiResponseChannel<Option<BlockNumber>>),
|
||||
/// Request the block header by hash.
|
||||
/// Returns `None` if a block with the given hash is not present in the db.
|
||||
BlockHeader(Hash, ChainApiResponseChannel<Option<BlockHeader>>),
|
||||
/// Get the cumulative weight of the given block, by hash.
|
||||
/// If the block or weight is unknown, this returns `None`.
|
||||
///
|
||||
/// Note: this the weight within the low-level fork-choice rule,
|
||||
/// not the high-level one implemented in the chain-selection subsystem.
|
||||
///
|
||||
/// Weight is used for comparing blocks in a fork-choice rule.
|
||||
BlockWeight(Hash, ChainApiResponseChannel<Option<BlockWeight>>),
|
||||
/// Request the finalized block hash by number.
|
||||
/// Returns `None` if a block with the given number is not present in the db.
|
||||
/// Note: the caller must ensure the block is finalized.
|
||||
FinalizedBlockHash(BlockNumber, ChainApiResponseChannel<Option<Hash>>),
|
||||
/// Request the last finalized block number.
|
||||
/// This request always succeeds.
|
||||
FinalizedBlockNumber(ChainApiResponseChannel<BlockNumber>),
|
||||
/// Request the `k` ancestors block hashes of a block with the given hash.
|
||||
/// The response channel may return a `Vec` of size up to `k`
|
||||
/// filled with ancestors hashes with the following order:
|
||||
/// `parent`, `grandparent`, ...
|
||||
Ancestors {
|
||||
/// The hash of the block in question.
|
||||
hash: Hash,
|
||||
/// The number of ancestors to request.
|
||||
k: usize,
|
||||
/// The response channel.
|
||||
response_channel: ChainApiResponseChannel<Vec<Hash>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl ChainApiMessage {
|
||||
/// If the current variant contains the relay parent hash, return it.
|
||||
pub fn relay_parent(&self) -> Option<Hash> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Chain selection subsystem messages
|
||||
#[derive(Debug)]
|
||||
pub enum ChainSelectionMessage {
|
||||
/// Signal to the chain selection subsystem that a specific block has been approved.
|
||||
Approved(Hash),
|
||||
/// Request the leaves in descending order by score.
|
||||
Leaves(oneshot::Sender<Vec<Hash>>),
|
||||
/// Request the best leaf containing the given block in its ancestry. Return `None` if
|
||||
/// there is no such leaf.
|
||||
BestLeafContaining(Hash, oneshot::Sender<Option<Hash>>),
|
||||
}
|
||||
|
||||
impl ChainSelectionMessage {
|
||||
/// If the current variant contains the relay parent hash, return it.
|
||||
pub fn relay_parent(&self) -> Option<Hash> {
|
||||
// None of the messages, even the ones containing specific
|
||||
// block hashes, can be considered to have those blocks as
|
||||
// a relay parent.
|
||||
match *self {
|
||||
ChainSelectionMessage::Approved(_) => None,
|
||||
ChainSelectionMessage::Leaves(_) => None,
|
||||
ChainSelectionMessage::BestLeafContaining(..) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A sender for the result of a runtime API request.
|
||||
pub type RuntimeApiSender<T> = oneshot::Sender<Result<T, crate::errors::RuntimeApiError>>;
|
||||
|
||||
/// A request to the Runtime API subsystem.
|
||||
#[derive(Debug)]
|
||||
pub enum RuntimeApiRequest {
|
||||
/// Get the next, current and some previous authority discovery set deduplicated.
|
||||
Authorities(RuntimeApiSender<Vec<AuthorityDiscoveryId>>),
|
||||
/// Get the current validator set.
|
||||
Validators(RuntimeApiSender<Vec<ValidatorId>>),
|
||||
/// Get the validator groups and group rotation info.
|
||||
ValidatorGroups(RuntimeApiSender<(Vec<Vec<ValidatorIndex>>, GroupRotationInfo)>),
|
||||
/// Get information on all availability cores.
|
||||
AvailabilityCores(RuntimeApiSender<Vec<CoreState>>),
|
||||
/// Get the persisted validation data for a particular para, taking the given
|
||||
/// `OccupiedCoreAssumption`, which will inform on how the validation data should be computed
|
||||
/// if the para currently occupies a core.
|
||||
PersistedValidationData(
|
||||
ParaId,
|
||||
OccupiedCoreAssumption,
|
||||
RuntimeApiSender<Option<PersistedValidationData>>,
|
||||
),
|
||||
/// Sends back `true` if the validation outputs pass all acceptance criteria checks.
|
||||
CheckValidationOutputs(
|
||||
ParaId,
|
||||
polkadot_primitives::v1::CandidateCommitments,
|
||||
RuntimeApiSender<bool>,
|
||||
),
|
||||
/// Get the session index that a child of the block will have.
|
||||
SessionIndexForChild(RuntimeApiSender<SessionIndex>),
|
||||
/// Get the validation code for a para, taking the given `OccupiedCoreAssumption`, which
|
||||
/// will inform on how the validation data should be computed if the para currently
|
||||
/// occupies a core.
|
||||
ValidationCode(
|
||||
ParaId,
|
||||
OccupiedCoreAssumption,
|
||||
RuntimeApiSender<Option<ValidationCode>>,
|
||||
),
|
||||
/// Get validation code by its hash, either past, current or future code can be returned, as long as state is still
|
||||
/// available.
|
||||
ValidationCodeByHash(ValidationCodeHash, RuntimeApiSender<Option<ValidationCode>>),
|
||||
/// Get a the candidate pending availability for a particular parachain by parachain / core index
|
||||
CandidatePendingAvailability(ParaId, RuntimeApiSender<Option<CommittedCandidateReceipt>>),
|
||||
/// Get all events concerning candidates (backing, inclusion, time-out) in the parent of
|
||||
/// the block in whose state this request is executed.
|
||||
CandidateEvents(RuntimeApiSender<Vec<CandidateEvent>>),
|
||||
/// Get the session info for the given session, if stored.
|
||||
SessionInfo(SessionIndex, RuntimeApiSender<Option<SessionInfo>>),
|
||||
/// Get all the pending inbound messages in the downward message queue for a para.
|
||||
DmqContents(
|
||||
ParaId,
|
||||
RuntimeApiSender<Vec<InboundDownwardMessage<BlockNumber>>>,
|
||||
),
|
||||
/// Get the contents of all channels addressed to the given recipient. Channels that have no
|
||||
/// messages in them are also included.
|
||||
InboundHrmpChannelsContents(
|
||||
ParaId,
|
||||
RuntimeApiSender<BTreeMap<ParaId, Vec<InboundHrmpMessage<BlockNumber>>>>,
|
||||
),
|
||||
/// Get information about the BABE epoch the block was included in.
|
||||
CurrentBabeEpoch(RuntimeApiSender<BabeEpoch>),
|
||||
}
|
||||
|
||||
/// A message to the Runtime API subsystem.
|
||||
#[derive(Debug)]
|
||||
pub enum RuntimeApiMessage {
|
||||
/// Make a request of the runtime API against the post-state of the given relay-parent.
|
||||
Request(Hash, RuntimeApiRequest),
|
||||
}
|
||||
|
||||
impl RuntimeApiMessage {
|
||||
/// If the current variant contains the relay parent hash, return it.
|
||||
pub fn relay_parent(&self) -> Option<Hash> {
|
||||
match self {
|
||||
Self::Request(hash, _) => Some(*hash),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Statement distribution message.
|
||||
#[derive(Debug, derive_more::From)]
|
||||
pub enum StatementDistributionMessage {
|
||||
/// We have originated a signed statement in the context of
|
||||
/// given relay-parent hash and it should be distributed to other validators.
|
||||
Share(Hash, SignedFullStatement),
|
||||
/// 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.
|
||||
// It needs to be cloneable because multiple potential block authors can request copies.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ProvisionableData {
|
||||
/// This bitfield indicates the availability of various candidate blocks.
|
||||
Bitfield(Hash, SignedAvailabilityBitfield),
|
||||
/// The Candidate Backing subsystem believes that this candidate is valid, pending availability.
|
||||
BackedCandidate(CandidateReceipt),
|
||||
/// Misbehavior reports are self-contained proofs of validator misbehavior.
|
||||
MisbehaviorReport(Hash, ValidatorIndex, Misbehavior),
|
||||
/// Disputes trigger a broad dispute resolution process.
|
||||
Dispute(Hash, ValidatorSignature),
|
||||
}
|
||||
|
||||
/// Inherent data returned by the provisioner
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProvisionerInherentData {
|
||||
/// Signed bitfields.
|
||||
pub bitfields: SignedAvailabilityBitfields,
|
||||
/// Backed candidates.
|
||||
pub backed_candidates: Vec<BackedCandidate>,
|
||||
/// Dispute statement sets.
|
||||
pub disputes: MultiDisputeStatementSet,
|
||||
}
|
||||
|
||||
/// Message to the Provisioner.
|
||||
///
|
||||
/// In all cases, the Hash is that of the relay parent.
|
||||
#[derive(Debug)]
|
||||
pub enum ProvisionerMessage {
|
||||
/// This message allows external subsystems to request the set of bitfields and backed candidates
|
||||
/// associated with a particular potential block hash.
|
||||
///
|
||||
/// This is expected to be used by a proposer, to inject that information into the InherentData
|
||||
/// where it can be assembled into the ParaInherent.
|
||||
RequestInherentData(Hash, oneshot::Sender<ProvisionerInherentData>),
|
||||
/// This data should become part of a relay chain block
|
||||
ProvisionableData(Hash, ProvisionableData),
|
||||
}
|
||||
|
||||
impl BoundToRelayParent for ProvisionerMessage {
|
||||
fn relay_parent(&self) -> Hash {
|
||||
match self {
|
||||
Self::RequestInherentData(hash, _) => *hash,
|
||||
Self::ProvisionableData(hash, _) => *hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Message to the Collation Generation subsystem.
|
||||
#[derive(Debug)]
|
||||
pub enum CollationGenerationMessage {
|
||||
/// Initialize the collation generation subsystem
|
||||
Initialize(CollationGenerationConfig),
|
||||
}
|
||||
|
||||
impl CollationGenerationMessage {
|
||||
/// If the current variant contains the relay parent hash, return it.
|
||||
pub fn relay_parent(&self) -> Option<Hash> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// The result type of [`ApprovalVotingMessage::CheckAndImportAssignment`] request.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AssignmentCheckResult {
|
||||
/// The vote was accepted and should be propagated onwards.
|
||||
Accepted,
|
||||
/// The vote was valid but duplicate and should not be propagated onwards.
|
||||
AcceptedDuplicate,
|
||||
/// The vote was valid but too far in the future to accept right now.
|
||||
TooFarInFuture,
|
||||
/// The vote was bad and should be ignored, reporting the peer who propagated it.
|
||||
Bad(AssignmentCheckError),
|
||||
}
|
||||
|
||||
/// The error result type of [`ApprovalVotingMessage::CheckAndImportAssignment`] request.
|
||||
#[derive(Error, Debug, Clone, PartialEq, Eq)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum AssignmentCheckError {
|
||||
#[error("Unknown block: {0:?}")]
|
||||
UnknownBlock(Hash),
|
||||
#[error("Unknown session index: {0}")]
|
||||
UnknownSessionIndex(SessionIndex),
|
||||
#[error("Invalid candidate index: {0}")]
|
||||
InvalidCandidateIndex(CandidateIndex),
|
||||
#[error("Invalid candidate {0}: {1:?}")]
|
||||
InvalidCandidate(CandidateIndex, CandidateHash),
|
||||
#[error("Invalid cert: {0:?}")]
|
||||
InvalidCert(ValidatorIndex),
|
||||
#[error("Internal state mismatch: {0:?}, {1:?}")]
|
||||
Internal(Hash, CandidateHash),
|
||||
}
|
||||
|
||||
/// The result type of [`ApprovalVotingMessage::CheckAndImportApproval`] request.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ApprovalCheckResult {
|
||||
/// The vote was accepted and should be propagated onwards.
|
||||
Accepted,
|
||||
/// The vote was bad and should be ignored, reporting the peer who propagated it.
|
||||
Bad(ApprovalCheckError)
|
||||
}
|
||||
|
||||
/// The error result type of [`ApprovalVotingMessage::CheckAndImportApproval`] request.
|
||||
#[derive(Error, Debug, Clone, PartialEq, Eq)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum ApprovalCheckError {
|
||||
#[error("Unknown block: {0:?}")]
|
||||
UnknownBlock(Hash),
|
||||
#[error("Unknown session index: {0}")]
|
||||
UnknownSessionIndex(SessionIndex),
|
||||
#[error("Invalid candidate index: {0}")]
|
||||
InvalidCandidateIndex(CandidateIndex),
|
||||
#[error("Invalid validator index: {0:?}")]
|
||||
InvalidValidatorIndex(ValidatorIndex),
|
||||
#[error("Invalid candidate {0}: {1:?}")]
|
||||
InvalidCandidate(CandidateIndex, CandidateHash),
|
||||
#[error("Invalid signature: {0:?}")]
|
||||
InvalidSignature(ValidatorIndex),
|
||||
#[error("No assignment for {0:?}")]
|
||||
NoAssignment(ValidatorIndex),
|
||||
#[error("Internal state mismatch: {0:?}, {1:?}")]
|
||||
Internal(Hash, CandidateHash),
|
||||
}
|
||||
|
||||
/// Message to the Approval Voting subsystem.
|
||||
#[derive(Debug)]
|
||||
pub enum ApprovalVotingMessage {
|
||||
/// Check if the assignment is valid and can be accepted by our view of the protocol.
|
||||
/// Should not be sent unless the block hash is known.
|
||||
CheckAndImportAssignment(
|
||||
IndirectAssignmentCert,
|
||||
CandidateIndex,
|
||||
oneshot::Sender<AssignmentCheckResult>,
|
||||
),
|
||||
/// Check if the approval vote is valid and can be accepted by our view of the
|
||||
/// protocol.
|
||||
///
|
||||
/// Should not be sent unless the block hash within the indirect vote is known.
|
||||
CheckAndImportApproval(
|
||||
IndirectSignedApprovalVote,
|
||||
oneshot::Sender<ApprovalCheckResult>,
|
||||
),
|
||||
/// Returns the highest possible ancestor hash of the provided block hash which is
|
||||
/// acceptable to vote on finality for.
|
||||
/// The `BlockNumber` provided is the number of the block's ancestor which is the
|
||||
/// earliest possible vote.
|
||||
///
|
||||
/// It can also return the same block hash, if that is acceptable to vote upon.
|
||||
/// Return `None` if the input hash is unrecognized.
|
||||
ApprovedAncestor(Hash, BlockNumber, oneshot::Sender<Option<(Hash, BlockNumber)>>),
|
||||
}
|
||||
|
||||
/// Message to the Approval Distribution subsystem.
|
||||
#[derive(Debug, derive_more::From)]
|
||||
pub enum ApprovalDistributionMessage {
|
||||
/// Notify the `ApprovalDistribution` subsystem about new blocks
|
||||
/// and the candidates contained within them.
|
||||
NewBlocks(Vec<BlockApprovalMeta>),
|
||||
/// Distribute an assignment cert from the local validator. The cert is assumed
|
||||
/// to be valid, relevant, and for the given relay-parent and validator index.
|
||||
DistributeAssignment(IndirectAssignmentCert, CandidateIndex),
|
||||
/// Distribute an approval vote for the local validator. The approval vote is assumed to be
|
||||
/// valid, relevant, and the corresponding approval already issued.
|
||||
/// If not, the subsystem is free to drop the message.
|
||||
DistributeApproval(IndirectSignedApprovalVote),
|
||||
/// An update from the network bridge.
|
||||
#[from]
|
||||
NetworkBridgeUpdateV1(NetworkBridgeEvent<protocol_v1::ApprovalDistributionMessage>),
|
||||
}
|
||||
|
||||
/// Message to the Gossip Support subsystem.
|
||||
#[derive(Debug)]
|
||||
pub enum GossipSupportMessage {
|
||||
}
|
||||
|
||||
/// A message type tying together all message types that are used across Subsystems.
|
||||
#[subsystem_dispatch_gen(NetworkBridgeEvent<protocol_v1::ValidationProtocol>)]
|
||||
#[derive(Debug, derive_more::From)]
|
||||
pub enum AllMessages {
|
||||
/// Message for the validation subsystem.
|
||||
#[skip]
|
||||
CandidateValidation(CandidateValidationMessage),
|
||||
/// Message for the candidate backing subsystem.
|
||||
#[skip]
|
||||
CandidateBacking(CandidateBackingMessage),
|
||||
/// Message for the Chain API subsystem.
|
||||
#[skip]
|
||||
ChainApi(ChainApiMessage),
|
||||
/// Message for the Collator Protocol subsystem.
|
||||
#[skip]
|
||||
CollatorProtocol(CollatorProtocolMessage),
|
||||
/// Message for the statement distribution subsystem.
|
||||
StatementDistribution(StatementDistributionMessage),
|
||||
/// Message for the availability distribution subsystem.
|
||||
#[skip]
|
||||
AvailabilityDistribution(AvailabilityDistributionMessage),
|
||||
/// Message for the availability recovery subsystem.
|
||||
#[skip]
|
||||
AvailabilityRecovery(AvailabilityRecoveryMessage),
|
||||
/// Message for the bitfield distribution subsystem.
|
||||
BitfieldDistribution(BitfieldDistributionMessage),
|
||||
/// Message for the bitfield signing subsystem.
|
||||
#[skip]
|
||||
BitfieldSigning(BitfieldSigningMessage),
|
||||
/// Message for the Provisioner subsystem.
|
||||
#[skip]
|
||||
Provisioner(ProvisionerMessage),
|
||||
/// Message for the Runtime API subsystem.
|
||||
#[skip]
|
||||
RuntimeApi(RuntimeApiMessage),
|
||||
/// Message for the availability store subsystem.
|
||||
#[skip]
|
||||
AvailabilityStore(AvailabilityStoreMessage),
|
||||
/// Message for the network bridge subsystem.
|
||||
#[skip]
|
||||
NetworkBridge(NetworkBridgeMessage),
|
||||
/// Message for the Collation Generation subsystem.
|
||||
#[skip]
|
||||
CollationGeneration(CollationGenerationMessage),
|
||||
/// Message for the Approval Voting subsystem.
|
||||
#[skip]
|
||||
ApprovalVoting(ApprovalVotingMessage),
|
||||
/// Message for the Approval Distribution subsystem.
|
||||
ApprovalDistribution(ApprovalDistributionMessage),
|
||||
/// Message for the Gossip Support subsystem.
|
||||
#[skip]
|
||||
GossipSupport(GossipSupportMessage),
|
||||
/// Message for the dispute coordinator subsystem.
|
||||
#[skip]
|
||||
DisputeCoordinator(DisputeCoordinatorMessage),
|
||||
/// Message for the dispute participation subsystem.
|
||||
#[skip]
|
||||
DisputeParticipation(DisputeParticipationMessage),
|
||||
/// Message for the chain selection subsystem.
|
||||
#[skip]
|
||||
ChainSelection(ChainSelectionMessage),
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
// Copyright 2017-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::convert::TryFrom;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub use sc_network::{ReputationChange, PeerId};
|
||||
|
||||
use polkadot_node_network_protocol::{WrongVariant, ObservedRole, OurView, View};
|
||||
use polkadot_primitives::v1::AuthorityDiscoveryId;
|
||||
|
||||
/// Events from network.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum NetworkBridgeEvent<M> {
|
||||
/// A peer has connected.
|
||||
PeerConnected(PeerId, ObservedRole, Option<AuthorityDiscoveryId>),
|
||||
|
||||
/// A peer has disconnected.
|
||||
PeerDisconnected(PeerId),
|
||||
|
||||
/// Our neighbors in the new gossip topology.
|
||||
/// We're not necessarily connected to all of them.
|
||||
///
|
||||
/// This message is issued only on the validation peer set.
|
||||
///
|
||||
/// Note, that the distribution subsystems need to handle the last
|
||||
/// view update of the newly added gossip peers manually.
|
||||
NewGossipTopology(HashSet<PeerId>),
|
||||
|
||||
/// Peer has sent a message.
|
||||
PeerMessage(PeerId, M),
|
||||
|
||||
/// Peer's `View` has changed.
|
||||
PeerViewChange(PeerId, View),
|
||||
|
||||
/// Our view has changed.
|
||||
OurViewChange(OurView),
|
||||
}
|
||||
|
||||
impl<M> NetworkBridgeEvent<M> {
|
||||
/// Focus an overarching network-bridge event into some more specific variant.
|
||||
///
|
||||
/// This tries to transform M in `PeerMessage` to a message type specific to a subsystem.
|
||||
/// It is used to dispatch events coming from a peer set to the various subsystems that are
|
||||
/// handled within that peer set. More concretely a `ValidationProtocol` will be transformed
|
||||
/// for example into a `BitfieldDistributionMessage` in case of the `BitfieldDistribution`
|
||||
/// constructor.
|
||||
///
|
||||
/// Therefore a NetworkBridgeEvent<ValidationProtocol> will become for example a
|
||||
/// NetworkBridgeEvent<BitfieldDistributionMessage>, with the more specific message type
|
||||
/// `BitfieldDistributionMessage`.
|
||||
///
|
||||
/// This acts as a call to `clone`, except in the case where the event is a message event,
|
||||
/// in which case the clone can be expensive and it only clones if the message type can
|
||||
/// be focused.
|
||||
pub fn focus<'a, T>(&'a self) -> Result<NetworkBridgeEvent<T>, WrongVariant>
|
||||
where T: 'a + Clone, &'a T: TryFrom<&'a M, Error = WrongVariant>
|
||||
{
|
||||
Ok(match *self {
|
||||
NetworkBridgeEvent::PeerConnected(ref peer, ref role, ref authority_id)
|
||||
=> NetworkBridgeEvent::PeerConnected(peer.clone(), role.clone(), authority_id.clone()),
|
||||
NetworkBridgeEvent::PeerDisconnected(ref peer)
|
||||
=> NetworkBridgeEvent::PeerDisconnected(peer.clone()),
|
||||
NetworkBridgeEvent::NewGossipTopology(ref peers)
|
||||
=> NetworkBridgeEvent::NewGossipTopology(peers.clone()),
|
||||
NetworkBridgeEvent::PeerMessage(ref peer, ref msg)
|
||||
=> NetworkBridgeEvent::PeerMessage(peer.clone(), <&'a T>::try_from(msg)?.clone()),
|
||||
NetworkBridgeEvent::PeerViewChange(ref peer, ref view)
|
||||
=> NetworkBridgeEvent::PeerViewChange(peer.clone(), view.clone()),
|
||||
NetworkBridgeEvent::OurViewChange(ref view)
|
||||
=> NetworkBridgeEvent::OurViewChange(view.clone()),
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user