feat: initialize Kurdistan SDK - independent fork of Polkadot SDK
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
[package]
|
||||
name = "pezkuwi-node-network-protocol"
|
||||
version = "7.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
description = "Primitives types for the Node-side"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-channel = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
bitvec = { workspace = true, default-features = true }
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
derive_more = { workspace = true, default-features = true }
|
||||
fatality = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
gum = { workspace = true, default-features = true }
|
||||
hex = { workspace = true, default-features = true }
|
||||
pezkuwi-node-primitives = { workspace = true, default-features = true }
|
||||
pezkuwi-primitives = { workspace = true, default-features = true }
|
||||
rand = { workspace = true, default-features = true }
|
||||
sc-authority-discovery = { workspace = true, default-features = true }
|
||||
sc-network = { workspace = true, default-features = true }
|
||||
sc-network-types = { workspace = true, default-features = true }
|
||||
sp-runtime = { workspace = true, default-features = true }
|
||||
strum = { features = ["derive"], workspace = true, default-features = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rand_chacha = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
runtime-benchmarks = [
|
||||
"gum/runtime-benchmarks",
|
||||
"pezkuwi-node-primitives/runtime-benchmarks",
|
||||
"pezkuwi-primitives/runtime-benchmarks",
|
||||
"sc-authority-discovery/runtime-benchmarks",
|
||||
"sc-network/runtime-benchmarks",
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi 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.
|
||||
|
||||
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Authority discovery service interfacing.
|
||||
|
||||
use std::{collections::HashSet, fmt::Debug};
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use sc_authority_discovery::Service as AuthorityDiscoveryService;
|
||||
|
||||
use pezkuwi_primitives::AuthorityDiscoveryId;
|
||||
use sc_network::Multiaddr;
|
||||
use sc_network_types::PeerId;
|
||||
|
||||
/// An abstraction over the authority discovery service.
|
||||
///
|
||||
/// Needed for mocking in tests mostly.
|
||||
#[async_trait]
|
||||
pub trait AuthorityDiscovery: Send + Debug + 'static {
|
||||
/// Get the addresses for the given [`AuthorityDiscoveryId`] from the local address cache.
|
||||
async fn get_addresses_by_authority_id(
|
||||
&mut self,
|
||||
authority: AuthorityDiscoveryId,
|
||||
) -> Option<HashSet<Multiaddr>>;
|
||||
/// Get the [`AuthorityDiscoveryId`] for the given [`PeerId`] from the local address cache.
|
||||
async fn get_authority_ids_by_peer_id(
|
||||
&mut self,
|
||||
peer_id: PeerId,
|
||||
) -> Option<HashSet<AuthorityDiscoveryId>>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AuthorityDiscovery for AuthorityDiscoveryService {
|
||||
async fn get_addresses_by_authority_id(
|
||||
&mut self,
|
||||
authority: AuthorityDiscoveryId,
|
||||
) -> Option<HashSet<Multiaddr>> {
|
||||
AuthorityDiscoveryService::get_addresses_by_authority_id(self, authority).await
|
||||
}
|
||||
|
||||
async fn get_authority_ids_by_peer_id(
|
||||
&mut self,
|
||||
peer_id: PeerId,
|
||||
) -> Option<HashSet<AuthorityDiscoveryId>> {
|
||||
AuthorityDiscoveryService::get_authority_ids_by_peer_id(self, peer_id).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,738 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi 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.
|
||||
|
||||
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Grid topology support implementation
|
||||
//! The basic operation of the 2D grid topology is that:
|
||||
//! * A validator producing a message sends it to its row-neighbors and its column-neighbors
|
||||
//! * A validator receiving a message originating from one of its row-neighbors sends it to its
|
||||
//! column-neighbors
|
||||
//! * A validator receiving a message originating from one of its column-neighbors sends it to its
|
||||
//! row-neighbors
|
||||
//!
|
||||
//! This grid approach defines 2 unique paths for every validator to reach every other validator in
|
||||
//! at most 2 hops.
|
||||
//!
|
||||
//! However, we also supplement this with some degree of random propagation:
|
||||
//! every validator, upon seeing a message for the first time, propagates it to 8 random peers.
|
||||
//! This inserts some redundancy in case the grid topology isn't working or is being attacked -
|
||||
//! an adversary doesn't know which peers a validator will send to.
|
||||
//! This is combined with the property that the adversary doesn't know which validators will elect
|
||||
//! to check a block.
|
||||
|
||||
use crate::PeerId;
|
||||
use pezkuwi_primitives::{AuthorityDiscoveryId, SessionIndex, ValidatorIndex};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::{
|
||||
collections::{hash_map, HashMap, HashSet},
|
||||
fmt::Debug,
|
||||
};
|
||||
|
||||
const LOG_TARGET: &str = "teyrchain::grid-topology";
|
||||
|
||||
/// The sample rate for randomly propagating messages. This
|
||||
/// reduces the left tail of the binomial distribution but also
|
||||
/// introduces a bias towards peers who we sample before others
|
||||
/// (i.e. those who get a block before others).
|
||||
pub const DEFAULT_RANDOM_SAMPLE_RATE: usize = crate::MIN_GOSSIP_PEERS;
|
||||
|
||||
/// The number of peers to randomly propagate messages to.
|
||||
pub const DEFAULT_RANDOM_CIRCULATION: usize = 4;
|
||||
|
||||
/// Information about a peer in the gossip topology for a session.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct TopologyPeerInfo {
|
||||
/// The validator's known peer IDs.
|
||||
pub peer_ids: Vec<PeerId>,
|
||||
/// The index of the validator in the discovery keys of the corresponding
|
||||
/// `SessionInfo`. This can extend _beyond_ the set of active teyrchain validators.
|
||||
pub validator_index: ValidatorIndex,
|
||||
/// The authority discovery public key of the validator in the corresponding
|
||||
/// `SessionInfo`.
|
||||
pub discovery_id: AuthorityDiscoveryId,
|
||||
}
|
||||
|
||||
/// Topology representation for a session.
|
||||
#[derive(Default, Clone, Debug, PartialEq)]
|
||||
pub struct SessionGridTopology {
|
||||
/// An array mapping validator indices to their indices in the
|
||||
/// shuffling itself. This has the same size as the number of validators
|
||||
/// in the session.
|
||||
shuffled_indices: Vec<usize>,
|
||||
/// The canonical shuffling of validators for the session.
|
||||
canonical_shuffling: Vec<TopologyPeerInfo>,
|
||||
/// The list of peer-ids in an efficient way to search.
|
||||
peer_ids: HashSet<PeerId>,
|
||||
}
|
||||
|
||||
impl SessionGridTopology {
|
||||
/// Create a new session grid topology.
|
||||
pub fn new(shuffled_indices: Vec<usize>, canonical_shuffling: Vec<TopologyPeerInfo>) -> Self {
|
||||
let mut peer_ids = HashSet::new();
|
||||
for peer_info in canonical_shuffling.iter() {
|
||||
for peer_id in peer_info.peer_ids.iter() {
|
||||
peer_ids.insert(*peer_id);
|
||||
}
|
||||
}
|
||||
SessionGridTopology { shuffled_indices, canonical_shuffling, peer_ids }
|
||||
}
|
||||
|
||||
/// Updates the known peer ids for the passed authorities ids.
|
||||
pub fn update_authority_ids(
|
||||
&mut self,
|
||||
peer_id: PeerId,
|
||||
ids: &HashSet<AuthorityDiscoveryId>,
|
||||
) -> bool {
|
||||
let mut updated = false;
|
||||
if !self.peer_ids.contains(&peer_id) {
|
||||
for peer in self
|
||||
.canonical_shuffling
|
||||
.iter_mut()
|
||||
.filter(|peer| ids.contains(&peer.discovery_id))
|
||||
{
|
||||
peer.peer_ids.push(peer_id);
|
||||
self.peer_ids.insert(peer_id);
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
updated
|
||||
}
|
||||
/// Produces the outgoing routing logic for a particular peer.
|
||||
///
|
||||
/// Returns `None` if the validator index is out of bounds.
|
||||
pub fn compute_grid_neighbors_for(&self, v: ValidatorIndex) -> Option<GridNeighbors> {
|
||||
if self.shuffled_indices.len() != self.canonical_shuffling.len() {
|
||||
return None;
|
||||
}
|
||||
let shuffled_val_index = *self.shuffled_indices.get(v.0 as usize)?;
|
||||
|
||||
let neighbors = matrix_neighbors(shuffled_val_index, self.shuffled_indices.len())?;
|
||||
|
||||
let mut grid_subset = GridNeighbors::empty();
|
||||
for r_n in neighbors.row_neighbors {
|
||||
let n = &self.canonical_shuffling[r_n];
|
||||
grid_subset.validator_indices_x.insert(n.validator_index);
|
||||
for p in &n.peer_ids {
|
||||
grid_subset.peers_x.insert(*p);
|
||||
}
|
||||
}
|
||||
|
||||
for c_n in neighbors.column_neighbors {
|
||||
let n = &self.canonical_shuffling[c_n];
|
||||
grid_subset.validator_indices_y.insert(n.validator_index);
|
||||
for p in &n.peer_ids {
|
||||
grid_subset.peers_y.insert(*p);
|
||||
}
|
||||
}
|
||||
|
||||
Some(grid_subset)
|
||||
}
|
||||
|
||||
/// Tells if a given peer id is validator in a session
|
||||
pub fn is_validator(&self, peer: &PeerId) -> bool {
|
||||
self.peer_ids.contains(peer)
|
||||
}
|
||||
}
|
||||
|
||||
struct MatrixNeighbors<R, C> {
|
||||
row_neighbors: R,
|
||||
column_neighbors: C,
|
||||
}
|
||||
|
||||
/// Compute the row and column neighbors of `val_index` in a matrix
|
||||
fn matrix_neighbors(
|
||||
val_index: usize,
|
||||
len: usize,
|
||||
) -> Option<MatrixNeighbors<impl Iterator<Item = usize>, impl Iterator<Item = usize>>> {
|
||||
if val_index >= len {
|
||||
return None;
|
||||
}
|
||||
|
||||
// e.g. for size 11 the matrix would be
|
||||
//
|
||||
// 0 1 2
|
||||
// 3 4 5
|
||||
// 6 7 8
|
||||
// 9 10
|
||||
//
|
||||
// and for index 10, the neighbors would be 1, 4, 7, 9
|
||||
|
||||
let sqrt = (len as f64).sqrt() as usize;
|
||||
let our_row = val_index / sqrt;
|
||||
let our_column = val_index % sqrt;
|
||||
let row_neighbors = our_row * sqrt..std::cmp::min(our_row * sqrt + sqrt, len);
|
||||
let column_neighbors = (our_column..len).step_by(sqrt);
|
||||
|
||||
Some(MatrixNeighbors {
|
||||
row_neighbors: row_neighbors.filter(move |i| *i != val_index),
|
||||
column_neighbors: column_neighbors.filter(move |i| *i != val_index),
|
||||
})
|
||||
}
|
||||
|
||||
/// Information about the grid neighbors for a particular node in the topology.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct GridNeighbors {
|
||||
/// Represent peers in the X axis
|
||||
pub peers_x: HashSet<PeerId>,
|
||||
/// Represent validators in the X axis
|
||||
pub validator_indices_x: HashSet<ValidatorIndex>,
|
||||
/// Represent peers in the Y axis
|
||||
pub peers_y: HashSet<PeerId>,
|
||||
/// Represent validators in the Y axis
|
||||
pub validator_indices_y: HashSet<ValidatorIndex>,
|
||||
}
|
||||
|
||||
impl GridNeighbors {
|
||||
/// Utility function for creating an empty set of grid neighbors.
|
||||
/// Useful for testing.
|
||||
pub fn empty() -> Self {
|
||||
GridNeighbors {
|
||||
peers_x: HashSet::new(),
|
||||
validator_indices_x: HashSet::new(),
|
||||
peers_y: HashSet::new(),
|
||||
validator_indices_y: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Given the originator of a message as a validator index, indicates the part of the topology
|
||||
/// we're meant to send the message to.
|
||||
pub fn required_routing_by_index(
|
||||
&self,
|
||||
originator: ValidatorIndex,
|
||||
local: bool,
|
||||
) -> RequiredRouting {
|
||||
if local {
|
||||
return RequiredRouting::GridXY;
|
||||
}
|
||||
|
||||
let grid_x = self.validator_indices_x.contains(&originator);
|
||||
let grid_y = self.validator_indices_y.contains(&originator);
|
||||
|
||||
match (grid_x, grid_y) {
|
||||
(false, false) => RequiredRouting::None,
|
||||
(true, false) => RequiredRouting::GridY, // messages from X go to Y
|
||||
(false, true) => RequiredRouting::GridX, // messages from Y go to X
|
||||
(true, true) => RequiredRouting::GridXY, /* if the grid works as expected, this
|
||||
* shouldn't happen. */
|
||||
}
|
||||
}
|
||||
|
||||
/// Given the originator of a message as a peer index, indicates the part of the topology
|
||||
/// we're meant to send the message to.
|
||||
pub fn required_routing_by_peer_id(&self, originator: PeerId, local: bool) -> RequiredRouting {
|
||||
if local {
|
||||
return RequiredRouting::GridXY;
|
||||
}
|
||||
|
||||
let grid_x = self.peers_x.contains(&originator);
|
||||
let grid_y = self.peers_y.contains(&originator);
|
||||
|
||||
match (grid_x, grid_y) {
|
||||
(false, false) => RequiredRouting::None,
|
||||
(true, false) => RequiredRouting::GridY, // messages from X go to Y
|
||||
(false, true) => RequiredRouting::GridX, // messages from Y go to X
|
||||
(true, true) => {
|
||||
gum::debug!(
|
||||
target: LOG_TARGET,
|
||||
?originator,
|
||||
"Grid topology is unexpected, play it safe and send to X AND Y"
|
||||
);
|
||||
RequiredRouting::GridXY
|
||||
}, /* if the grid works as expected, this
|
||||
* shouldn't happen. */
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a filter function based on this topology and the required routing
|
||||
/// which returns `true` for peers that are within the required routing set
|
||||
/// and false otherwise.
|
||||
pub fn route_to_peer(&self, required_routing: RequiredRouting, peer: &PeerId) -> bool {
|
||||
match required_routing {
|
||||
RequiredRouting::All => true,
|
||||
RequiredRouting::GridX => self.peers_x.contains(peer),
|
||||
RequiredRouting::GridY => self.peers_y.contains(peer),
|
||||
RequiredRouting::GridXY => self.peers_x.contains(peer) || self.peers_y.contains(peer),
|
||||
RequiredRouting::None | RequiredRouting::PendingTopology => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the difference between this and the `other` topology as a vector of peers
|
||||
pub fn peers_diff(&self, other: &Self) -> Vec<PeerId> {
|
||||
self.peers_x
|
||||
.iter()
|
||||
.chain(self.peers_y.iter())
|
||||
.filter(|peer_id| !(other.peers_x.contains(peer_id) || other.peers_y.contains(peer_id)))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
/// A convenience method that returns total number of peers in the topology
|
||||
pub fn len(&self) -> usize {
|
||||
self.peers_x.len().saturating_add(self.peers_y.len())
|
||||
}
|
||||
}
|
||||
|
||||
/// An entry tracking a session grid topology and some cached local neighbors.
|
||||
#[derive(Debug)]
|
||||
pub struct SessionGridTopologyEntry {
|
||||
topology: SessionGridTopology,
|
||||
local_neighbors: GridNeighbors,
|
||||
local_index: Option<ValidatorIndex>,
|
||||
}
|
||||
|
||||
impl SessionGridTopologyEntry {
|
||||
/// Access the local grid neighbors.
|
||||
pub fn local_grid_neighbors(&self) -> &GridNeighbors {
|
||||
&self.local_neighbors
|
||||
}
|
||||
|
||||
/// Access the local grid neighbors mutably.
|
||||
pub fn local_grid_neighbors_mut(&mut self) -> &mut GridNeighbors {
|
||||
&mut self.local_neighbors
|
||||
}
|
||||
|
||||
/// Access the underlying topology.
|
||||
pub fn get(&self) -> &SessionGridTopology {
|
||||
&self.topology
|
||||
}
|
||||
|
||||
/// Tells if a given peer id is validator in a session
|
||||
pub fn is_validator(&self, peer: &PeerId) -> bool {
|
||||
self.topology.is_validator(peer)
|
||||
}
|
||||
|
||||
/// Returns the list of peers to route based on the required routing.
|
||||
pub fn peers_to_route(&self, required_routing: RequiredRouting) -> Vec<PeerId> {
|
||||
match required_routing {
|
||||
RequiredRouting::All => self.topology.peer_ids.iter().copied().collect(),
|
||||
RequiredRouting::GridX => self.local_neighbors.peers_x.iter().copied().collect(),
|
||||
RequiredRouting::GridY => self.local_neighbors.peers_y.iter().copied().collect(),
|
||||
RequiredRouting::GridXY => self
|
||||
.local_neighbors
|
||||
.peers_x
|
||||
.iter()
|
||||
.chain(self.local_neighbors.peers_y.iter())
|
||||
.copied()
|
||||
.collect(),
|
||||
RequiredRouting::None | RequiredRouting::PendingTopology => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the known peer ids for the passed authorities ids.
|
||||
pub fn update_authority_ids(
|
||||
&mut self,
|
||||
peer_id: PeerId,
|
||||
ids: &HashSet<AuthorityDiscoveryId>,
|
||||
) -> bool {
|
||||
let peer_id_updated = self.topology.update_authority_ids(peer_id, ids);
|
||||
// If we added a new peer id we need to recompute the grid neighbors, so that
|
||||
// neighbors_x and neighbors_y reflect the right peer ids.
|
||||
if peer_id_updated {
|
||||
if let Some(local_index) = self.local_index.as_ref() {
|
||||
if let Some(new_grid) = self.topology.compute_grid_neighbors_for(*local_index) {
|
||||
self.local_neighbors = new_grid;
|
||||
}
|
||||
}
|
||||
}
|
||||
peer_id_updated
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of topologies indexed by session
|
||||
#[derive(Default)]
|
||||
pub struct SessionGridTopologies {
|
||||
inner: HashMap<SessionIndex, (Option<SessionGridTopologyEntry>, usize)>,
|
||||
}
|
||||
|
||||
impl SessionGridTopologies {
|
||||
/// Returns a topology for the specific session index
|
||||
pub fn get_topology(&self, session: SessionIndex) -> Option<&SessionGridTopologyEntry> {
|
||||
self.inner.get(&session).and_then(|val| val.0.as_ref())
|
||||
}
|
||||
|
||||
/// Updates the known peer ids for the passed authorities ids.
|
||||
pub fn update_authority_ids(
|
||||
&mut self,
|
||||
peer_id: PeerId,
|
||||
ids: &HashSet<AuthorityDiscoveryId>,
|
||||
) -> bool {
|
||||
self.inner
|
||||
.iter_mut()
|
||||
.map(|(_, topology)| {
|
||||
topology.0.as_mut().map(|topology| topology.update_authority_ids(peer_id, ids))
|
||||
})
|
||||
.any(|updated| updated.unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Increase references counter for a specific topology
|
||||
pub fn inc_session_refs(&mut self, session: SessionIndex) {
|
||||
self.inner.entry(session).or_insert((None, 0)).1 += 1;
|
||||
}
|
||||
|
||||
/// Decrease references counter for a specific topology
|
||||
pub fn dec_session_refs(&mut self, session: SessionIndex) {
|
||||
if let hash_map::Entry::Occupied(mut occupied) = self.inner.entry(session) {
|
||||
occupied.get_mut().1 = occupied.get().1.saturating_sub(1);
|
||||
if occupied.get().1 == 0 {
|
||||
let _ = occupied.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a new topology, no-op if already present.
|
||||
pub fn insert_topology(
|
||||
&mut self,
|
||||
session: SessionIndex,
|
||||
topology: SessionGridTopology,
|
||||
local_index: Option<ValidatorIndex>,
|
||||
) {
|
||||
let entry = self.inner.entry(session).or_insert((None, 0));
|
||||
if entry.0.is_none() {
|
||||
let local_neighbors = local_index
|
||||
.and_then(|l| topology.compute_grid_neighbors_for(l))
|
||||
.unwrap_or_else(GridNeighbors::empty);
|
||||
|
||||
entry.0 = Some(SessionGridTopologyEntry { topology, local_neighbors, local_index });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple storage for a topology and the corresponding session index
|
||||
#[derive(Debug)]
|
||||
struct GridTopologySessionBound {
|
||||
entry: SessionGridTopologyEntry,
|
||||
session_index: SessionIndex,
|
||||
}
|
||||
|
||||
/// A storage for the current and maybe previous topology
|
||||
#[derive(Debug)]
|
||||
pub struct SessionBoundGridTopologyStorage {
|
||||
current_topology: GridTopologySessionBound,
|
||||
prev_topology: Option<GridTopologySessionBound>,
|
||||
}
|
||||
|
||||
impl Default for SessionBoundGridTopologyStorage {
|
||||
fn default() -> Self {
|
||||
// having this struct be `Default` is objectively stupid
|
||||
// but used in a few places
|
||||
SessionBoundGridTopologyStorage {
|
||||
current_topology: GridTopologySessionBound {
|
||||
// session 0 is valid so we should use the upper bound
|
||||
// as the default instead of the lower bound.
|
||||
session_index: SessionIndex::max_value(),
|
||||
entry: SessionGridTopologyEntry {
|
||||
topology: SessionGridTopology {
|
||||
shuffled_indices: Vec::new(),
|
||||
canonical_shuffling: Vec::new(),
|
||||
peer_ids: Default::default(),
|
||||
},
|
||||
local_neighbors: GridNeighbors::empty(),
|
||||
local_index: None,
|
||||
},
|
||||
},
|
||||
prev_topology: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SessionBoundGridTopologyStorage {
|
||||
/// Return a grid topology based on the session index:
|
||||
/// If we need a previous session and it is registered in the storage, then return that session.
|
||||
/// Otherwise, return a current session to have some grid topology in any case
|
||||
pub fn get_topology_or_fallback(&self, idx: SessionIndex) -> &SessionGridTopologyEntry {
|
||||
self.get_topology(idx).unwrap_or(&self.current_topology.entry)
|
||||
}
|
||||
|
||||
/// Return the grid topology for the specific session index, if no such a session is stored
|
||||
/// returns `None`.
|
||||
pub fn get_topology(&self, idx: SessionIndex) -> Option<&SessionGridTopologyEntry> {
|
||||
if let Some(prev_topology) = &self.prev_topology {
|
||||
if idx == prev_topology.session_index {
|
||||
return Some(&prev_topology.entry);
|
||||
}
|
||||
}
|
||||
if self.current_topology.session_index == idx {
|
||||
return Some(&self.current_topology.entry);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Update the current topology preserving the previous one
|
||||
pub fn update_topology(
|
||||
&mut self,
|
||||
session_index: SessionIndex,
|
||||
topology: SessionGridTopology,
|
||||
local_index: Option<ValidatorIndex>,
|
||||
) {
|
||||
let local_neighbors = local_index
|
||||
.and_then(|l| topology.compute_grid_neighbors_for(l))
|
||||
.unwrap_or_else(GridNeighbors::empty);
|
||||
|
||||
let old_current = std::mem::replace(
|
||||
&mut self.current_topology,
|
||||
GridTopologySessionBound {
|
||||
entry: SessionGridTopologyEntry { topology, local_neighbors, local_index },
|
||||
session_index,
|
||||
},
|
||||
);
|
||||
self.prev_topology.replace(old_current);
|
||||
}
|
||||
|
||||
/// Returns a current grid topology
|
||||
pub fn get_current_topology(&self) -> &SessionGridTopologyEntry {
|
||||
&self.current_topology.entry
|
||||
}
|
||||
|
||||
/// Returns the current session index.
|
||||
pub fn get_current_session_index(&self) -> SessionIndex {
|
||||
self.current_topology.session_index
|
||||
}
|
||||
|
||||
/// Access the current grid topology mutably. Dangerous and intended
|
||||
/// to be used in tests.
|
||||
pub fn get_current_topology_mut(&mut self) -> &mut SessionGridTopologyEntry {
|
||||
&mut self.current_topology.entry
|
||||
}
|
||||
}
|
||||
|
||||
/// A representation of routing based on sample
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RandomRouting {
|
||||
/// The number of peers to target.
|
||||
target: usize,
|
||||
/// The number of peers this has been sent to.
|
||||
sent: usize,
|
||||
/// Sampling rate
|
||||
sample_rate: usize,
|
||||
}
|
||||
|
||||
impl Default for RandomRouting {
|
||||
fn default() -> Self {
|
||||
RandomRouting {
|
||||
target: DEFAULT_RANDOM_CIRCULATION,
|
||||
sent: 0_usize,
|
||||
sample_rate: DEFAULT_RANDOM_SAMPLE_RATE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RandomRouting {
|
||||
/// Perform random sampling for a specific peer
|
||||
/// Returns `true` for a lucky peer
|
||||
pub fn sample(&self, n_peers_total: usize, rng: &mut (impl CryptoRng + Rng)) -> bool {
|
||||
if n_peers_total == 0 || self.sent >= self.target {
|
||||
false
|
||||
} else if self.sample_rate > n_peers_total {
|
||||
true
|
||||
} else {
|
||||
rng.gen_ratio(self.sample_rate as _, n_peers_total as _)
|
||||
}
|
||||
}
|
||||
|
||||
/// Increase number of messages being sent
|
||||
pub fn inc_sent(&mut self) {
|
||||
self.sent += 1
|
||||
}
|
||||
|
||||
/// Returns `true` if we already took all the necessary samples.
|
||||
pub fn is_complete(&self) -> bool {
|
||||
self.sent >= self.target
|
||||
}
|
||||
}
|
||||
|
||||
/// Routing mode
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum RequiredRouting {
|
||||
/// We don't know yet, because we're waiting for topology info
|
||||
/// (race condition between learning about the first blocks in a new session
|
||||
/// and getting the topology for that session)
|
||||
PendingTopology,
|
||||
/// Propagate to all peers of any kind.
|
||||
All,
|
||||
/// Propagate to all peers sharing either the X or Y dimension of the grid.
|
||||
GridXY,
|
||||
/// Propagate to all peers sharing the X dimension of the grid.
|
||||
GridX,
|
||||
/// Propagate to all peers sharing the Y dimension of the grid.
|
||||
GridY,
|
||||
/// No required propagation.
|
||||
None,
|
||||
}
|
||||
|
||||
impl RequiredRouting {
|
||||
/// Whether the required routing set is definitely empty.
|
||||
pub fn is_empty(self) -> bool {
|
||||
match self {
|
||||
RequiredRouting::PendingTopology | RequiredRouting::None => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Combine two required routing sets into one that would cover both routing modes.
|
||||
pub fn combine(self, other: Self) -> Self {
|
||||
match (self, other) {
|
||||
(RequiredRouting::All, _) | (_, RequiredRouting::All) => RequiredRouting::All,
|
||||
(RequiredRouting::GridXY, _) | (_, RequiredRouting::GridXY) => RequiredRouting::GridXY,
|
||||
(RequiredRouting::GridX, RequiredRouting::GridY) |
|
||||
(RequiredRouting::GridY, RequiredRouting::GridX) => RequiredRouting::GridXY,
|
||||
(RequiredRouting::GridX, RequiredRouting::GridX) => RequiredRouting::GridX,
|
||||
(RequiredRouting::GridY, RequiredRouting::GridY) => RequiredRouting::GridY,
|
||||
(RequiredRouting::None, RequiredRouting::PendingTopology) |
|
||||
(RequiredRouting::PendingTopology, RequiredRouting::None) => RequiredRouting::PendingTopology,
|
||||
(RequiredRouting::None, _) | (RequiredRouting::PendingTopology, _) => other,
|
||||
(_, RequiredRouting::None) | (_, RequiredRouting::PendingTopology) => self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rand::SeedableRng;
|
||||
use rand_chacha::ChaCha12Rng;
|
||||
|
||||
fn dummy_rng() -> ChaCha12Rng {
|
||||
rand_chacha::ChaCha12Rng::seed_from_u64(12345)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_required_routing_combine() {
|
||||
assert_eq!(RequiredRouting::All.combine(RequiredRouting::None), RequiredRouting::All);
|
||||
assert_eq!(RequiredRouting::All.combine(RequiredRouting::GridXY), RequiredRouting::All);
|
||||
assert_eq!(RequiredRouting::GridXY.combine(RequiredRouting::All), RequiredRouting::All);
|
||||
assert_eq!(RequiredRouting::None.combine(RequiredRouting::All), RequiredRouting::All);
|
||||
assert_eq!(RequiredRouting::None.combine(RequiredRouting::None), RequiredRouting::None);
|
||||
assert_eq!(
|
||||
RequiredRouting::PendingTopology.combine(RequiredRouting::GridX),
|
||||
RequiredRouting::GridX
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
RequiredRouting::GridX.combine(RequiredRouting::PendingTopology),
|
||||
RequiredRouting::GridX
|
||||
);
|
||||
assert_eq!(RequiredRouting::GridX.combine(RequiredRouting::GridY), RequiredRouting::GridXY);
|
||||
assert_eq!(RequiredRouting::GridY.combine(RequiredRouting::GridX), RequiredRouting::GridXY);
|
||||
assert_eq!(
|
||||
RequiredRouting::GridXY.combine(RequiredRouting::GridXY),
|
||||
RequiredRouting::GridXY
|
||||
);
|
||||
assert_eq!(RequiredRouting::GridX.combine(RequiredRouting::GridX), RequiredRouting::GridX);
|
||||
assert_eq!(RequiredRouting::GridY.combine(RequiredRouting::GridY), RequiredRouting::GridY);
|
||||
|
||||
assert_eq!(RequiredRouting::None.combine(RequiredRouting::GridY), RequiredRouting::GridY);
|
||||
assert_eq!(RequiredRouting::None.combine(RequiredRouting::GridX), RequiredRouting::GridX);
|
||||
assert_eq!(RequiredRouting::None.combine(RequiredRouting::GridXY), RequiredRouting::GridXY);
|
||||
|
||||
assert_eq!(RequiredRouting::GridY.combine(RequiredRouting::None), RequiredRouting::GridY);
|
||||
assert_eq!(RequiredRouting::GridX.combine(RequiredRouting::None), RequiredRouting::GridX);
|
||||
assert_eq!(RequiredRouting::GridXY.combine(RequiredRouting::None), RequiredRouting::GridXY);
|
||||
|
||||
assert_eq!(
|
||||
RequiredRouting::PendingTopology.combine(RequiredRouting::None),
|
||||
RequiredRouting::PendingTopology
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
RequiredRouting::None.combine(RequiredRouting::PendingTopology),
|
||||
RequiredRouting::PendingTopology
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_random_routing_sample() {
|
||||
// This test is fragile as it relies on a specific ChaCha12Rng
|
||||
// sequence that might be implementation defined even for a static seed
|
||||
let mut rng = dummy_rng();
|
||||
let mut random_routing = RandomRouting { target: 4, sent: 0, sample_rate: 8 };
|
||||
|
||||
assert_eq!(random_routing.sample(16, &mut rng), true);
|
||||
random_routing.inc_sent();
|
||||
assert_eq!(random_routing.sample(16, &mut rng), false);
|
||||
assert_eq!(random_routing.sample(16, &mut rng), false);
|
||||
assert_eq!(random_routing.sample(16, &mut rng), true);
|
||||
random_routing.inc_sent();
|
||||
assert_eq!(random_routing.sample(16, &mut rng), true);
|
||||
random_routing.inc_sent();
|
||||
assert_eq!(random_routing.sample(16, &mut rng), false);
|
||||
assert_eq!(random_routing.sample(16, &mut rng), false);
|
||||
assert_eq!(random_routing.sample(16, &mut rng), false);
|
||||
assert_eq!(random_routing.sample(16, &mut rng), true);
|
||||
random_routing.inc_sent();
|
||||
|
||||
for _ in 0..16 {
|
||||
assert_eq!(random_routing.sample(16, &mut rng), false);
|
||||
}
|
||||
}
|
||||
|
||||
fn run_random_routing(
|
||||
random_routing: &mut RandomRouting,
|
||||
rng: &mut (impl CryptoRng + Rng),
|
||||
npeers: usize,
|
||||
iters: usize,
|
||||
) -> usize {
|
||||
let mut ret = 0_usize;
|
||||
|
||||
for _ in 0..iters {
|
||||
if random_routing.sample(npeers, rng) {
|
||||
random_routing.inc_sent();
|
||||
ret += 1;
|
||||
}
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_random_routing_distribution() {
|
||||
let mut rng = dummy_rng();
|
||||
|
||||
let mut random_routing = RandomRouting { target: 4, sent: 0, sample_rate: 8 };
|
||||
assert_eq!(run_random_routing(&mut random_routing, &mut rng, 100, 10000), 4);
|
||||
|
||||
let mut random_routing = RandomRouting { target: 8, sent: 0, sample_rate: 100 };
|
||||
assert_eq!(run_random_routing(&mut random_routing, &mut rng, 100, 10000), 8);
|
||||
|
||||
let mut random_routing = RandomRouting { target: 0, sent: 0, sample_rate: 100 };
|
||||
assert_eq!(run_random_routing(&mut random_routing, &mut rng, 100, 10000), 0);
|
||||
|
||||
let mut random_routing = RandomRouting { target: 10, sent: 0, sample_rate: 10 };
|
||||
assert_eq!(run_random_routing(&mut random_routing, &mut rng, 10, 100), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_matrix_neighbors() {
|
||||
for (our_index, len, expected_row, expected_column) in vec![
|
||||
(0usize, 1usize, vec![], vec![]),
|
||||
(1, 2, vec![], vec![0usize]),
|
||||
(0, 9, vec![1, 2], vec![3, 6]),
|
||||
(9, 10, vec![], vec![0, 3, 6]),
|
||||
(10, 11, vec![9], vec![1, 4, 7]),
|
||||
(7, 11, vec![6, 8], vec![1, 4, 10]),
|
||||
]
|
||||
.into_iter()
|
||||
{
|
||||
let matrix = matrix_neighbors(our_index, len).unwrap();
|
||||
let mut row_result: Vec<_> = matrix.row_neighbors.collect();
|
||||
let mut column_result: Vec<_> = matrix.column_neighbors.collect();
|
||||
row_result.sort();
|
||||
column_result.sort();
|
||||
|
||||
assert_eq!(row_result, expected_row);
|
||||
assert_eq!(column_result, expected_column);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,773 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi 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.
|
||||
|
||||
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Network protocol types for teyrchains.
|
||||
|
||||
#![deny(unused_crate_dependencies)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use pezkuwi_primitives::{BlockNumber, Hash};
|
||||
use std::fmt;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use sc_network::IfDisconnected;
|
||||
pub use sc_network_types::PeerId;
|
||||
#[doc(hidden)]
|
||||
pub use std::sync::Arc;
|
||||
|
||||
mod reputation;
|
||||
pub use self::reputation::{ReputationChange, UnifiedReputationChange};
|
||||
|
||||
/// Peer-sets and protocols used for teyrchains.
|
||||
pub mod peer_set;
|
||||
|
||||
/// Request/response protocols used in Pezkuwi.
|
||||
pub mod request_response;
|
||||
|
||||
/// Accessing authority discovery service
|
||||
pub mod authority_discovery;
|
||||
/// Grid topology support module
|
||||
pub mod grid_topology;
|
||||
|
||||
/// The minimum amount of peers to send gossip messages to.
|
||||
pub const MIN_GOSSIP_PEERS: usize = 25;
|
||||
|
||||
/// An error indicating that this the over-arching message type had the wrong variant
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct WrongVariant;
|
||||
|
||||
impl fmt::Display for WrongVariant {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(formatter, "Wrong message variant")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for WrongVariant {}
|
||||
|
||||
/// The advertised role of a node.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum ObservedRole {
|
||||
/// A light node.
|
||||
Light,
|
||||
/// A full node.
|
||||
Full,
|
||||
/// A node claiming to be an authority (unauthenticated)
|
||||
Authority,
|
||||
}
|
||||
|
||||
impl From<sc_network::ObservedRole> for ObservedRole {
|
||||
fn from(role: sc_network::ObservedRole) -> ObservedRole {
|
||||
match role {
|
||||
sc_network::ObservedRole::Light => ObservedRole::Light,
|
||||
sc_network::ObservedRole::Authority => ObservedRole::Authority,
|
||||
sc_network::ObservedRole::Full => ObservedRole::Full,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<sc_network::ObservedRole> for ObservedRole {
|
||||
fn into(self) -> sc_network::ObservedRole {
|
||||
match self {
|
||||
ObservedRole::Light => sc_network::ObservedRole::Light,
|
||||
ObservedRole::Full => sc_network::ObservedRole::Full,
|
||||
ObservedRole::Authority => sc_network::ObservedRole::Authority,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Specialized wrapper around [`View`].
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct OurView {
|
||||
view: View,
|
||||
}
|
||||
|
||||
impl OurView {
|
||||
/// Creates a new instance.
|
||||
pub fn new(heads: impl IntoIterator<Item = Hash>, finalized_number: BlockNumber) -> Self {
|
||||
let view = View::new(heads, finalized_number);
|
||||
Self { view }
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for OurView {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.view == other.view
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for OurView {
|
||||
type Target = View;
|
||||
|
||||
fn deref(&self) -> &View {
|
||||
&self.view
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a new [`OurView`] with the given chain heads, finalized number 0
|
||||
///
|
||||
/// NOTE: Use for tests only.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use pezkuwi_node_network_protocol::our_view;
|
||||
/// # use pezkuwi_primitives::Hash;
|
||||
/// let our_view = our_view![Hash::repeat_byte(1), Hash::repeat_byte(2)];
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! our_view {
|
||||
( $( $hash:expr ),* $(,)? ) => {
|
||||
$crate::OurView::new(
|
||||
vec![ $( $hash.clone() ),* ].into_iter().map(|h| h),
|
||||
0,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
/// A succinct representation of a peer's view. This consists of a bounded amount of chain heads
|
||||
/// and the highest known finalized block number.
|
||||
///
|
||||
/// Up to `N` (5?) chain heads.
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Encode, Decode)]
|
||||
pub struct View {
|
||||
/// A bounded amount of chain heads.
|
||||
/// Invariant: Sorted.
|
||||
heads: Vec<Hash>,
|
||||
/// The highest known finalized block number.
|
||||
pub finalized_number: BlockNumber,
|
||||
}
|
||||
|
||||
/// Construct a new view with the given chain heads and finalized number 0.
|
||||
///
|
||||
/// NOTE: Use for tests only.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use pezkuwi_node_network_protocol::view;
|
||||
/// # use pezkuwi_primitives::Hash;
|
||||
/// let view = view![Hash::repeat_byte(1), Hash::repeat_byte(2)];
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! view {
|
||||
( $( $hash:expr ),* $(,)? ) => {
|
||||
$crate::View::new(vec![ $( $hash.clone() ),* ], 0)
|
||||
};
|
||||
}
|
||||
|
||||
impl View {
|
||||
/// Construct a new view based on heads and a finalized block number.
|
||||
pub fn new(heads: impl IntoIterator<Item = Hash>, finalized_number: BlockNumber) -> Self {
|
||||
let mut heads = heads.into_iter().collect::<Vec<Hash>>();
|
||||
heads.sort();
|
||||
Self { heads, finalized_number }
|
||||
}
|
||||
|
||||
/// Start with no heads, but only a finalized block number.
|
||||
pub fn with_finalized(finalized_number: BlockNumber) -> Self {
|
||||
Self { heads: Vec::new(), finalized_number }
|
||||
}
|
||||
|
||||
/// Obtain the number of heads that are in view.
|
||||
pub fn len(&self) -> usize {
|
||||
self.heads.len()
|
||||
}
|
||||
|
||||
/// Check if the number of heads contained, is null.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.heads.is_empty()
|
||||
}
|
||||
|
||||
/// Obtain an iterator over all heads.
|
||||
pub fn iter(&self) -> impl Iterator<Item = &Hash> {
|
||||
self.heads.iter()
|
||||
}
|
||||
|
||||
/// Obtain an iterator over all heads.
|
||||
pub fn into_iter(self) -> impl Iterator<Item = Hash> {
|
||||
self.heads.into_iter()
|
||||
}
|
||||
|
||||
/// Replace `self` with `new`.
|
||||
///
|
||||
/// Returns an iterator that will yield all elements of `new` that were not part of `self`.
|
||||
pub fn replace_difference(&mut self, new: View) -> impl Iterator<Item = &Hash> {
|
||||
let old = std::mem::replace(self, new);
|
||||
|
||||
self.heads.iter().filter(move |h| !old.contains(h))
|
||||
}
|
||||
|
||||
/// Returns an iterator of the hashes present in `Self` but not in `other`.
|
||||
pub fn difference<'a>(&'a self, other: &'a View) -> impl Iterator<Item = &'a Hash> + 'a {
|
||||
self.heads.iter().filter(move |h| !other.contains(h))
|
||||
}
|
||||
|
||||
/// An iterator containing hashes present in both `Self` and in `other`.
|
||||
pub fn intersection<'a>(&'a self, other: &'a View) -> impl Iterator<Item = &'a Hash> + 'a {
|
||||
self.heads.iter().filter(move |h| other.contains(h))
|
||||
}
|
||||
|
||||
/// Whether the view contains a given hash.
|
||||
pub fn contains(&self, hash: &Hash) -> bool {
|
||||
self.heads.contains(hash)
|
||||
}
|
||||
|
||||
/// Check if two views have the same heads.
|
||||
///
|
||||
/// Equivalent to the `PartialEq` function,
|
||||
/// but ignores the `finalized_number` field.
|
||||
pub fn check_heads_eq(&self, other: &Self) -> bool {
|
||||
self.heads == other.heads
|
||||
}
|
||||
}
|
||||
|
||||
/// A protocol-versioned type for validation.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ValidationProtocols<V3> {
|
||||
/// V3 type.
|
||||
V3(V3),
|
||||
}
|
||||
|
||||
/// A protocol-versioned type for collation.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum CollationProtocols<V1, V2> {
|
||||
/// V1 type.
|
||||
V1(V1),
|
||||
/// V2 type.
|
||||
V2(V2),
|
||||
}
|
||||
|
||||
impl<V3: Clone> ValidationProtocols<&'_ V3> {
|
||||
/// Convert to a fully-owned version of the message.
|
||||
pub fn clone_inner(&self) -> ValidationProtocols<V3> {
|
||||
match *self {
|
||||
ValidationProtocols::V3(inner) => ValidationProtocols::V3(inner.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V1: Clone, V2: Clone> CollationProtocols<&'_ V1, &'_ V2> {
|
||||
/// Convert to a fully-owned version of the message.
|
||||
pub fn clone_inner(&self) -> CollationProtocols<V1, V2> {
|
||||
match *self {
|
||||
CollationProtocols::V1(inner) => CollationProtocols::V1(inner.clone()),
|
||||
CollationProtocols::V2(inner) => CollationProtocols::V2(inner.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// All supported versions of the validation protocol message.
|
||||
pub type VersionedValidationProtocol = ValidationProtocols<v3::ValidationProtocol>;
|
||||
|
||||
impl From<v3::ValidationProtocol> for VersionedValidationProtocol {
|
||||
fn from(v3: v3::ValidationProtocol) -> Self {
|
||||
VersionedValidationProtocol::V3(v3)
|
||||
}
|
||||
}
|
||||
|
||||
/// All supported versions of the collation protocol message.
|
||||
pub type VersionedCollationProtocol =
|
||||
CollationProtocols<v1::CollationProtocol, v2::CollationProtocol>;
|
||||
|
||||
impl From<v1::CollationProtocol> for VersionedCollationProtocol {
|
||||
fn from(v1: v1::CollationProtocol) -> Self {
|
||||
VersionedCollationProtocol::V1(v1)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::CollationProtocol> for VersionedCollationProtocol {
|
||||
fn from(v2: v2::CollationProtocol) -> Self {
|
||||
VersionedCollationProtocol::V2(v2)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_versioned_validation_full_protocol_from {
|
||||
($from:ty, $out:ty, $variant:ident) => {
|
||||
impl From<$from> for $out {
|
||||
fn from(versioned_from: $from) -> $out {
|
||||
match versioned_from {
|
||||
ValidationProtocols::V3(x) => ValidationProtocols::V3(x.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_versioned_collation_full_protocol_from {
|
||||
($from:ty, $out:ty, $variant:ident) => {
|
||||
impl From<$from> for $out {
|
||||
fn from(versioned_from: $from) -> $out {
|
||||
match versioned_from {
|
||||
CollationProtocols::V1(x) => CollationProtocols::V1(x.into()),
|
||||
CollationProtocols::V2(x) => CollationProtocols::V2(x.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Implement `TryFrom` for one versioned validation enum variant into the inner type.
|
||||
/// `$m_ty::$variant(inner) -> Ok(inner)`
|
||||
macro_rules! impl_versioned_validation_try_from {
|
||||
(
|
||||
$from:ty,
|
||||
$out:ty,
|
||||
$v3_pat:pat => $v3_out:expr
|
||||
) => {
|
||||
impl TryFrom<$from> for $out {
|
||||
type Error = crate::WrongVariant;
|
||||
|
||||
fn try_from(x: $from) -> Result<$out, Self::Error> {
|
||||
#[allow(unreachable_patterns)] // when there is only one variant
|
||||
match x {
|
||||
ValidationProtocols::V3($v3_pat) => Ok(ValidationProtocols::V3($v3_out)),
|
||||
_ => Err(crate::WrongVariant),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a $from> for $out {
|
||||
type Error = crate::WrongVariant;
|
||||
|
||||
fn try_from(x: &'a $from) -> Result<$out, Self::Error> {
|
||||
#[allow(unreachable_patterns)] // when there is only one variant
|
||||
match x {
|
||||
ValidationProtocols::V3($v3_pat) =>
|
||||
Ok(ValidationProtocols::V3($v3_out.clone())),
|
||||
_ => Err(crate::WrongVariant),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Implement `TryFrom` for one versioned collation enum variant into the inner type.
|
||||
/// `$m_ty::$variant(inner) -> Ok(inner)`
|
||||
macro_rules! impl_versioned_collation_try_from {
|
||||
(
|
||||
$from:ty,
|
||||
$out:ty,
|
||||
$v1_pat:pat => $v1_out:expr,
|
||||
$v2_pat:pat => $v2_out:expr
|
||||
) => {
|
||||
impl TryFrom<$from> for $out {
|
||||
type Error = crate::WrongVariant;
|
||||
|
||||
fn try_from(x: $from) -> Result<$out, Self::Error> {
|
||||
#[allow(unreachable_patterns)] // when there is only one variant
|
||||
match x {
|
||||
CollationProtocols::V1($v1_pat) => Ok(CollationProtocols::V1($v1_out)),
|
||||
CollationProtocols::V2($v2_pat) => Ok(CollationProtocols::V2($v2_out)),
|
||||
_ => Err(crate::WrongVariant),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a $from> for $out {
|
||||
type Error = crate::WrongVariant;
|
||||
|
||||
fn try_from(x: &'a $from) -> Result<$out, Self::Error> {
|
||||
#[allow(unreachable_patterns)] // when there is only one variant
|
||||
match x {
|
||||
CollationProtocols::V1($v1_pat) => Ok(CollationProtocols::V1($v1_out.clone())),
|
||||
CollationProtocols::V2($v2_pat) => Ok(CollationProtocols::V2($v2_out.clone())),
|
||||
_ => Err(crate::WrongVariant),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Version-annotated messages used by the bitfield distribution subsystem.
|
||||
pub type BitfieldDistributionMessage = ValidationProtocols<v3::BitfieldDistributionMessage>;
|
||||
impl_versioned_validation_full_protocol_from!(
|
||||
BitfieldDistributionMessage,
|
||||
VersionedValidationProtocol,
|
||||
BitfieldDistribution
|
||||
);
|
||||
impl_versioned_validation_try_from!(
|
||||
VersionedValidationProtocol,
|
||||
BitfieldDistributionMessage,
|
||||
v3::ValidationProtocol::BitfieldDistribution(x) => x
|
||||
);
|
||||
|
||||
/// Version-annotated messages used by the statement distribution subsystem.
|
||||
pub type StatementDistributionMessage = ValidationProtocols<v3::StatementDistributionMessage>;
|
||||
impl_versioned_validation_full_protocol_from!(
|
||||
StatementDistributionMessage,
|
||||
VersionedValidationProtocol,
|
||||
StatementDistribution
|
||||
);
|
||||
impl_versioned_validation_try_from!(
|
||||
VersionedValidationProtocol,
|
||||
StatementDistributionMessage,
|
||||
v3::ValidationProtocol::StatementDistribution(x) => x
|
||||
);
|
||||
|
||||
/// Version-annotated messages used by the approval distribution subsystem.
|
||||
pub type ApprovalDistributionMessage = ValidationProtocols<v3::ApprovalDistributionMessage>;
|
||||
impl_versioned_validation_full_protocol_from!(
|
||||
ApprovalDistributionMessage,
|
||||
VersionedValidationProtocol,
|
||||
ApprovalDistribution
|
||||
);
|
||||
impl_versioned_validation_try_from!(
|
||||
VersionedValidationProtocol,
|
||||
ApprovalDistributionMessage,
|
||||
v3::ValidationProtocol::ApprovalDistribution(x) => x
|
||||
|
||||
);
|
||||
|
||||
/// Version-annotated messages used by the gossip-support subsystem (this is void).
|
||||
pub type GossipSupportNetworkMessage = ValidationProtocols<v3::GossipSupportNetworkMessage>;
|
||||
|
||||
// This is a void enum placeholder, so never gets sent over the wire.
|
||||
impl TryFrom<VersionedValidationProtocol> for GossipSupportNetworkMessage {
|
||||
type Error = WrongVariant;
|
||||
fn try_from(_: VersionedValidationProtocol) -> Result<Self, Self::Error> {
|
||||
Err(WrongVariant)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a VersionedValidationProtocol> for GossipSupportNetworkMessage {
|
||||
type Error = WrongVariant;
|
||||
fn try_from(_: &'a VersionedValidationProtocol) -> Result<Self, Self::Error> {
|
||||
Err(WrongVariant)
|
||||
}
|
||||
}
|
||||
|
||||
/// Version-annotated messages used by the collator protocol subsystem.
|
||||
pub type CollatorProtocolMessage =
|
||||
CollationProtocols<v1::CollatorProtocolMessage, v2::CollatorProtocolMessage>;
|
||||
impl_versioned_collation_full_protocol_from!(
|
||||
CollatorProtocolMessage,
|
||||
VersionedCollationProtocol,
|
||||
CollatorProtocol
|
||||
);
|
||||
impl_versioned_collation_try_from!(
|
||||
VersionedCollationProtocol,
|
||||
CollatorProtocolMessage,
|
||||
v1::CollationProtocol::CollatorProtocol(x) => x,
|
||||
v2::CollationProtocol::CollatorProtocol(x) => x
|
||||
);
|
||||
|
||||
/// v1 notification protocol types.
|
||||
pub mod v1 {
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
use pezkuwi_primitives::{CollatorId, CollatorSignature, Hash, Id as ParaId};
|
||||
|
||||
use pezkuwi_node_primitives::UncheckedSignedFullStatement;
|
||||
|
||||
/// Network messages used by the collator protocol subsystem
|
||||
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
|
||||
pub enum CollatorProtocolMessage {
|
||||
/// Declare the intent to advertise collations under a collator ID, attaching a
|
||||
/// signature of the `PeerId` of the node using the given collator ID key.
|
||||
#[codec(index = 0)]
|
||||
Declare(CollatorId, ParaId, CollatorSignature),
|
||||
/// Advertise a collation to a validator. Can only be sent once the peer has
|
||||
/// declared that they are a collator with given ID.
|
||||
#[codec(index = 1)]
|
||||
AdvertiseCollation(Hash),
|
||||
/// A collation sent to a validator was seconded.
|
||||
#[codec(index = 4)]
|
||||
CollationSeconded(Hash, UncheckedSignedFullStatement),
|
||||
}
|
||||
|
||||
/// All network messages on the collation peer-set.
|
||||
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq, derive_more::From)]
|
||||
pub enum CollationProtocol {
|
||||
/// Collator protocol messages
|
||||
#[codec(index = 0)]
|
||||
#[from]
|
||||
CollatorProtocol(CollatorProtocolMessage),
|
||||
}
|
||||
|
||||
/// Get the payload that should be signed and included in a `Declare` message.
|
||||
///
|
||||
/// The payload is the local peer id of the node, which serves to prove that it
|
||||
/// controls the collator key it is declaring an intention to collate under.
|
||||
pub fn declare_signature_payload(peer_id: &sc_network_types::PeerId) -> Vec<u8> {
|
||||
let mut payload = peer_id.to_bytes();
|
||||
payload.extend_from_slice(b"COLL");
|
||||
payload
|
||||
}
|
||||
}
|
||||
|
||||
/// v2 network protocol types.
|
||||
pub mod v2 {
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
use pezkuwi_primitives::{CandidateHash, CollatorId, CollatorSignature, Hash, Id as ParaId};
|
||||
|
||||
use pezkuwi_node_primitives::UncheckedSignedFullStatement;
|
||||
|
||||
/// This parts of the protocol did not change from v1, so just alias them in v2.
|
||||
pub use super::v1::declare_signature_payload;
|
||||
|
||||
/// Network messages used by the collator protocol subsystem
|
||||
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
|
||||
pub enum CollatorProtocolMessage {
|
||||
/// Declare the intent to advertise collations under a collator ID, attaching a
|
||||
/// signature of the `PeerId` of the node using the given collator ID key.
|
||||
#[codec(index = 0)]
|
||||
Declare(CollatorId, ParaId, CollatorSignature),
|
||||
/// Advertise a collation to a validator. Can only be sent once the peer has
|
||||
/// declared that they are a collator with given ID.
|
||||
#[codec(index = 1)]
|
||||
AdvertiseCollation {
|
||||
/// Hash of the relay parent advertised collation is based on.
|
||||
relay_parent: Hash,
|
||||
/// Candidate hash.
|
||||
candidate_hash: CandidateHash,
|
||||
/// Teyrchain head data hash before candidate execution.
|
||||
parent_head_data_hash: Hash,
|
||||
},
|
||||
/// A collation sent to a validator was seconded.
|
||||
#[codec(index = 4)]
|
||||
CollationSeconded(Hash, UncheckedSignedFullStatement),
|
||||
}
|
||||
|
||||
/// All network messages on the collation peer-set.
|
||||
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq, derive_more::From)]
|
||||
pub enum CollationProtocol {
|
||||
/// Collator protocol messages
|
||||
#[codec(index = 0)]
|
||||
#[from]
|
||||
CollatorProtocol(CollatorProtocolMessage),
|
||||
}
|
||||
}
|
||||
|
||||
/// v3 network protocol types.
|
||||
/// Purpose is for changing ApprovalDistributionMessage to
|
||||
/// include more than one assignment and approval in a message.
|
||||
pub mod v3 {
|
||||
use bitvec::{order::Lsb0, slice::BitSlice, vec::BitVec};
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
use pezkuwi_primitives::{
|
||||
CandidateHash, GroupIndex, Hash, Id as ParaId, UncheckedSignedAvailabilityBitfield,
|
||||
UncheckedSignedStatement,
|
||||
};
|
||||
|
||||
use pezkuwi_node_primitives::approval::v2::{
|
||||
CandidateBitfield, IndirectAssignmentCertV2, IndirectSignedApprovalVoteV2,
|
||||
};
|
||||
|
||||
/// This parts of the protocol did not change from v2, so just alias them in v3.
|
||||
pub use super::v2::declare_signature_payload;
|
||||
|
||||
/// Network messages used by the bitfield distribution subsystem.
|
||||
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
|
||||
pub enum BitfieldDistributionMessage {
|
||||
/// A signed availability bitfield for a given relay-parent hash.
|
||||
#[codec(index = 0)]
|
||||
Bitfield(Hash, UncheckedSignedAvailabilityBitfield),
|
||||
}
|
||||
|
||||
/// Bitfields indicating the statements that are known or undesired
|
||||
/// about a candidate.
|
||||
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
|
||||
pub struct StatementFilter {
|
||||
/// Seconded statements. '1' is known or undesired.
|
||||
pub seconded_in_group: BitVec<u8, Lsb0>,
|
||||
/// Valid statements. '1' is known or undesired.
|
||||
pub validated_in_group: BitVec<u8, Lsb0>,
|
||||
}
|
||||
|
||||
impl StatementFilter {
|
||||
/// Create a new blank filter with the given group size.
|
||||
pub fn blank(group_size: usize) -> Self {
|
||||
StatementFilter {
|
||||
seconded_in_group: BitVec::repeat(false, group_size),
|
||||
validated_in_group: BitVec::repeat(false, group_size),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new full filter with the given group size.
|
||||
pub fn full(group_size: usize) -> Self {
|
||||
StatementFilter {
|
||||
seconded_in_group: BitVec::repeat(true, group_size),
|
||||
validated_in_group: BitVec::repeat(true, group_size),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the filter has a specific expected length, consistent across both
|
||||
/// bitfields.
|
||||
pub fn has_len(&self, len: usize) -> bool {
|
||||
self.seconded_in_group.len() == len && self.validated_in_group.len() == len
|
||||
}
|
||||
|
||||
/// Determine the number of backing validators in the statement filter.
|
||||
pub fn backing_validators(&self) -> usize {
|
||||
self.seconded_in_group
|
||||
.iter()
|
||||
.by_vals()
|
||||
.zip(self.validated_in_group.iter().by_vals())
|
||||
.filter(|&(s, v)| s || v) // no double-counting
|
||||
.count()
|
||||
}
|
||||
|
||||
/// Whether the statement filter has at least one seconded statement.
|
||||
pub fn has_seconded(&self) -> bool {
|
||||
self.seconded_in_group.iter().by_vals().any(|x| x)
|
||||
}
|
||||
|
||||
/// Mask out `Seconded` statements in `self` according to the provided
|
||||
/// bitvec. Bits appearing in `mask` will not appear in `self` afterwards.
|
||||
pub fn mask_seconded(&mut self, mask: &BitSlice<u8, Lsb0>) {
|
||||
for (mut x, mask) in self
|
||||
.seconded_in_group
|
||||
.iter_mut()
|
||||
.zip(mask.iter().by_vals().chain(std::iter::repeat(false)))
|
||||
{
|
||||
// (x, mask) => x
|
||||
// (true, true) => false
|
||||
// (true, false) => true
|
||||
// (false, true) => false
|
||||
// (false, false) => false
|
||||
*x = *x && !mask;
|
||||
}
|
||||
}
|
||||
|
||||
/// Mask out `Valid` statements in `self` according to the provided
|
||||
/// bitvec. Bits appearing in `mask` will not appear in `self` afterwards.
|
||||
pub fn mask_valid(&mut self, mask: &BitSlice<u8, Lsb0>) {
|
||||
for (mut x, mask) in self
|
||||
.validated_in_group
|
||||
.iter_mut()
|
||||
.zip(mask.iter().by_vals().chain(std::iter::repeat(false)))
|
||||
{
|
||||
// (x, mask) => x
|
||||
// (true, true) => false
|
||||
// (true, false) => true
|
||||
// (false, true) => false
|
||||
// (false, false) => false
|
||||
*x = *x && !mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A manifest of a known backed candidate, along with a description
|
||||
/// of the statements backing it.
|
||||
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
|
||||
pub struct BackedCandidateManifest {
|
||||
/// The relay-parent of the candidate.
|
||||
pub relay_parent: Hash,
|
||||
/// The hash of the candidate.
|
||||
pub candidate_hash: CandidateHash,
|
||||
/// The group index backing the candidate at the relay-parent.
|
||||
pub group_index: GroupIndex,
|
||||
/// The para ID of the candidate. It is illegal for this to
|
||||
/// be a para ID which is not assigned to the group indicated
|
||||
/// in this manifest.
|
||||
pub para_id: ParaId,
|
||||
/// The head-data corresponding to the candidate.
|
||||
pub parent_head_data_hash: Hash,
|
||||
/// A statement filter which indicates which validators in the
|
||||
/// para's group at the relay-parent have validated this candidate
|
||||
/// and issued statements about it, to the advertiser's knowledge.
|
||||
///
|
||||
/// This MUST have exactly the minimum amount of bytes
|
||||
/// necessary to represent the number of validators in the assigned
|
||||
/// backing group as-of the relay-parent.
|
||||
pub statement_knowledge: StatementFilter,
|
||||
}
|
||||
|
||||
/// An acknowledgement of a backed candidate being known.
|
||||
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
|
||||
pub struct BackedCandidateAcknowledgement {
|
||||
/// The hash of the candidate.
|
||||
pub candidate_hash: CandidateHash,
|
||||
/// A statement filter which indicates which validators in the
|
||||
/// para's group at the relay-parent have validated this candidate
|
||||
/// and issued statements about it, to the advertiser's knowledge.
|
||||
///
|
||||
/// This MUST have exactly the minimum amount of bytes
|
||||
/// necessary to represent the number of validators in the assigned
|
||||
/// backing group as-of the relay-parent.
|
||||
pub statement_knowledge: StatementFilter,
|
||||
}
|
||||
|
||||
/// Network messages used by the statement distribution subsystem.
|
||||
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
|
||||
pub enum StatementDistributionMessage {
|
||||
/// A notification of a signed statement in compact form, for a given relay parent.
|
||||
#[codec(index = 0)]
|
||||
Statement(Hash, UncheckedSignedStatement),
|
||||
|
||||
/// A notification of a backed candidate being known by the
|
||||
/// sending node, for the purpose of being requested by the receiving node
|
||||
/// if needed.
|
||||
#[codec(index = 1)]
|
||||
BackedCandidateManifest(BackedCandidateManifest),
|
||||
|
||||
/// A notification of a backed candidate being known by the sending node,
|
||||
/// for the purpose of informing a receiving node which already has the candidate.
|
||||
#[codec(index = 2)]
|
||||
BackedCandidateKnown(BackedCandidateAcknowledgement),
|
||||
}
|
||||
|
||||
/// Network messages used by the approval distribution subsystem.
|
||||
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
|
||||
pub enum ApprovalDistributionMessage {
|
||||
/// Assignments for candidates in recent, unfinalized blocks.
|
||||
/// We use a bitfield to reference claimed candidates, where the bit index is equal to
|
||||
/// candidate index.
|
||||
///
|
||||
/// Actually checking the assignment may yield a different result.
|
||||
///
|
||||
/// TODO at next protocol upgrade opportunity:
|
||||
/// - remove redundancy `candidate_index` vs `core_index`
|
||||
/// - `<https://github.com/pezkuwichain/pezkuwi-sdk/issues/106>`
|
||||
#[codec(index = 0)]
|
||||
Assignments(Vec<(IndirectAssignmentCertV2, CandidateBitfield)>),
|
||||
/// Approvals for candidates in some recent, unfinalized block.
|
||||
#[codec(index = 1)]
|
||||
Approvals(Vec<IndirectSignedApprovalVoteV2>),
|
||||
}
|
||||
|
||||
/// Dummy network message type, so we will receive connect/disconnect events.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum GossipSupportNetworkMessage {}
|
||||
|
||||
/// All network messages on the validation peer-set.
|
||||
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq, derive_more::From)]
|
||||
pub enum ValidationProtocol {
|
||||
/// Bitfield distribution messages
|
||||
#[codec(index = 1)]
|
||||
#[from]
|
||||
BitfieldDistribution(BitfieldDistributionMessage),
|
||||
/// Statement distribution messages
|
||||
#[codec(index = 3)]
|
||||
#[from]
|
||||
StatementDistribution(StatementDistributionMessage),
|
||||
/// Approval distribution messages
|
||||
#[codec(index = 4)]
|
||||
#[from]
|
||||
ApprovalDistribution(ApprovalDistributionMessage),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the subset of `peers` with the specified `version`.
|
||||
pub fn filter_by_peer_version(
|
||||
peers: &[(PeerId, peer_set::ProtocolVersion)],
|
||||
version: peer_set::ProtocolVersion,
|
||||
) -> Vec<PeerId> {
|
||||
peers.iter().filter(|(_, v)| v == &version).map(|(p, _)| *p).collect::<Vec<_>>()
|
||||
}
|
||||
@@ -0,0 +1,616 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi 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.
|
||||
|
||||
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! All peersets and protocols used for teyrchains.
|
||||
|
||||
use derive_more::Display;
|
||||
use pezkuwi_primitives::Hash;
|
||||
use sc_network::{
|
||||
config::SetConfig, peer_store::PeerStoreProvider, service::NotificationMetrics,
|
||||
types::ProtocolName, NetworkBackend, NotificationService,
|
||||
};
|
||||
use sp_runtime::traits::Block;
|
||||
use std::{
|
||||
collections::{hash_map::Entry, HashMap},
|
||||
ops::{Index, IndexMut},
|
||||
sync::Arc,
|
||||
};
|
||||
use strum::{EnumIter, IntoEnumIterator};
|
||||
|
||||
/// The legacy collation protocol name. Only supported on version = 1.
|
||||
const LEGACY_COLLATION_PROTOCOL_V1: &str = "/pezkuwi/collation/1";
|
||||
|
||||
/// The legacy protocol version. Is always 1 for collation.
|
||||
const LEGACY_COLLATION_PROTOCOL_VERSION_V1: u32 = 1;
|
||||
|
||||
/// Max notification size is currently constant.
|
||||
pub const MAX_NOTIFICATION_SIZE: u64 = 100 * 1024;
|
||||
|
||||
/// Maximum allowed incoming connection streams for validator nodes on the collation protocol.
|
||||
pub const MAX_AUTHORITY_INCOMING_STREAMS: u32 = 100;
|
||||
|
||||
/// The peer-sets and thus the protocols which are used for the network.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
|
||||
pub enum PeerSet {
|
||||
/// The validation peer-set is responsible for all messages related to candidate validation and
|
||||
/// communication among validators.
|
||||
Validation,
|
||||
/// The collation peer-set is used for validator<>collator communication.
|
||||
Collation,
|
||||
}
|
||||
|
||||
/// Whether a node is an authority or not.
|
||||
///
|
||||
/// Peer set configuration gets adjusted accordingly.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum IsAuthority {
|
||||
/// Node is authority.
|
||||
Yes,
|
||||
/// Node is not an authority.
|
||||
No,
|
||||
}
|
||||
|
||||
impl PeerSet {
|
||||
/// Get `sc_network` peer set configurations for each peerset on the default version.
|
||||
///
|
||||
/// Those should be used in the network configuration to register the protocols with the
|
||||
/// network service.
|
||||
pub fn get_info<B: Block, N: NetworkBackend<B, <B as Block>::Hash>>(
|
||||
self,
|
||||
is_authority: IsAuthority,
|
||||
peerset_protocol_names: &PeerSetProtocolNames,
|
||||
metrics: NotificationMetrics,
|
||||
peer_store_handle: Arc<dyn PeerStoreProvider>,
|
||||
) -> (N::NotificationProtocolConfig, (PeerSet, Box<dyn NotificationService>)) {
|
||||
// Networking layer relies on `get_main_name()` being the main name of the protocol
|
||||
// for peersets and connection management.
|
||||
let protocol = peerset_protocol_names.get_main_name(self);
|
||||
let fallback_names = PeerSetProtocolNames::get_fallback_names(
|
||||
self,
|
||||
&peerset_protocol_names.genesis_hash,
|
||||
peerset_protocol_names.fork_id.as_deref(),
|
||||
);
|
||||
let max_notification_size = self.get_max_notification_size(is_authority);
|
||||
|
||||
match self {
|
||||
PeerSet::Validation => {
|
||||
let (config, notification_service) = N::notification_config(
|
||||
protocol,
|
||||
fallback_names,
|
||||
max_notification_size,
|
||||
None,
|
||||
SetConfig {
|
||||
// we allow full nodes to connect to validators for gossip
|
||||
// to ensure any `MIN_GOSSIP_PEERS` always include reserved peers
|
||||
// we limit the amount of non-reserved slots to be less
|
||||
// than `MIN_GOSSIP_PEERS` in total
|
||||
in_peers: super::MIN_GOSSIP_PEERS as u32 / 2 - 1,
|
||||
out_peers: super::MIN_GOSSIP_PEERS as u32 / 2 - 1,
|
||||
reserved_nodes: Vec::new(),
|
||||
non_reserved_mode: sc_network::config::NonReservedPeerMode::Accept,
|
||||
},
|
||||
metrics,
|
||||
peer_store_handle,
|
||||
);
|
||||
|
||||
(config, (PeerSet::Validation, notification_service))
|
||||
},
|
||||
PeerSet::Collation => {
|
||||
let (config, notification_service) = N::notification_config(
|
||||
protocol,
|
||||
fallback_names,
|
||||
max_notification_size,
|
||||
None,
|
||||
SetConfig {
|
||||
// Non-authority nodes don't need to accept incoming connections on this
|
||||
// peer set:
|
||||
in_peers: if is_authority == IsAuthority::Yes {
|
||||
MAX_AUTHORITY_INCOMING_STREAMS
|
||||
} else {
|
||||
0
|
||||
},
|
||||
out_peers: 0,
|
||||
reserved_nodes: Vec::new(),
|
||||
non_reserved_mode: if is_authority == IsAuthority::Yes {
|
||||
sc_network::config::NonReservedPeerMode::Accept
|
||||
} else {
|
||||
sc_network::config::NonReservedPeerMode::Deny
|
||||
},
|
||||
},
|
||||
metrics,
|
||||
peer_store_handle,
|
||||
);
|
||||
|
||||
(config, (PeerSet::Collation, notification_service))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the main protocol version for this peer set.
|
||||
///
|
||||
/// Networking layer relies on `get_main_version()` being the version
|
||||
/// of the main protocol name reported by [`PeerSetProtocolNames::get_main_name()`].
|
||||
pub fn get_main_version(self) -> ProtocolVersion {
|
||||
match self {
|
||||
PeerSet::Validation => ValidationVersion::V3.into(),
|
||||
PeerSet::Collation => CollationVersion::V2.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the max notification size for this peer set.
|
||||
pub fn get_max_notification_size(self, _: IsAuthority) -> u64 {
|
||||
MAX_NOTIFICATION_SIZE
|
||||
}
|
||||
|
||||
/// Get the peer set label for metrics reporting.
|
||||
pub fn get_label(self) -> &'static str {
|
||||
match self {
|
||||
PeerSet::Validation => "validation",
|
||||
PeerSet::Collation => "collation",
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the protocol label for metrics reporting.
|
||||
pub fn get_protocol_label(self, version: ProtocolVersion) -> Option<&'static str> {
|
||||
// Unfortunately, labels must be static strings, so we must manually cover them
|
||||
// for all protocol versions here.
|
||||
match self {
|
||||
PeerSet::Validation =>
|
||||
if version == ValidationVersion::V3.into() {
|
||||
Some("validation/3")
|
||||
} else {
|
||||
None
|
||||
},
|
||||
PeerSet::Collation =>
|
||||
if version == CollationVersion::V1.into() {
|
||||
Some("collation/1")
|
||||
} else if version == CollationVersion::V2.into() {
|
||||
Some("collation/2")
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A small and nifty collection that allows to store data pertaining to each peer set.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PerPeerSet<T> {
|
||||
validation: T,
|
||||
collation: T,
|
||||
}
|
||||
|
||||
impl<T> Index<PeerSet> for PerPeerSet<T> {
|
||||
type Output = T;
|
||||
fn index(&self, index: PeerSet) -> &T {
|
||||
match index {
|
||||
PeerSet::Validation => &self.validation,
|
||||
PeerSet::Collation => &self.collation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IndexMut<PeerSet> for PerPeerSet<T> {
|
||||
fn index_mut(&mut self, index: PeerSet) -> &mut T {
|
||||
match index {
|
||||
PeerSet::Validation => &mut self.validation,
|
||||
PeerSet::Collation => &mut self.collation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get `NonDefaultSetConfig`s for all available peer sets, at their default versions.
|
||||
///
|
||||
/// Should be used during network configuration (added to `NetworkConfiguration::extra_sets`)
|
||||
/// or shortly after startup to register the protocols with the network service.
|
||||
pub fn peer_sets_info<B: Block, N: NetworkBackend<B, <B as Block>::Hash>>(
|
||||
is_authority: IsAuthority,
|
||||
peerset_protocol_names: &PeerSetProtocolNames,
|
||||
metrics: NotificationMetrics,
|
||||
peer_store_handle: Arc<dyn PeerStoreProvider>,
|
||||
) -> Vec<(N::NotificationProtocolConfig, (PeerSet, Box<dyn NotificationService>))> {
|
||||
PeerSet::iter()
|
||||
.map(|s| {
|
||||
s.get_info::<B, N>(
|
||||
is_authority,
|
||||
&peerset_protocol_names,
|
||||
metrics.clone(),
|
||||
Arc::clone(&peer_store_handle),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// A generic version of the protocol. This struct must not be created directly.
|
||||
#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, Hash)]
|
||||
pub struct ProtocolVersion(u32);
|
||||
|
||||
impl From<ProtocolVersion> for u32 {
|
||||
fn from(version: ProtocolVersion) -> u32 {
|
||||
version.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Supported validation protocol versions. Only versions defined here must be used in the codebase.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
|
||||
pub enum ValidationVersion {
|
||||
/// The third version.
|
||||
V3 = 3,
|
||||
}
|
||||
|
||||
/// Supported collation protocol versions. Only versions defined here must be used in the codebase.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
|
||||
pub enum CollationVersion {
|
||||
/// The first version.
|
||||
V1 = 1,
|
||||
/// The second version.
|
||||
V2 = 2,
|
||||
}
|
||||
|
||||
/// Marker indicating the version is unknown.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct UnknownVersion;
|
||||
|
||||
impl TryFrom<ProtocolVersion> for ValidationVersion {
|
||||
type Error = UnknownVersion;
|
||||
|
||||
fn try_from(p: ProtocolVersion) -> Result<Self, UnknownVersion> {
|
||||
for v in Self::iter() {
|
||||
if v as u32 == p.0 {
|
||||
return Ok(v);
|
||||
}
|
||||
}
|
||||
|
||||
Err(UnknownVersion)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ProtocolVersion> for CollationVersion {
|
||||
type Error = UnknownVersion;
|
||||
|
||||
fn try_from(p: ProtocolVersion) -> Result<Self, UnknownVersion> {
|
||||
for v in Self::iter() {
|
||||
if v as u32 == p.0 {
|
||||
return Ok(v);
|
||||
}
|
||||
}
|
||||
|
||||
Err(UnknownVersion)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ValidationVersion> for ProtocolVersion {
|
||||
fn from(version: ValidationVersion) -> ProtocolVersion {
|
||||
ProtocolVersion(version as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CollationVersion> for ProtocolVersion {
|
||||
fn from(version: CollationVersion) -> ProtocolVersion {
|
||||
ProtocolVersion(version as u32)
|
||||
}
|
||||
}
|
||||
|
||||
/// On the wire protocol name to [`PeerSet`] mapping.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PeerSetProtocolNames {
|
||||
protocols: HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
|
||||
names: HashMap<(PeerSet, ProtocolVersion), ProtocolName>,
|
||||
genesis_hash: Hash,
|
||||
fork_id: Option<String>,
|
||||
}
|
||||
|
||||
impl PeerSetProtocolNames {
|
||||
/// Construct [`PeerSetProtocolNames`] using `genesis_hash` and `fork_id`.
|
||||
pub fn new(genesis_hash: Hash, fork_id: Option<&str>) -> Self {
|
||||
let mut protocols = HashMap::new();
|
||||
let mut names = HashMap::new();
|
||||
for protocol in PeerSet::iter() {
|
||||
match protocol {
|
||||
PeerSet::Validation =>
|
||||
for version in ValidationVersion::iter() {
|
||||
Self::register_main_protocol(
|
||||
&mut protocols,
|
||||
&mut names,
|
||||
protocol,
|
||||
version.into(),
|
||||
&genesis_hash,
|
||||
fork_id,
|
||||
);
|
||||
},
|
||||
PeerSet::Collation => {
|
||||
for version in CollationVersion::iter() {
|
||||
Self::register_main_protocol(
|
||||
&mut protocols,
|
||||
&mut names,
|
||||
protocol,
|
||||
version.into(),
|
||||
&genesis_hash,
|
||||
fork_id,
|
||||
);
|
||||
}
|
||||
Self::register_legacy_collation_protocol(&mut protocols, protocol);
|
||||
},
|
||||
}
|
||||
}
|
||||
Self { protocols, names, genesis_hash, fork_id: fork_id.map(|fork_id| fork_id.into()) }
|
||||
}
|
||||
|
||||
/// Helper function to register main protocol.
|
||||
fn register_main_protocol(
|
||||
protocols: &mut HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
|
||||
names: &mut HashMap<(PeerSet, ProtocolVersion), ProtocolName>,
|
||||
protocol: PeerSet,
|
||||
version: ProtocolVersion,
|
||||
genesis_hash: &Hash,
|
||||
fork_id: Option<&str>,
|
||||
) {
|
||||
let protocol_name = Self::generate_name(genesis_hash, fork_id, protocol, version);
|
||||
names.insert((protocol, version), protocol_name.clone());
|
||||
Self::insert_protocol_or_panic(protocols, protocol_name, protocol, version);
|
||||
}
|
||||
|
||||
/// Helper function to register legacy collation protocol.
|
||||
fn register_legacy_collation_protocol(
|
||||
protocols: &mut HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
|
||||
protocol: PeerSet,
|
||||
) {
|
||||
Self::insert_protocol_or_panic(
|
||||
protocols,
|
||||
LEGACY_COLLATION_PROTOCOL_V1.into(),
|
||||
protocol,
|
||||
ProtocolVersion(LEGACY_COLLATION_PROTOCOL_VERSION_V1),
|
||||
)
|
||||
}
|
||||
|
||||
/// Helper function to make sure no protocols have the same name.
|
||||
fn insert_protocol_or_panic(
|
||||
protocols: &mut HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
|
||||
name: ProtocolName,
|
||||
protocol: PeerSet,
|
||||
version: ProtocolVersion,
|
||||
) {
|
||||
match protocols.entry(name) {
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert((protocol, version));
|
||||
},
|
||||
Entry::Occupied(entry) => {
|
||||
panic!(
|
||||
"Protocol {:?} (version {}) has the same on-the-wire name as protocol {:?} (version {}): `{}`.",
|
||||
protocol,
|
||||
version,
|
||||
entry.get().0,
|
||||
entry.get().1,
|
||||
entry.key(),
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Lookup the protocol using its on the wire name.
|
||||
pub fn try_get_protocol(&self, name: &ProtocolName) -> Option<(PeerSet, ProtocolVersion)> {
|
||||
self.protocols.get(name).map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
/// Get the main protocol name. It's used by the networking for keeping track
|
||||
/// of peersets and connections.
|
||||
pub fn get_main_name(&self, protocol: PeerSet) -> ProtocolName {
|
||||
self.get_name(protocol, protocol.get_main_version())
|
||||
}
|
||||
|
||||
/// Get the protocol name for specific version.
|
||||
pub fn get_name(&self, protocol: PeerSet, version: ProtocolVersion) -> ProtocolName {
|
||||
self.names
|
||||
.get(&(protocol, version))
|
||||
.expect("Protocols & versions are specified via enums defined above, and they are all registered in `new()`; qed")
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// The protocol name of this protocol based on `genesis_hash` and `fork_id`.
|
||||
fn generate_name(
|
||||
genesis_hash: &Hash,
|
||||
fork_id: Option<&str>,
|
||||
protocol: PeerSet,
|
||||
version: ProtocolVersion,
|
||||
) -> ProtocolName {
|
||||
let prefix = if let Some(fork_id) = fork_id {
|
||||
format!("/{}/{}", hex::encode(genesis_hash), fork_id)
|
||||
} else {
|
||||
format!("/{}", hex::encode(genesis_hash))
|
||||
};
|
||||
|
||||
let short_name = match protocol {
|
||||
PeerSet::Validation => "validation",
|
||||
PeerSet::Collation => "collation",
|
||||
};
|
||||
|
||||
format!("{}/{}/{}", prefix, short_name, version).into()
|
||||
}
|
||||
|
||||
/// Get the protocol fallback names. Currently, it only holds
|
||||
/// the legacy name for the collation protocol version 1.
|
||||
fn get_fallback_names(
|
||||
protocol: PeerSet,
|
||||
_genesis_hash: &Hash,
|
||||
_fork_id: Option<&str>,
|
||||
) -> Vec<ProtocolName> {
|
||||
let mut fallbacks = vec![];
|
||||
match protocol {
|
||||
PeerSet::Validation => {
|
||||
// The validation protocol no longer supports protocol versions 1 and 2,
|
||||
// and only version 3 is used. Therefore, fallback protocols remain empty.
|
||||
},
|
||||
PeerSet::Collation => {
|
||||
fallbacks.push(LEGACY_COLLATION_PROTOCOL_V1.into());
|
||||
},
|
||||
};
|
||||
fallbacks
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
CollationVersion, Hash, PeerSet, PeerSetProtocolNames, ProtocolVersion, ValidationVersion,
|
||||
};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
struct TestVersion(u32);
|
||||
|
||||
impl From<TestVersion> for ProtocolVersion {
|
||||
fn from(version: TestVersion) -> ProtocolVersion {
|
||||
ProtocolVersion(version.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn protocol_names_are_correctly_generated() {
|
||||
let genesis_hash = Hash::from([
|
||||
122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
|
||||
38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
|
||||
]);
|
||||
let name = PeerSetProtocolNames::generate_name(
|
||||
&genesis_hash,
|
||||
None,
|
||||
PeerSet::Validation,
|
||||
TestVersion(3).into(),
|
||||
);
|
||||
let expected =
|
||||
"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/validation/3";
|
||||
assert_eq!(name, expected.into());
|
||||
|
||||
let name = PeerSetProtocolNames::generate_name(
|
||||
&genesis_hash,
|
||||
None,
|
||||
PeerSet::Collation,
|
||||
TestVersion(5).into(),
|
||||
);
|
||||
let expected =
|
||||
"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/collation/5";
|
||||
assert_eq!(name, expected.into());
|
||||
|
||||
let fork_id = Some("test-fork");
|
||||
let name = PeerSetProtocolNames::generate_name(
|
||||
&genesis_hash,
|
||||
fork_id,
|
||||
PeerSet::Validation,
|
||||
TestVersion(7).into(),
|
||||
);
|
||||
let expected =
|
||||
"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/test-fork/validation/7";
|
||||
assert_eq!(name, expected.into());
|
||||
|
||||
let name = PeerSetProtocolNames::generate_name(
|
||||
&genesis_hash,
|
||||
fork_id,
|
||||
PeerSet::Collation,
|
||||
TestVersion(11).into(),
|
||||
);
|
||||
let expected =
|
||||
"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/test-fork/collation/11";
|
||||
assert_eq!(name, expected.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_protocol_names_are_known() {
|
||||
let genesis_hash = Hash::from([
|
||||
122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
|
||||
38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
|
||||
]);
|
||||
let protocol_names = PeerSetProtocolNames::new(genesis_hash, None);
|
||||
|
||||
let validation_main =
|
||||
"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/validation/3";
|
||||
assert_eq!(
|
||||
protocol_names.try_get_protocol(&validation_main.into()),
|
||||
Some((PeerSet::Validation, TestVersion(3).into())),
|
||||
);
|
||||
|
||||
let validation_legacy = "/pezkuwi/validation/1";
|
||||
assert!(protocol_names.try_get_protocol(&validation_legacy.into()).is_none());
|
||||
|
||||
let collation_main =
|
||||
"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/collation/1";
|
||||
assert_eq!(
|
||||
protocol_names.try_get_protocol(&collation_main.into()),
|
||||
Some((PeerSet::Collation, TestVersion(1).into())),
|
||||
);
|
||||
|
||||
let collation_legacy = "/pezkuwi/collation/1";
|
||||
assert_eq!(
|
||||
protocol_names.try_get_protocol(&collation_legacy.into()),
|
||||
Some((PeerSet::Collation, TestVersion(1).into())),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_protocol_versions_are_registered() {
|
||||
let genesis_hash = Hash::from([
|
||||
122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
|
||||
38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
|
||||
]);
|
||||
let protocol_names = PeerSetProtocolNames::new(genesis_hash, None);
|
||||
|
||||
for protocol in PeerSet::iter() {
|
||||
match protocol {
|
||||
PeerSet::Validation =>
|
||||
for version in ValidationVersion::iter() {
|
||||
assert_eq!(
|
||||
protocol_names.get_name(protocol, version.into()),
|
||||
PeerSetProtocolNames::generate_name(
|
||||
&genesis_hash,
|
||||
None,
|
||||
protocol,
|
||||
version.into(),
|
||||
),
|
||||
);
|
||||
},
|
||||
PeerSet::Collation =>
|
||||
for version in CollationVersion::iter() {
|
||||
assert_eq!(
|
||||
protocol_names.get_name(protocol, version.into()),
|
||||
PeerSetProtocolNames::generate_name(
|
||||
&genesis_hash,
|
||||
None,
|
||||
protocol,
|
||||
version.into(),
|
||||
),
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_protocol_versions_have_labels() {
|
||||
for protocol in PeerSet::iter() {
|
||||
match protocol {
|
||||
PeerSet::Validation =>
|
||||
for version in ValidationVersion::iter() {
|
||||
protocol
|
||||
.get_protocol_label(version.into())
|
||||
.expect("All validation protocol versions must have a label.");
|
||||
},
|
||||
PeerSet::Collation =>
|
||||
for version in CollationVersion::iter() {
|
||||
protocol
|
||||
.get_protocol_label(version.into())
|
||||
.expect("All collation protocol versions must have a label.");
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi 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.
|
||||
|
||||
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
pub use sc_network::ReputationChange;
|
||||
|
||||
/// Unified annoyance cost and good behavior benefits.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum UnifiedReputationChange {
|
||||
CostMajor(&'static str),
|
||||
CostMinor(&'static str),
|
||||
CostMajorRepeated(&'static str),
|
||||
CostMinorRepeated(&'static str),
|
||||
Malicious(&'static str),
|
||||
BenefitMinorFirst(&'static str),
|
||||
BenefitMinor(&'static str),
|
||||
BenefitMajorFirst(&'static str),
|
||||
BenefitMajor(&'static str),
|
||||
}
|
||||
|
||||
impl UnifiedReputationChange {
|
||||
/// Obtain the cost or benefit associated with
|
||||
/// the enum variant.
|
||||
///
|
||||
/// Order of magnitude rationale:
|
||||
///
|
||||
/// * the peerset will not connect to a peer whose reputation is below a fixed value
|
||||
/// * `max(2% *$rep, 1)` is the delta of convergence towards a reputation of 0
|
||||
///
|
||||
/// The whole range of an `i32` should be used, so order of magnitude of
|
||||
/// something malicious should be `1<<20` (give or take).
|
||||
pub const fn cost_or_benefit(&self) -> i32 {
|
||||
match self {
|
||||
Self::CostMinor(_) => -100_000,
|
||||
Self::CostMajor(_) => -300_000,
|
||||
Self::CostMinorRepeated(_) => -200_000,
|
||||
Self::CostMajorRepeated(_) => -600_000,
|
||||
Self::Malicious(_) => i32::MIN,
|
||||
Self::BenefitMajorFirst(_) => 300_000,
|
||||
Self::BenefitMajor(_) => 200_000,
|
||||
Self::BenefitMinorFirst(_) => 15_000,
|
||||
Self::BenefitMinor(_) => 10_000,
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the static description.
|
||||
pub const fn description(&self) -> &'static str {
|
||||
match self {
|
||||
Self::CostMinor(description) => description,
|
||||
Self::CostMajor(description) => description,
|
||||
Self::CostMinorRepeated(description) => description,
|
||||
Self::CostMajorRepeated(description) => description,
|
||||
Self::Malicious(description) => description,
|
||||
Self::BenefitMajorFirst(description) => description,
|
||||
Self::BenefitMajor(description) => description,
|
||||
Self::BenefitMinorFirst(description) => description,
|
||||
Self::BenefitMinor(description) => description,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the reputation change is for good behavior.
|
||||
pub const fn is_benefit(&self) -> bool {
|
||||
match self {
|
||||
Self::BenefitMajorFirst(_) |
|
||||
Self::BenefitMajor(_) |
|
||||
Self::BenefitMinorFirst(_) |
|
||||
Self::BenefitMinor(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UnifiedReputationChange> for ReputationChange {
|
||||
fn from(value: UnifiedReputationChange) -> Self {
|
||||
ReputationChange::new(value.cost_or_benefit(), value.description())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi 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.
|
||||
|
||||
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Error handling related code and Error/Result definitions.
|
||||
|
||||
use sc_network_types::PeerId;
|
||||
|
||||
use codec::Error as DecodingError;
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[fatality::fatality(splitable)]
|
||||
pub enum Error {
|
||||
// Incoming request stream exhausted. Should only happen on shutdown.
|
||||
#[fatal]
|
||||
#[error("Incoming request channel got closed.")]
|
||||
RequestChannelExhausted,
|
||||
|
||||
/// 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 (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi 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.
|
||||
|
||||
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use futures::{channel::oneshot, StreamExt};
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
use sc_network::{config as netconfig, NetworkBackend};
|
||||
use sc_network_types::PeerId;
|
||||
use sp_runtime::traits::Block;
|
||||
|
||||
use super::{IsRequest, ReqProtocolNames};
|
||||
use crate::UnifiedReputationChange;
|
||||
|
||||
mod error;
|
||||
pub use error::{Error, FatalError, JfyiError, 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_protocols` 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<B: Block, N: NetworkBackend<B, <B as Block>::Hash>>(
|
||||
req_protocol_names: &ReqProtocolNames,
|
||||
) -> (IncomingRequestReceiver<Req>, N::RequestResponseProtocolConfig) {
|
||||
let (raw, cfg) = Req::PROTOCOL.get_config::<B, N>(req_protocol_names);
|
||||
(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, JfyiError> {
|
||||
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()).collect();
|
||||
let response = sc_network::config::OutgoingResponse {
|
||||
result: Err(()),
|
||||
reputation_changes,
|
||||
sent_feedback: None,
|
||||
};
|
||||
|
||||
if let Err(_) = pending_response.send(response) {
|
||||
return Err(JfyiError::DecodingErrorNoReputationChange(peer, err));
|
||||
}
|
||||
return Err(JfyiError::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()).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: async_channel::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(FatalError::RequestChannelExhausted.into()),
|
||||
Some(raw) => IncomingRequest::<Req>::try_from_raw(raw, reputation_changes())?,
|
||||
};
|
||||
Ok(req)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi 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.
|
||||
|
||||
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Overview over request/responses as used in `Pezkuwi`.
|
||||
//!
|
||||
//! `enum Protocol` .... List of all supported protocols.
|
||||
//!
|
||||
//! `enum Requests` .... List of all supported requests, each entry matches one in protocols, but
|
||||
//! has the actual request as payload.
|
||||
//!
|
||||
//! `struct IncomingRequest` .... wrapper for incoming requests, containing a sender for sending
|
||||
//! responses.
|
||||
//!
|
||||
//! `struct OutgoingRequest` .... wrapper for outgoing requests, containing a sender used by the
|
||||
//! networking code for delivering responses/delivery errors.
|
||||
//!
|
||||
//! `trait IsRequest` .... A trait describing a particular request. It is used for gathering meta
|
||||
//! data, like what is the corresponding response type.
|
||||
//!
|
||||
//! ## Versioning
|
||||
//!
|
||||
//! Versioning for request-response protocols can be done in multiple ways.
|
||||
//!
|
||||
//! If you're just changing the protocol name but the binary payloads are the same, just add a new
|
||||
//! `fallback_name` to the protocol config.
|
||||
//!
|
||||
//! One way in which versioning has historically been achieved for req-response protocols is to
|
||||
//! bundle the new req-resp version with an upgrade of a notifications protocol. The subsystem would
|
||||
//! then know which request version to use based on stored data about the peer's notifications
|
||||
//! protocol version.
|
||||
//!
|
||||
//! When bumping a notifications protocol version is not needed/desirable, you may add a new
|
||||
//! req-resp protocol and set the old request as a fallback (see
|
||||
//! `OutgoingRequest::new_with_fallback`). A request with the new version will be attempted and if
|
||||
//! the protocol is refused by the peer, the fallback protocol request will be used.
|
||||
//! Information about the actually used protocol will be returned alongside the raw response, so
|
||||
//! that you know how to decode it.
|
||||
|
||||
use std::{collections::HashMap, time::Duration, u64};
|
||||
|
||||
use pezkuwi_primitives::MAX_CODE_SIZE;
|
||||
use sc_network::{NetworkBackend, MAX_RESPONSE_SIZE};
|
||||
use sp_runtime::traits::Block;
|
||||
use strum::{EnumIter, IntoEnumIterator};
|
||||
|
||||
pub use sc_network::{config as network, config::RequestResponseConfig, ProtocolName};
|
||||
|
||||
/// 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;
|
||||
|
||||
/// Actual versioned requests and responses that are sent over the wire.
|
||||
pub mod v1;
|
||||
|
||||
/// Actual versioned requests and responses that are sent over the wire.
|
||||
pub mod v2;
|
||||
|
||||
/// A protocol per subsystem seems to make the most sense, this way we don't need any dispatching
|
||||
/// within protocols.
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, EnumIter)]
|
||||
pub enum Protocol {
|
||||
/// Protocol for chunk fetching, used by availability distribution and availability recovery.
|
||||
ChunkFetchingV1,
|
||||
/// Protocol for fetching collations from collators.
|
||||
CollationFetchingV1,
|
||||
/// Protocol for fetching collations from collators when async backing is enabled.
|
||||
CollationFetchingV2,
|
||||
/// Protocol for fetching seconded PoVs from validators of the same group.
|
||||
PoVFetchingV1,
|
||||
/// Protocol for fetching available data.
|
||||
AvailableDataFetchingV1,
|
||||
/// Sending of dispute statements with application level confirmations.
|
||||
DisputeSendingV1,
|
||||
|
||||
/// Protocol for requesting candidates with attestations in statement distribution
|
||||
/// when async backing is enabled.
|
||||
AttestedCandidateV2,
|
||||
|
||||
/// Protocol for chunk fetching version 2, used by availability distribution and availability
|
||||
/// recovery.
|
||||
ChunkFetchingV2,
|
||||
}
|
||||
|
||||
/// Minimum bandwidth we expect for validators - 500Mbit/s is the recommendation, so approximately
|
||||
/// 50MB per second:
|
||||
const MIN_BANDWIDTH_BYTES: u64 = 50 * 1024 * 1024;
|
||||
|
||||
/// Default request timeout in seconds.
|
||||
///
|
||||
/// When decreasing this value, take into account that the very first request might need to open a
|
||||
/// connection, which can be slow. If this causes problems, we should ensure connectivity via peer
|
||||
/// sets.
|
||||
#[allow(dead_code)]
|
||||
const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(3);
|
||||
|
||||
/// Request timeout where we can assume the connection is already open (e.g. we have peers in a
|
||||
/// peer set as well).
|
||||
const DEFAULT_REQUEST_TIMEOUT_CONNECTED: Duration = Duration::from_secs(1);
|
||||
|
||||
/// Timeout for requesting availability chunks.
|
||||
pub const CHUNK_REQUEST_TIMEOUT: Duration = DEFAULT_REQUEST_TIMEOUT_CONNECTED;
|
||||
|
||||
/// This timeout is based on the following parameters, assuming we use asynchronous backing with no
|
||||
/// time budget within a relay block:
|
||||
/// - 500 Mbit/s networking speed
|
||||
/// - 10 MB PoV
|
||||
/// - 10 parallel executions
|
||||
const POV_REQUEST_TIMEOUT_CONNECTED: Duration = Duration::from_millis(2000);
|
||||
|
||||
/// We want attested candidate requests to time out relatively fast,
|
||||
/// because slow requests will bottleneck the backing system. Ideally, we'd have
|
||||
/// an adaptive timeout based on the candidate size, because there will be a lot of variance
|
||||
/// in candidate sizes: candidates with no code and no messages vs candidates with code
|
||||
/// and messages.
|
||||
///
|
||||
/// We supply leniency because there are often large candidates and asynchronous
|
||||
/// backing allows them to be included over a longer window of time. Exponential back-off
|
||||
/// up to a maximum of 10 seconds would be ideal, but isn't supported by the
|
||||
/// infrastructure here yet: see https://github.com/paritytech/polkadot/issues/6009
|
||||
const ATTESTED_CANDIDATE_TIMEOUT: Duration = Duration::from_millis(2500);
|
||||
|
||||
/// We don't want a slow peer to slow down all the others, at the same time we want to get out the
|
||||
/// data quickly in full to at least some peers (as this will reduce load on us as they then can
|
||||
/// start serving the data). So this value is a tradeoff. 5 seems to be sensible. So we would need
|
||||
/// to have 5 slow nodes connected, to delay transfer for others by `ATTESTED_CANDIDATE_TIMEOUT`.
|
||||
pub const MAX_PARALLEL_ATTESTED_CANDIDATE_REQUESTS: u32 = 5;
|
||||
|
||||
/// Response size limit for responses of POV like data.
|
||||
///
|
||||
/// Same as what we use in substrate networking.
|
||||
const POV_RESPONSE_SIZE: u64 = MAX_RESPONSE_SIZE;
|
||||
|
||||
/// Maximum response sizes for `AttestedCandidateV2`.
|
||||
///
|
||||
/// This is `MAX_CODE_SIZE` plus some additional space for protocol overhead and
|
||||
/// additional backing statements.
|
||||
const ATTESTED_CANDIDATE_RESPONSE_SIZE: u64 = MAX_CODE_SIZE as u64 + 100_000;
|
||||
|
||||
/// We can have relative large timeouts here, there is no value of hitting a
|
||||
/// timeout as we want to get statements through to each node in any case.
|
||||
pub const DISPUTE_REQUEST_TIMEOUT: Duration = Duration::from_secs(12);
|
||||
|
||||
impl Protocol {
|
||||
/// Get a configuration for a given Request response protocol.
|
||||
///
|
||||
/// Returns a `ProtocolConfig` for this protocol.
|
||||
/// Use this if you plan only to send requests for this protocol.
|
||||
pub fn get_outbound_only_config<B: Block, N: NetworkBackend<B, <B as Block>::Hash>>(
|
||||
self,
|
||||
req_protocol_names: &ReqProtocolNames,
|
||||
) -> N::RequestResponseProtocolConfig {
|
||||
self.create_config::<B, N>(req_protocol_names, None)
|
||||
}
|
||||
|
||||
/// Get a configuration for a given Request response protocol.
|
||||
///
|
||||
/// Returns a receiver for messages received on this protocol and the requested
|
||||
/// `ProtocolConfig`.
|
||||
pub fn get_config<B: Block, N: NetworkBackend<B, <B as Block>::Hash>>(
|
||||
self,
|
||||
req_protocol_names: &ReqProtocolNames,
|
||||
) -> (async_channel::Receiver<network::IncomingRequest>, N::RequestResponseProtocolConfig) {
|
||||
let (tx, rx) = async_channel::bounded(self.get_channel_size());
|
||||
let cfg = self.create_config::<B, N>(req_protocol_names, Some(tx));
|
||||
(rx, cfg)
|
||||
}
|
||||
|
||||
fn create_config<B: Block, N: NetworkBackend<B, <B as Block>::Hash>>(
|
||||
self,
|
||||
req_protocol_names: &ReqProtocolNames,
|
||||
tx: Option<async_channel::Sender<network::IncomingRequest>>,
|
||||
) -> N::RequestResponseProtocolConfig {
|
||||
let name = req_protocol_names.get_name(self);
|
||||
let legacy_names = self.get_legacy_name().into_iter().map(Into::into).collect();
|
||||
match self {
|
||||
Protocol::ChunkFetchingV1 | Protocol::ChunkFetchingV2 => N::request_response_config(
|
||||
name,
|
||||
legacy_names,
|
||||
1_000,
|
||||
POV_RESPONSE_SIZE,
|
||||
// We are connected to all validators:
|
||||
CHUNK_REQUEST_TIMEOUT,
|
||||
tx,
|
||||
),
|
||||
Protocol::CollationFetchingV1 | Protocol::CollationFetchingV2 => {
|
||||
N::request_response_config(
|
||||
name,
|
||||
legacy_names,
|
||||
1_000,
|
||||
POV_RESPONSE_SIZE,
|
||||
// Taken from initial implementation in collator protocol:
|
||||
POV_REQUEST_TIMEOUT_CONNECTED,
|
||||
tx,
|
||||
)
|
||||
},
|
||||
Protocol::PoVFetchingV1 => N::request_response_config(
|
||||
name,
|
||||
legacy_names,
|
||||
1_000,
|
||||
POV_RESPONSE_SIZE,
|
||||
POV_REQUEST_TIMEOUT_CONNECTED,
|
||||
tx,
|
||||
),
|
||||
Protocol::AvailableDataFetchingV1 => N::request_response_config(
|
||||
name,
|
||||
legacy_names,
|
||||
1_000,
|
||||
// Available data size is dominated by the PoV size.
|
||||
POV_RESPONSE_SIZE,
|
||||
POV_REQUEST_TIMEOUT_CONNECTED,
|
||||
tx,
|
||||
),
|
||||
Protocol::DisputeSendingV1 => N::request_response_config(
|
||||
name,
|
||||
legacy_names,
|
||||
1_000,
|
||||
// Responses are just confirmation, in essence not even a bit. So 100 seems
|
||||
// plenty.
|
||||
100,
|
||||
DISPUTE_REQUEST_TIMEOUT,
|
||||
tx,
|
||||
),
|
||||
Protocol::AttestedCandidateV2 => N::request_response_config(
|
||||
name,
|
||||
legacy_names,
|
||||
1_000,
|
||||
ATTESTED_CANDIDATE_RESPONSE_SIZE,
|
||||
ATTESTED_CANDIDATE_TIMEOUT,
|
||||
tx,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// Channel sizes for the supported protocols.
|
||||
fn get_channel_size(self) -> usize {
|
||||
match self {
|
||||
// Hundreds of validators will start requesting their chunks once they see a candidate
|
||||
// awaiting availability on chain. Given that they will see that block at different
|
||||
// times (due to network delays), 100 seems big enough to accommodate for "bursts",
|
||||
// assuming we can service requests relatively quickly, which would need to be measured
|
||||
// as well.
|
||||
Protocol::ChunkFetchingV1 | Protocol::ChunkFetchingV2 => 100,
|
||||
// 10 seems reasonable, considering group sizes of max 10 validators.
|
||||
Protocol::CollationFetchingV1 | Protocol::CollationFetchingV2 => 10,
|
||||
// 10 seems reasonable, considering group sizes of max 10 validators.
|
||||
Protocol::PoVFetchingV1 => 10,
|
||||
// Validators are constantly self-selecting to request available data which may lead
|
||||
// to constant load and occasional burstiness.
|
||||
Protocol::AvailableDataFetchingV1 => 100,
|
||||
// Incoming requests can get bursty, we should also be able to handle them fast on
|
||||
// average, so something in the ballpark of 100 should be fine. Nodes will retry on
|
||||
// failure, so having a good value here is mostly about performance tuning.
|
||||
Protocol::DisputeSendingV1 => 100,
|
||||
|
||||
Protocol::AttestedCandidateV2 => {
|
||||
// We assume we can utilize up to 70% of the available bandwidth for statements.
|
||||
// This is just a guess/estimate, with the following considerations: If we are
|
||||
// faster than that, queue size will stay low anyway, even if not - requesters will
|
||||
// get an immediate error, but if we are slower, requesters will run in a timeout -
|
||||
// wasting precious time.
|
||||
let available_bandwidth = 7 * MIN_BANDWIDTH_BYTES / 10;
|
||||
let size = u64::saturating_sub(
|
||||
ATTESTED_CANDIDATE_TIMEOUT.as_millis() as u64 * available_bandwidth /
|
||||
(1000 * MAX_CODE_SIZE as u64),
|
||||
MAX_PARALLEL_ATTESTED_CANDIDATE_REQUESTS as u64,
|
||||
);
|
||||
debug_assert!(
|
||||
size > 0,
|
||||
"We should have a channel size greater zero, otherwise we won't accept any requests."
|
||||
);
|
||||
size as usize
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Legacy protocol name associated with each peer set, if any.
|
||||
/// The request will be tried on this legacy protocol name if the remote refuses to speak the
|
||||
/// protocol.
|
||||
const fn get_legacy_name(self) -> Option<&'static str> {
|
||||
match self {
|
||||
Protocol::ChunkFetchingV1 => Some("/pezkuwi/req_chunk/1"),
|
||||
Protocol::CollationFetchingV1 => Some("/pezkuwi/req_collation/1"),
|
||||
Protocol::PoVFetchingV1 => Some("/pezkuwi/req_pov/1"),
|
||||
Protocol::AvailableDataFetchingV1 => Some("/pezkuwi/req_available_data/1"),
|
||||
Protocol::DisputeSendingV1 => Some("/pezkuwi/send_dispute/1"),
|
||||
|
||||
// Introduced after legacy names became legacy.
|
||||
Protocol::AttestedCandidateV2 => None,
|
||||
Protocol::CollationFetchingV2 => None,
|
||||
Protocol::ChunkFetchingV2 => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Common properties of any `Request`.
|
||||
pub trait IsRequest {
|
||||
/// Each request has a corresponding `Response`.
|
||||
type Response;
|
||||
|
||||
/// What protocol this `Request` implements.
|
||||
const PROTOCOL: Protocol;
|
||||
}
|
||||
|
||||
/// Type for getting on the wire [`Protocol`] names using genesis hash & fork id.
|
||||
#[derive(Clone)]
|
||||
pub struct ReqProtocolNames {
|
||||
names: HashMap<Protocol, ProtocolName>,
|
||||
}
|
||||
|
||||
impl ReqProtocolNames {
|
||||
/// Construct [`ReqProtocolNames`] from `genesis_hash` and `fork_id`.
|
||||
pub fn new<Hash: AsRef<[u8]>>(genesis_hash: Hash, fork_id: Option<&str>) -> Self {
|
||||
let mut names = HashMap::new();
|
||||
for protocol in Protocol::iter() {
|
||||
names.insert(protocol, Self::generate_name(protocol, &genesis_hash, fork_id));
|
||||
}
|
||||
Self { names }
|
||||
}
|
||||
|
||||
/// Get on the wire [`Protocol`] name.
|
||||
pub fn get_name(&self, protocol: Protocol) -> ProtocolName {
|
||||
self.names
|
||||
.get(&protocol)
|
||||
.expect("All `Protocol` enum variants are added above via `strum`; qed")
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Protocol name of this protocol based on `genesis_hash` and `fork_id`.
|
||||
fn generate_name<Hash: AsRef<[u8]>>(
|
||||
protocol: Protocol,
|
||||
genesis_hash: &Hash,
|
||||
fork_id: Option<&str>,
|
||||
) -> ProtocolName {
|
||||
let prefix = if let Some(fork_id) = fork_id {
|
||||
format!("/{}/{}", hex::encode(genesis_hash), fork_id)
|
||||
} else {
|
||||
format!("/{}", hex::encode(genesis_hash))
|
||||
};
|
||||
|
||||
let short_name = match protocol {
|
||||
// V1:
|
||||
Protocol::ChunkFetchingV1 => "/req_chunk/1",
|
||||
Protocol::CollationFetchingV1 => "/req_collation/1",
|
||||
Protocol::PoVFetchingV1 => "/req_pov/1",
|
||||
Protocol::AvailableDataFetchingV1 => "/req_available_data/1",
|
||||
Protocol::DisputeSendingV1 => "/send_dispute/1",
|
||||
|
||||
// V2:
|
||||
Protocol::CollationFetchingV2 => "/req_collation/2",
|
||||
Protocol::AttestedCandidateV2 => "/req_attested_candidate/2",
|
||||
Protocol::ChunkFetchingV2 => "/req_chunk/2",
|
||||
};
|
||||
|
||||
format!("{}{}", prefix, short_name).into()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi 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.
|
||||
|
||||
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use futures::{channel::oneshot, prelude::Future, FutureExt};
|
||||
|
||||
use codec::{Decode, Encode, Error as DecodingError};
|
||||
use network::ProtocolName;
|
||||
|
||||
use sc_network as network;
|
||||
use sc_network_types::PeerId;
|
||||
|
||||
use pezkuwi_primitives::AuthorityDiscoveryId;
|
||||
|
||||
use super::{v1, v2, IsRequest, Protocol};
|
||||
|
||||
/// All requests that can be sent to the network bridge via `NetworkBridgeTxMessage::SendRequest`.
|
||||
#[derive(Debug)]
|
||||
pub enum Requests {
|
||||
/// Request an availability chunk from a node.
|
||||
ChunkFetching(OutgoingRequest<v2::ChunkFetchingRequest, v1::ChunkFetchingRequest>),
|
||||
/// Fetch a collation from a collator which previously announced it.
|
||||
CollationFetchingV1(OutgoingRequest<v1::CollationFetchingRequest>),
|
||||
/// Fetch a PoV from a validator which previously sent out a seconded statement.
|
||||
PoVFetchingV1(OutgoingRequest<v1::PoVFetchingRequest>),
|
||||
/// Request full available data from a node.
|
||||
AvailableDataFetchingV1(OutgoingRequest<v1::AvailableDataFetchingRequest>),
|
||||
/// Requests for notifying about an ongoing dispute.
|
||||
DisputeSendingV1(OutgoingRequest<v1::DisputeRequest>),
|
||||
|
||||
/// Request a candidate and attestations.
|
||||
AttestedCandidateV2(OutgoingRequest<v2::AttestedCandidateRequest>),
|
||||
/// Fetch a collation from a collator which previously announced it.
|
||||
/// Compared to V1 it requires specifying which candidate is requested by its hash.
|
||||
CollationFetchingV2(OutgoingRequest<v2::CollationFetchingRequest>),
|
||||
}
|
||||
|
||||
impl Requests {
|
||||
/// Encode the request.
|
||||
///
|
||||
/// The corresponding protocol is returned as well, as we are now leaving typed territory.
|
||||
///
|
||||
/// Note: `Requests` is just an enum collecting all supported requests supported by network
|
||||
/// bridge, it is never sent over the wire. This function just encodes the individual requests
|
||||
/// contained in the `enum`.
|
||||
pub fn encode_request(self) -> (Protocol, OutgoingRequest<Vec<u8>>) {
|
||||
match self {
|
||||
Self::ChunkFetching(r) => r.encode_request(),
|
||||
Self::CollationFetchingV1(r) => r.encode_request(),
|
||||
Self::CollationFetchingV2(r) => r.encode_request(),
|
||||
Self::PoVFetchingV1(r) => r.encode_request(),
|
||||
Self::AvailableDataFetchingV1(r) => r.encode_request(),
|
||||
Self::DisputeSendingV1(r) => r.encode_request(),
|
||||
Self::AttestedCandidateV2(r) => r.encode_request(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used by the network to send us a response to a request.
|
||||
pub type ResponseSender = oneshot::Sender<Result<(Vec<u8>, ProtocolName), network::RequestFailure>>;
|
||||
|
||||
/// Any error that can occur when sending a request.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum RequestError {
|
||||
/// Response could not be decoded.
|
||||
#[error("Response could not be decoded: {0}")]
|
||||
InvalidResponse(#[from] DecodingError),
|
||||
|
||||
/// Some error in substrate/libp2p happened.
|
||||
#[error("{0}")]
|
||||
NetworkError(#[from] network::RequestFailure),
|
||||
|
||||
/// Response got canceled by networking.
|
||||
#[error("Response channel got canceled")]
|
||||
Canceled(#[from] oneshot::Canceled),
|
||||
}
|
||||
|
||||
impl RequestError {
|
||||
/// Whether the error represents some kind of timeout condition.
|
||||
pub fn is_timed_out(&self) -> bool {
|
||||
match self {
|
||||
Self::Canceled(_) |
|
||||
Self::NetworkError(network::RequestFailure::Obsolete) |
|
||||
Self::NetworkError(network::RequestFailure::Network(
|
||||
network::OutboundFailure::Timeout,
|
||||
)) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A request to be sent to the network bridge, including a sender for sending responses/failures.
|
||||
///
|
||||
/// The network implementation will make use of that sender for informing the requesting subsystem
|
||||
/// about responses/errors.
|
||||
///
|
||||
/// When using `Recipient::Peer`, keep in mind that no address (as in IP address and port) might
|
||||
/// be known for that specific peer. You are encouraged to use `Peer` for peers that you are
|
||||
/// expected to be already connected to.
|
||||
/// When using `Recipient::Authority`, the addresses can be found thanks to the authority
|
||||
/// discovery system.
|
||||
#[derive(Debug)]
|
||||
pub struct OutgoingRequest<Req, FallbackReq = Req> {
|
||||
/// Intended recipient of this request.
|
||||
pub peer: Recipient,
|
||||
/// The actual request to send over the wire.
|
||||
pub payload: Req,
|
||||
/// Optional fallback request and protocol.
|
||||
pub fallback_request: Option<(FallbackReq, Protocol)>,
|
||||
/// Sender which is used by networking to get us back a response.
|
||||
pub pending_response: ResponseSender,
|
||||
}
|
||||
|
||||
/// 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`.
|
||||
pub type OutgoingResult<Res> = Result<Res, RequestError>;
|
||||
|
||||
impl<Req, FallbackReq> OutgoingRequest<Req, FallbackReq>
|
||||
where
|
||||
Req: IsRequest + Encode,
|
||||
Req::Response: Decode,
|
||||
FallbackReq: IsRequest + Encode,
|
||||
FallbackReq::Response: Decode,
|
||||
{
|
||||
/// Create a new `OutgoingRequest`.
|
||||
///
|
||||
/// It will contain a sender that is used by the networking for sending back responses. The
|
||||
/// connected receiver is returned as the second element in the returned tuple.
|
||||
pub fn new(
|
||||
peer: Recipient,
|
||||
payload: Req,
|
||||
) -> (Self, impl Future<Output = OutgoingResult<Req::Response>>) {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let r = Self { peer, payload, pending_response: tx, fallback_request: None };
|
||||
(r, receive_response::<Req>(rx.map(|r| r.map(|r| r.map(|(resp, _)| resp)))))
|
||||
}
|
||||
|
||||
/// Create a new `OutgoingRequest` with a fallback in case the remote does not support this
|
||||
/// protocol. Useful when adding a new version of a req-response protocol, to achieve
|
||||
/// compatibility with the older version.
|
||||
///
|
||||
/// Returns a raw `Vec<u8>` response over the channel. Use the associated `ProtocolName` to know
|
||||
/// which request was the successful one and appropriately decode the response.
|
||||
pub fn new_with_fallback(
|
||||
peer: Recipient,
|
||||
payload: Req,
|
||||
fallback_request: FallbackReq,
|
||||
) -> (Self, impl Future<Output = OutgoingResult<(Vec<u8>, ProtocolName)>>) {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let r = Self {
|
||||
peer,
|
||||
payload,
|
||||
pending_response: tx,
|
||||
fallback_request: Some((fallback_request, FallbackReq::PROTOCOL)),
|
||||
};
|
||||
(r, async { Ok(rx.await??) })
|
||||
}
|
||||
|
||||
/// Encode a request into a `Vec<u8>`.
|
||||
///
|
||||
/// As this throws away type information, we also return the `Protocol` this encoded request
|
||||
/// adheres to.
|
||||
pub fn encode_request(self) -> (Protocol, OutgoingRequest<Vec<u8>>) {
|
||||
let OutgoingRequest { peer, payload, pending_response, fallback_request } = self;
|
||||
let encoded = OutgoingRequest {
|
||||
peer,
|
||||
payload: payload.encode(),
|
||||
fallback_request: fallback_request.map(|(r, p)| (r.encode(), p)),
|
||||
pending_response,
|
||||
};
|
||||
(Req::PROTOCOL, encoded)
|
||||
}
|
||||
}
|
||||
|
||||
/// Future for actually receiving a typed response for an `OutgoingRequest`.
|
||||
async fn receive_response<Req>(
|
||||
rec: impl Future<Output = Result<Result<Vec<u8>, network::RequestFailure>, oneshot::Canceled>>,
|
||||
) -> OutgoingResult<Req::Response>
|
||||
where
|
||||
Req: IsRequest,
|
||||
Req::Response: Decode,
|
||||
{
|
||||
let raw = rec.await??;
|
||||
Ok(Decode::decode(&mut raw.as_ref())?)
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi 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.
|
||||
|
||||
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Requests and responses as sent over the wire for the individual protocols.
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
use pezkuwi_node_primitives::{
|
||||
AvailableData, DisputeMessage, ErasureChunk, PoV, Proof, UncheckedDisputeMessage,
|
||||
};
|
||||
use pezkuwi_primitives::{
|
||||
CandidateHash, CandidateReceiptV2 as CandidateReceipt, Hash, HeadData, Id as ParaId,
|
||||
ValidatorIndex,
|
||||
};
|
||||
|
||||
use super::{IsRequest, Protocol};
|
||||
|
||||
/// Request an availability chunk.
|
||||
#[derive(Debug, Copy, Clone, Encode, Decode)]
|
||||
pub struct ChunkFetchingRequest {
|
||||
/// Hash of candidate we want a chunk for.
|
||||
pub candidate_hash: CandidateHash,
|
||||
/// The validator index we are requesting from. This must be identical to the index of the
|
||||
/// chunk we'll receive. For v2, this may not be the case.
|
||||
pub index: ValidatorIndex,
|
||||
}
|
||||
|
||||
/// Receive a requested erasure chunk.
|
||||
#[derive(Debug, Clone, Encode, Decode)]
|
||||
pub enum ChunkFetchingResponse {
|
||||
/// The requested chunk data.
|
||||
#[codec(index = 0)]
|
||||
Chunk(ChunkResponse),
|
||||
/// Node was not in possession of the requested chunk.
|
||||
#[codec(index = 1)]
|
||||
NoSuchChunk,
|
||||
}
|
||||
|
||||
impl From<Option<ChunkResponse>> for ChunkFetchingResponse {
|
||||
fn from(x: Option<ChunkResponse>) -> Self {
|
||||
match x {
|
||||
Some(c) => ChunkFetchingResponse::Chunk(c),
|
||||
None => ChunkFetchingResponse::NoSuchChunk,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChunkFetchingResponse> for Option<ChunkResponse> {
|
||||
fn from(x: ChunkFetchingResponse) -> Self {
|
||||
match x {
|
||||
ChunkFetchingResponse::Chunk(c) => Some(c),
|
||||
ChunkFetchingResponse::NoSuchChunk => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Skimmed down variant of `ErasureChunk`.
|
||||
///
|
||||
/// Instead of transmitting a full `ErasureChunk` we transmit `ChunkResponse` in
|
||||
/// `ChunkFetchingResponse`, which omits the chunk's index. The index is already known by
|
||||
/// the requester and by not transmitting it, we ensure the requester is going to use his index
|
||||
/// value for validating the response, thus making sure he got what he requested.
|
||||
#[derive(Debug, Clone, Encode, Decode)]
|
||||
pub struct ChunkResponse {
|
||||
/// The erasure-encoded chunk of data belonging to the candidate block.
|
||||
pub chunk: Vec<u8>,
|
||||
/// Proof for this chunk's branch in the Merkle tree.
|
||||
pub proof: Proof,
|
||||
}
|
||||
|
||||
impl From<ErasureChunk> for ChunkResponse {
|
||||
fn from(ErasureChunk { chunk, index: _, proof }: ErasureChunk) -> Self {
|
||||
ChunkResponse { chunk, proof }
|
||||
}
|
||||
}
|
||||
|
||||
impl ChunkResponse {
|
||||
/// Re-build an `ErasureChunk` from response and request.
|
||||
pub fn recombine_into_chunk(self, req: &ChunkFetchingRequest) -> ErasureChunk {
|
||||
ErasureChunk { chunk: self.chunk, proof: self.proof, index: req.index.into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl IsRequest for ChunkFetchingRequest {
|
||||
type Response = ChunkFetchingResponse;
|
||||
const PROTOCOL: Protocol = Protocol::ChunkFetchingV1;
|
||||
}
|
||||
|
||||
/// Request the advertised collation at that relay-parent.
|
||||
#[derive(Debug, Clone, Encode, Decode)]
|
||||
pub struct CollationFetchingRequest {
|
||||
/// Relay parent we want a collation for.
|
||||
pub relay_parent: Hash,
|
||||
/// The `ParaId` of the collation.
|
||||
pub para_id: ParaId,
|
||||
}
|
||||
|
||||
/// Responses as sent by collators.
|
||||
#[derive(Debug, Clone, Encode, Decode)]
|
||||
pub enum CollationFetchingResponse {
|
||||
/// Deliver requested collation.
|
||||
#[codec(index = 0)]
|
||||
Collation(CandidateReceipt, PoV),
|
||||
|
||||
/// Deliver requested collation along with parent head data.
|
||||
#[codec(index = 1)]
|
||||
CollationWithParentHeadData {
|
||||
/// The receipt of the candidate.
|
||||
receipt: CandidateReceipt,
|
||||
/// Candidate's proof of validity.
|
||||
pov: PoV,
|
||||
/// The head data of the candidate's parent.
|
||||
/// This is needed for elastic scaling to work.
|
||||
parent_head_data: HeadData,
|
||||
},
|
||||
}
|
||||
|
||||
impl IsRequest for CollationFetchingRequest {
|
||||
type Response = CollationFetchingResponse;
|
||||
const PROTOCOL: Protocol = Protocol::CollationFetchingV1;
|
||||
}
|
||||
|
||||
/// Request the advertised collation at that relay-parent.
|
||||
#[derive(Debug, Clone, Encode, Decode)]
|
||||
pub struct PoVFetchingRequest {
|
||||
/// Candidate we want a PoV for.
|
||||
pub candidate_hash: CandidateHash,
|
||||
}
|
||||
|
||||
/// Responses to `PoVFetchingRequest`.
|
||||
#[derive(Debug, Clone, Encode, Decode)]
|
||||
pub enum PoVFetchingResponse {
|
||||
/// Deliver requested PoV.
|
||||
#[codec(index = 0)]
|
||||
PoV(PoV),
|
||||
/// PoV was not found in store.
|
||||
#[codec(index = 1)]
|
||||
NoSuchPoV,
|
||||
}
|
||||
|
||||
impl IsRequest for PoVFetchingRequest {
|
||||
type Response = PoVFetchingResponse;
|
||||
const PROTOCOL: Protocol = Protocol::PoVFetchingV1;
|
||||
}
|
||||
|
||||
/// Request the entire available data for a candidate.
|
||||
#[derive(Debug, Clone, Encode, Decode)]
|
||||
pub struct AvailableDataFetchingRequest {
|
||||
/// The candidate hash to get the available data for.
|
||||
pub candidate_hash: CandidateHash,
|
||||
}
|
||||
|
||||
/// Receive a requested available data.
|
||||
#[derive(Debug, Clone, Encode, Decode)]
|
||||
pub enum AvailableDataFetchingResponse {
|
||||
/// The requested data.
|
||||
#[codec(index = 0)]
|
||||
AvailableData(AvailableData),
|
||||
/// Node was not in possession of the requested data.
|
||||
#[codec(index = 1)]
|
||||
NoSuchData,
|
||||
}
|
||||
|
||||
impl From<Option<AvailableData>> for AvailableDataFetchingResponse {
|
||||
fn from(x: Option<AvailableData>) -> Self {
|
||||
match x {
|
||||
Some(data) => AvailableDataFetchingResponse::AvailableData(data),
|
||||
None => AvailableDataFetchingResponse::NoSuchData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IsRequest for AvailableDataFetchingRequest {
|
||||
type Response = AvailableDataFetchingResponse;
|
||||
const PROTOCOL: Protocol = Protocol::AvailableDataFetchingV1;
|
||||
}
|
||||
|
||||
/// A dispute request.
|
||||
///
|
||||
/// Contains an invalid vote a valid one for a particular candidate in a given session.
|
||||
#[derive(Clone, Encode, Decode, Debug)]
|
||||
pub struct DisputeRequest(pub UncheckedDisputeMessage);
|
||||
|
||||
impl From<DisputeMessage> for DisputeRequest {
|
||||
fn from(msg: DisputeMessage) -> Self {
|
||||
Self(msg.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Possible responses to a `DisputeRequest`.
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq)]
|
||||
pub enum DisputeResponse {
|
||||
/// Recipient successfully processed the dispute request.
|
||||
#[codec(index = 0)]
|
||||
Confirmed,
|
||||
}
|
||||
|
||||
impl IsRequest for DisputeRequest {
|
||||
type Response = DisputeResponse;
|
||||
const PROTOCOL: Protocol = Protocol::DisputeSendingV1;
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi 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.
|
||||
|
||||
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Requests and responses as sent over the wire for the individual protocols.
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
use pezkuwi_node_primitives::ErasureChunk;
|
||||
use pezkuwi_primitives::{
|
||||
CandidateHash, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, Hash, Id as ParaId,
|
||||
PersistedValidationData, UncheckedSignedStatement, ValidatorIndex,
|
||||
};
|
||||
|
||||
use super::{v1, IsRequest, Protocol};
|
||||
use crate::v3::StatementFilter;
|
||||
|
||||
/// Request a candidate with statements.
|
||||
#[derive(Debug, Clone, Encode, Decode)]
|
||||
pub struct AttestedCandidateRequest {
|
||||
/// Hash of the candidate we want to request.
|
||||
pub candidate_hash: CandidateHash,
|
||||
/// Statement filter with 'OR' semantics, indicating which validators
|
||||
/// not to send statements for.
|
||||
///
|
||||
/// The filter must have exactly the minimum size required to
|
||||
/// fit all validators from the backing group.
|
||||
///
|
||||
/// The response may not contain any statements masked out by this mask.
|
||||
pub mask: StatementFilter,
|
||||
}
|
||||
|
||||
/// Response to an `AttestedCandidateRequest`.
|
||||
#[derive(Debug, Clone, Encode, Decode)]
|
||||
pub struct AttestedCandidateResponse {
|
||||
/// The candidate receipt, with commitments.
|
||||
pub candidate_receipt: CommittedCandidateReceipt,
|
||||
/// The [`PersistedValidationData`] corresponding to the candidate.
|
||||
pub persisted_validation_data: PersistedValidationData,
|
||||
/// All known statements about the candidate, in compact form,
|
||||
/// omitting `Seconded` statements which were intended to be masked
|
||||
/// out.
|
||||
pub statements: Vec<UncheckedSignedStatement>,
|
||||
}
|
||||
|
||||
impl IsRequest for AttestedCandidateRequest {
|
||||
type Response = AttestedCandidateResponse;
|
||||
const PROTOCOL: Protocol = Protocol::AttestedCandidateV2;
|
||||
}
|
||||
|
||||
/// Responses as sent by collators.
|
||||
pub type CollationFetchingResponse = super::v1::CollationFetchingResponse;
|
||||
|
||||
/// Request the advertised collation at that relay-parent.
|
||||
#[derive(Debug, Clone, Encode, Decode)]
|
||||
pub struct CollationFetchingRequest {
|
||||
/// Relay parent collation is built on top of.
|
||||
pub relay_parent: Hash,
|
||||
/// The `ParaId` of the collation.
|
||||
pub para_id: ParaId,
|
||||
/// Candidate hash.
|
||||
pub candidate_hash: CandidateHash,
|
||||
}
|
||||
|
||||
impl IsRequest for CollationFetchingRequest {
|
||||
// The response is the same as for V1.
|
||||
type Response = CollationFetchingResponse;
|
||||
const PROTOCOL: Protocol = Protocol::CollationFetchingV2;
|
||||
}
|
||||
|
||||
/// Request an availability chunk.
|
||||
#[derive(Debug, Copy, Clone, Encode, Decode)]
|
||||
pub struct ChunkFetchingRequest {
|
||||
/// Hash of candidate we want a chunk for.
|
||||
pub candidate_hash: CandidateHash,
|
||||
/// The validator index we are requesting from. This may not be identical to the index of the
|
||||
/// chunk we'll receive. It's up to the caller to decide whether they need to validate they got
|
||||
/// the chunk they were expecting.
|
||||
pub index: ValidatorIndex,
|
||||
}
|
||||
|
||||
/// Receive a requested erasure chunk.
|
||||
#[derive(Debug, Clone, Encode, Decode)]
|
||||
pub enum ChunkFetchingResponse {
|
||||
/// The requested chunk data.
|
||||
#[codec(index = 0)]
|
||||
Chunk(ErasureChunk),
|
||||
/// Node was not in possession of the requested chunk.
|
||||
#[codec(index = 1)]
|
||||
NoSuchChunk,
|
||||
}
|
||||
|
||||
impl From<Option<ErasureChunk>> for ChunkFetchingResponse {
|
||||
fn from(x: Option<ErasureChunk>) -> Self {
|
||||
match x {
|
||||
Some(c) => ChunkFetchingResponse::Chunk(c),
|
||||
None => ChunkFetchingResponse::NoSuchChunk,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChunkFetchingResponse> for Option<ErasureChunk> {
|
||||
fn from(x: ChunkFetchingResponse) -> Self {
|
||||
match x {
|
||||
ChunkFetchingResponse::Chunk(c) => Some(c),
|
||||
ChunkFetchingResponse::NoSuchChunk => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v1::ChunkFetchingRequest> for ChunkFetchingRequest {
|
||||
fn from(v1::ChunkFetchingRequest { candidate_hash, index }: v1::ChunkFetchingRequest) -> Self {
|
||||
Self { candidate_hash, index }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChunkFetchingRequest> for v1::ChunkFetchingRequest {
|
||||
fn from(ChunkFetchingRequest { candidate_hash, index }: ChunkFetchingRequest) -> Self {
|
||||
Self { candidate_hash, index }
|
||||
}
|
||||
}
|
||||
|
||||
impl IsRequest for ChunkFetchingRequest {
|
||||
type Response = ChunkFetchingResponse;
|
||||
const PROTOCOL: Protocol = Protocol::ChunkFetchingV2;
|
||||
}
|
||||
Reference in New Issue
Block a user