feat: Rebrand Polkadot/Substrate references to PezkuwiChain

This commit systematically rebrands various references from Parity Technologies'
Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk.

Key changes include:
- Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks.
- Modified internal documentation and code comments to reflect PezkuwiChain naming and structure.
- Replaced direct references to  with  or specific paths within the  for XCM, Pezkuwi, and other modules.
- Cleaned up deprecated  issue and PR references in various  and  files, particularly in  and  modules.
- Adjusted image and logo URLs in documentation to point to PezkuwiChain assets.
- Removed or rephrased comments related to external Polkadot/Substrate PRs and issues.

This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
@@ -0,0 +1,47 @@
[package]
description = "Gossiping for the Bizinikiwi network protocol"
name = "pezsc-network-gossip"
version = "0.34.0"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
authors.workspace = true
edition.workspace = true
homepage.workspace = true
repository.workspace = true
documentation = "https://docs.rs/pezsc-network-gossip"
readme = "README.md"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
ahash = { workspace = true }
futures = { workspace = true }
futures-timer = { workspace = true }
log = { workspace = true, default-features = true }
prometheus-endpoint = { workspace = true, default-features = true }
pezsc-network = { workspace = true, default-features = true }
pezsc-network-common = { workspace = true, default-features = true }
pezsc-network-sync = { workspace = true, default-features = true }
pezsc-network-types = { workspace = true, default-features = true }
schnellru = { workspace = true }
pezsp-runtime = { workspace = true, default-features = true }
tracing = { workspace = true, default-features = true }
[dev-dependencies]
async-trait = { workspace = true }
codec = { features = ["derive"], workspace = true, default-features = true }
quickcheck = { workspace = true }
bizinikiwi-test-runtime-client = { workspace = true }
tokio = { workspace = true, default-features = true }
[features]
runtime-benchmarks = [
"pezsc-network-common/runtime-benchmarks",
"pezsc-network-sync/runtime-benchmarks",
"pezsc-network/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"bizinikiwi-test-runtime-client/runtime-benchmarks",
]
@@ -0,0 +1,41 @@
Polite gossiping.
This crate provides gossiping capabilities on top of a network.
Gossip messages are separated by two categories: "topics" and consensus engine ID.
The consensus engine ID is sent over the wire with the message, while the topic is not,
with the expectation that the topic can be derived implicitly from the content of the
message, assuming it is valid.
Topics are a single 32-byte tag associated with a message, used to group those messages
in an opaque way. Consensus code can invoke `broadcast_topic` to attempt to send all messages
under a single topic to all peers who don't have them yet, and `send_topic` to
send all messages under a single topic to a specific peer.
# Usage
- Implement the `Network` trait, representing the low-level networking primitives. It is
already implemented on `sc_network::NetworkService`.
- Implement the `Validator` trait. See the section below.
- Decide on a `ConsensusEngineId`. Each gossiping protocol should have a different one.
- Build a `GossipEngine` using these three elements.
- Use the methods of the `GossipEngine` in order to send out messages and receive incoming
messages.
# What is a validator?
The primary role of a `Validator` is to process incoming messages from peers, and decide
whether to discard them or process them. It also decides whether to re-broadcast the message.
The secondary role of the `Validator` is to check if a message is allowed to be sent to a given
peer. All messages, before being sent, will be checked against this filter.
This enables the validator to use information it's aware of about connected peers to decide
whether to send messages to them at any given moment in time - In particular, to wait until
peers can accept and process the message before sending it.
Lastly, the fact that gossip validators can decide not to rebroadcast messages
opens the door for neighbor status packets to be baked into the gossip protocol.
These status packets will typically contain light pieces of information
used to inform peers of a current view of protocol state.
License: GPL-3.0-or-later WITH Classpath-exception-2.0
@@ -0,0 +1,890 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
state_machine::{ConsensusGossip, TopicNotification, PERIODIC_MAINTENANCE_INTERVAL},
Network, Syncing, Validator,
};
use pezsc_network::{
service::traits::{NotificationEvent, ValidationResult},
types::ProtocolName,
NotificationService, ReputationChange,
};
use pezsc_network_sync::SyncEvent;
use futures::{
channel::mpsc::{channel, Receiver, Sender},
prelude::*,
};
use log::trace;
use prometheus_endpoint::Registry;
use pezsc_network_types::PeerId;
use pezsp_runtime::traits::Block as BlockT;
use std::{
collections::{HashMap, VecDeque},
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
/// Wraps around an implementation of the [`Network`] trait and provides gossiping capabilities on
/// top of it.
pub struct GossipEngine<B: BlockT> {
state_machine: ConsensusGossip<B>,
network: Box<dyn Network<B> + Send>,
sync: Box<dyn Syncing<B>>,
periodic_maintenance_interval: futures_timer::Delay,
protocol: ProtocolName,
/// Incoming events from the syncing service.
sync_event_stream: Pin<Box<dyn Stream<Item = SyncEvent> + Send>>,
/// Handle for polling notification-related events.
notification_service: Box<dyn NotificationService>,
/// Outgoing events to the consumer.
message_sinks: HashMap<B::Hash, Vec<Sender<TopicNotification>>>,
/// Buffered messages (see [`ForwardingState`]).
forwarding_state: ForwardingState<B>,
is_terminated: bool,
}
/// A gossip engine receives messages from the network via the `network_event_stream` and forwards
/// them to upper layers via the `message_sinks`. In the scenario where messages have been received
/// from the network but a subscribed message sink is not yet ready to receive the messages, the
/// messages are buffered. To model this process a gossip engine can be in two states.
enum ForwardingState<B: BlockT> {
/// The gossip engine is currently not forwarding any messages and will poll the network for
/// more messages to forward.
Idle,
/// The gossip engine is in the progress of forwarding messages and thus will not poll the
/// network for more messages until it has send all current messages into the subscribed
/// message sinks.
Busy(VecDeque<(B::Hash, TopicNotification)>),
}
impl<B: BlockT> Unpin for GossipEngine<B> {}
impl<B: BlockT> GossipEngine<B> {
/// Create a new instance.
pub fn new<N, S>(
network: N,
sync: S,
notification_service: Box<dyn NotificationService>,
protocol: impl Into<ProtocolName>,
validator: Arc<dyn Validator<B>>,
metrics_registry: Option<&Registry>,
) -> Self
where
B: 'static,
N: Network<B> + Send + Clone + 'static,
S: Syncing<B> + Send + Clone + 'static,
{
let protocol = protocol.into();
let sync_event_stream = sync.event_stream("network-gossip");
GossipEngine {
state_machine: ConsensusGossip::new(validator, protocol.clone(), metrics_registry),
network: Box::new(network),
sync: Box::new(sync),
notification_service,
periodic_maintenance_interval: futures_timer::Delay::new(PERIODIC_MAINTENANCE_INTERVAL),
protocol,
sync_event_stream,
message_sinks: HashMap::new(),
forwarding_state: ForwardingState::Idle,
is_terminated: false,
}
}
pub fn report(&self, who: PeerId, reputation: ReputationChange) {
self.network.report_peer(who, reputation);
}
/// Registers a message without propagating it to any peers. The message
/// becomes available to new peers or when the service is asked to gossip
/// the message's topic. No validation is performed on the message, if the
/// message is already expired it should be dropped on the next garbage
/// collection.
pub fn register_gossip_message(&mut self, topic: B::Hash, message: Vec<u8>) {
self.state_machine.register_message(topic, message);
}
/// Broadcast all messages with given topic.
pub fn broadcast_topic(&mut self, topic: B::Hash, force: bool) {
self.state_machine.broadcast_topic(&mut self.notification_service, topic, force);
}
/// Get data of valid, incoming messages for a topic (but might have expired meanwhile).
pub fn messages_for(&mut self, topic: B::Hash) -> Receiver<TopicNotification> {
let past_messages = self.state_machine.messages_for(topic).collect::<Vec<_>>();
// The channel length is not critical for correctness. By the implementation of `channel`
// each sender is guaranteed a single buffer slot, making it a non-rendezvous channel and
// thus preventing direct dead-locks. A minimum channel length of 10 is an estimate based on
// the fact that despite `NotificationsReceived` having a `Vec` of messages, it only ever
// contains a single message.
let (mut tx, rx) = channel(usize::max(past_messages.len(), 10));
for notification in past_messages {
tx.try_send(notification)
.expect("receiver known to be live, and buffer size known to suffice; qed");
}
self.message_sinks.entry(topic).or_default().push(tx);
rx
}
/// Send all messages with given topic to a peer.
pub fn send_topic(&mut self, who: &PeerId, topic: B::Hash, force: bool) {
self.state_machine.send_topic(&mut self.notification_service, who, topic, force)
}
/// Multicast a message to all peers.
pub fn gossip_message(&mut self, topic: B::Hash, message: Vec<u8>, force: bool) {
self.state_machine
.multicast(&mut self.notification_service, topic, message, force)
}
/// Send addressed message to the given peers. The message is not kept or multicast
/// later on.
pub fn send_message(&mut self, who: Vec<PeerId>, data: Vec<u8>) {
for who in &who {
self.state_machine
.send_message(&mut self.notification_service, who, data.clone());
}
}
/// Notify everyone we're connected to that we have the given block.
///
/// Note: this method isn't strictly related to gossiping and should eventually be moved
/// somewhere else.
pub fn announce(&self, block: B::Hash, associated_data: Option<Vec<u8>>) {
self.sync.announce_block(block, associated_data);
}
/// Consume [`GossipEngine`] and return the notification service.
pub fn take_notification_service(self) -> Box<dyn NotificationService> {
self.notification_service
}
}
impl<B: BlockT> Future for GossipEngine<B> {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let this = &mut *self;
'outer: loop {
match &mut this.forwarding_state {
ForwardingState::Idle => {
let next_notification_event =
this.notification_service.next_event().poll_unpin(cx);
let sync_event_stream = this.sync_event_stream.poll_next_unpin(cx);
if next_notification_event.is_pending() && sync_event_stream.is_pending() {
break;
}
match next_notification_event {
Poll::Ready(Some(event)) => match event {
NotificationEvent::ValidateInboundSubstream {
peer,
handshake,
result_tx,
..
} => {
// only accept peers whose role can be determined
let result = this
.network
.peer_role(peer, handshake)
.map_or(ValidationResult::Reject, |_| ValidationResult::Accept);
let _ = result_tx.send(result);
},
NotificationEvent::NotificationStreamOpened {
peer, handshake, ..
} =>
if let Some(role) = this.network.peer_role(peer, handshake) {
this.state_machine.new_peer(
&mut this.notification_service,
peer,
role,
);
} else {
log::debug!(target: "gossip", "role for {peer} couldn't be determined");
},
NotificationEvent::NotificationStreamClosed { peer } => {
this.state_machine
.peer_disconnected(&mut this.notification_service, peer);
},
NotificationEvent::NotificationReceived { peer, notification } => {
let to_forward = this.state_machine.on_incoming(
&mut *this.network,
&mut this.notification_service,
peer,
vec![notification],
);
this.forwarding_state = ForwardingState::Busy(to_forward.into());
},
},
// The network event stream closed. Do the same for [`GossipValidator`].
Poll::Ready(None) => {
self.is_terminated = true;
return Poll::Ready(());
},
Poll::Pending => {},
}
match sync_event_stream {
Poll::Ready(Some(event)) => match event {
SyncEvent::PeerConnected(remote) =>
this.network.add_set_reserved(remote, this.protocol.clone()),
SyncEvent::PeerDisconnected(remote) =>
this.network.remove_set_reserved(remote, this.protocol.clone()),
},
// The sync event stream closed. Do the same for [`GossipValidator`].
Poll::Ready(None) => {
self.is_terminated = true;
return Poll::Ready(());
},
Poll::Pending => {},
}
},
ForwardingState::Busy(to_forward) => {
let (topic, notification) = match to_forward.pop_front() {
Some(n) => n,
None => {
this.forwarding_state = ForwardingState::Idle;
continue;
},
};
let sinks = match this.message_sinks.get_mut(&topic) {
Some(sinks) => sinks,
None => continue,
};
// Make sure all sinks for the given topic are ready.
for sink in sinks.iter_mut() {
match sink.poll_ready(cx) {
Poll::Ready(Ok(())) => {},
// Receiver has been dropped. Ignore for now, filtered out in (1).
Poll::Ready(Err(_)) => {},
Poll::Pending => {
// Push back onto queue for later.
to_forward.push_front((topic, notification));
break 'outer;
},
}
}
// Filter out all closed sinks.
sinks.retain(|sink| !sink.is_closed()); // (1)
if sinks.is_empty() {
this.message_sinks.remove(&topic);
continue;
}
trace!(
target: "gossip",
"Pushing consensus message to sinks for {}.", topic,
);
// Send the notification on each sink.
for sink in sinks {
match sink.start_send(notification.clone()) {
Ok(()) => {},
Err(e) if e.is_full() => {
unreachable!("Previously ensured that all sinks are ready; qed.")
},
// Receiver got dropped. Will be removed in next iteration (See (1)).
Err(_) => {},
}
}
},
}
}
while let Poll::Ready(()) = this.periodic_maintenance_interval.poll_unpin(cx) {
this.periodic_maintenance_interval.reset(PERIODIC_MAINTENANCE_INTERVAL);
this.state_machine.tick(&mut this.notification_service);
this.message_sinks.retain(|_, sinks| {
sinks.retain(|sink| !sink.is_closed());
!sinks.is_empty()
});
}
Poll::Pending
}
}
impl<B: BlockT> futures::future::FusedFuture for GossipEngine<B> {
fn is_terminated(&self) -> bool {
self.is_terminated
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{ValidationResult, ValidatorContext};
use codec::{DecodeAll, Encode};
use futures::{
channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender},
executor::{block_on, block_on_stream},
future::poll_fn,
};
use quickcheck::{Arbitrary, Gen, QuickCheck};
use pezsc_network::{
config::MultiaddrWithPeerId,
service::traits::{Direction, MessageSink, NotificationEvent},
Event, NetworkBlock, NetworkEventStream, NetworkPeers, NotificationService, Roles,
};
use pezsc_network_common::role::ObservedRole;
use pezsc_network_sync::SyncEventStream;
use pezsc_network_types::multiaddr::Multiaddr;
use pezsp_runtime::{
testing::H256,
traits::{Block as BlockT, NumberFor},
};
use std::{
collections::HashSet,
sync::{Arc, Mutex},
};
use bizinikiwi_test_runtime_client::runtime::Block;
#[derive(Clone, Default)]
struct TestNetwork {}
#[async_trait::async_trait]
impl NetworkPeers for TestNetwork {
fn set_authorized_peers(&self, _peers: HashSet<PeerId>) {
unimplemented!();
}
fn set_authorized_only(&self, _reserved_only: bool) {
unimplemented!();
}
fn add_known_address(&self, _peer_id: PeerId, _addr: Multiaddr) {
unimplemented!();
}
fn report_peer(&self, _peer_id: PeerId, _cost_benefit: ReputationChange) {}
fn peer_reputation(&self, _peer_id: &PeerId) -> i32 {
unimplemented!()
}
fn disconnect_peer(&self, _peer_id: PeerId, _protocol: ProtocolName) {
unimplemented!();
}
fn accept_unreserved_peers(&self) {
unimplemented!();
}
fn deny_unreserved_peers(&self) {
unimplemented!();
}
fn add_reserved_peer(&self, _peer: MultiaddrWithPeerId) -> Result<(), String> {
unimplemented!();
}
fn remove_reserved_peer(&self, _peer_id: PeerId) {
unimplemented!();
}
fn set_reserved_peers(
&self,
_protocol: ProtocolName,
_peers: HashSet<Multiaddr>,
) -> Result<(), String> {
unimplemented!();
}
fn add_peers_to_reserved_set(
&self,
_protocol: ProtocolName,
_peers: HashSet<Multiaddr>,
) -> Result<(), String> {
unimplemented!();
}
fn remove_peers_from_reserved_set(
&self,
_protocol: ProtocolName,
_peers: Vec<PeerId>,
) -> Result<(), String> {
unimplemented!();
}
fn sync_num_connected(&self) -> usize {
unimplemented!();
}
fn peer_role(&self, _peer_id: PeerId, handshake: Vec<u8>) -> Option<ObservedRole> {
Roles::decode_all(&mut &handshake[..])
.ok()
.and_then(|role| Some(ObservedRole::from(role)))
}
async fn reserved_peers(&self) -> Result<Vec<PeerId>, ()> {
unimplemented!();
}
}
impl NetworkEventStream for TestNetwork {
fn event_stream(&self, _name: &'static str) -> Pin<Box<dyn Stream<Item = Event> + Send>> {
unimplemented!();
}
}
impl NetworkBlock<<Block as BlockT>::Hash, NumberFor<Block>> for TestNetwork {
fn announce_block(&self, _hash: <Block as BlockT>::Hash, _data: Option<Vec<u8>>) {
unimplemented!();
}
fn new_best_block_imported(
&self,
_hash: <Block as BlockT>::Hash,
_number: NumberFor<Block>,
) {
unimplemented!();
}
}
#[derive(Clone, Default)]
struct TestSync {
inner: Arc<Mutex<TestSyncInner>>,
}
#[derive(Clone, Default)]
struct TestSyncInner {
event_senders: Vec<UnboundedSender<SyncEvent>>,
}
impl SyncEventStream for TestSync {
fn event_stream(
&self,
_name: &'static str,
) -> Pin<Box<dyn Stream<Item = SyncEvent> + Send>> {
let (tx, rx) = unbounded();
self.inner.lock().unwrap().event_senders.push(tx);
Box::pin(rx)
}
}
impl NetworkBlock<<Block as BlockT>::Hash, NumberFor<Block>> for TestSync {
fn announce_block(&self, _hash: <Block as BlockT>::Hash, _data: Option<Vec<u8>>) {
unimplemented!();
}
fn new_best_block_imported(
&self,
_hash: <Block as BlockT>::Hash,
_number: NumberFor<Block>,
) {
unimplemented!();
}
}
#[derive(Debug)]
pub(crate) struct TestNotificationService {
rx: UnboundedReceiver<NotificationEvent>,
}
#[async_trait::async_trait]
impl pezsc_network::service::traits::NotificationService for TestNotificationService {
async fn open_substream(&mut self, _peer: PeerId) -> Result<(), ()> {
unimplemented!();
}
async fn close_substream(&mut self, _peer: PeerId) -> Result<(), ()> {
unimplemented!();
}
fn send_sync_notification(&mut self, _peer: &PeerId, _notification: Vec<u8>) {
unimplemented!();
}
async fn send_async_notification(
&mut self,
_peer: &PeerId,
_notification: Vec<u8>,
) -> Result<(), pezsc_network::error::Error> {
unimplemented!();
}
async fn set_handshake(&mut self, _handshake: Vec<u8>) -> Result<(), ()> {
unimplemented!();
}
fn try_set_handshake(&mut self, _handshake: Vec<u8>) -> Result<(), ()> {
unimplemented!();
}
async fn next_event(&mut self) -> Option<NotificationEvent> {
self.rx.next().await
}
fn clone(&mut self) -> Result<Box<dyn NotificationService>, ()> {
unimplemented!();
}
fn protocol(&self) -> &ProtocolName {
unimplemented!();
}
fn message_sink(&self, _peer: &PeerId) -> Option<Box<dyn MessageSink>> {
unimplemented!();
}
}
struct AllowAll;
impl Validator<Block> for AllowAll {
fn validate(
&self,
_context: &mut dyn ValidatorContext<Block>,
_sender: &PeerId,
_data: &[u8],
) -> ValidationResult<H256> {
ValidationResult::ProcessAndKeep(H256::default())
}
}
/// Regression test for the case where the `GossipEngine.network_event_stream` closes. One
/// should not ignore a `Poll::Ready(None)` as `poll_next_unpin` will panic on subsequent calls.
///
/// See https://github.com/pezkuwichain/kurdistan-sdk/issues/25 for details.
#[test]
fn returns_when_network_event_stream_closes() {
let network = TestNetwork::default();
let sync = Arc::new(TestSync::default());
let (tx, rx) = unbounded();
let notification_service = Box::new(TestNotificationService { rx });
let mut gossip_engine = GossipEngine::<Block>::new(
network.clone(),
sync,
notification_service,
"/my_protocol",
Arc::new(AllowAll {}),
None,
);
// drop notification service sender side.
drop(tx);
block_on(poll_fn(move |ctx| {
if let Poll::Pending = gossip_engine.poll_unpin(ctx) {
panic!(
"Expected gossip engine to finish on first poll, given that \
`GossipEngine.network_event_stream` closes right away."
)
}
Poll::Ready(())
}))
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn keeps_multiple_subscribers_per_topic_updated_with_both_old_and_new_messages() {
let topic = H256::default();
let protocol = ProtocolName::from("/my_protocol");
let remote_peer = PeerId::random();
let network = TestNetwork::default();
let sync = Arc::new(TestSync::default());
let (mut tx, rx) = unbounded();
let notification_service = Box::new(TestNotificationService { rx });
let mut gossip_engine = GossipEngine::<Block>::new(
network.clone(),
sync.clone(),
notification_service,
protocol.clone(),
Arc::new(AllowAll {}),
None,
);
// Register the remote peer.
tx.send(NotificationEvent::NotificationStreamOpened {
peer: remote_peer,
direction: Direction::Inbound,
negotiated_fallback: None,
handshake: Roles::FULL.encode(),
})
.await
.unwrap();
let messages = vec![vec![1], vec![2]];
// Send first event before subscribing.
tx.send(NotificationEvent::NotificationReceived {
peer: remote_peer,
notification: messages[0].clone().into(),
})
.await
.unwrap();
let mut subscribers = vec![];
for _ in 0..2 {
subscribers.push(gossip_engine.messages_for(topic));
}
// Send second event after subscribing.
tx.send(NotificationEvent::NotificationReceived {
peer: remote_peer,
notification: messages[1].clone().into(),
})
.await
.unwrap();
tokio::spawn(gossip_engine);
// Note: `block_on_stream()`-derived iterator will block the current thread,
// so we need a `multi_thread` `tokio::test` runtime flavor.
let mut subscribers =
subscribers.into_iter().map(|s| block_on_stream(s)).collect::<Vec<_>>();
// Expect each subscriber to receive both events.
for message in messages {
for subscriber in subscribers.iter_mut() {
assert_eq!(
subscriber.next(),
Some(TopicNotification { message: message.clone(), sender: Some(remote_peer) }),
);
}
}
}
#[test]
fn forwarding_to_different_size_and_topic_channels() {
#[derive(Clone, Debug)]
struct ChannelLengthAndTopic {
length: usize,
topic: H256,
}
impl Arbitrary for ChannelLengthAndTopic {
fn arbitrary(g: &mut Gen) -> Self {
let possible_length = (0..100).collect::<Vec<usize>>();
let possible_topics = (0..10).collect::<Vec<u64>>();
Self {
length: *g.choose(&possible_length).unwrap(),
// Make sure channel topics and message topics overlap by choosing a small
// range.
topic: H256::from_low_u64_ne(*g.choose(&possible_topics).unwrap()),
}
}
}
#[derive(Clone, Debug)]
struct Message {
topic: H256,
}
impl Arbitrary for Message {
fn arbitrary(g: &mut Gen) -> Self {
let possible_topics = (0..10).collect::<Vec<u64>>();
Self {
// Make sure channel topics and message topics overlap by choosing a small
// range.
topic: H256::from_low_u64_ne(*g.choose(&possible_topics).unwrap()),
}
}
}
/// Validator that always returns `ProcessAndKeep` interpreting the first 32 bytes of data
/// as the message topic.
struct TestValidator;
impl Validator<Block> for TestValidator {
fn validate(
&self,
_context: &mut dyn ValidatorContext<Block>,
_sender: &PeerId,
data: &[u8],
) -> ValidationResult<H256> {
ValidationResult::ProcessAndKeep(H256::from_slice(&data[0..32]))
}
}
fn prop(channels: Vec<ChannelLengthAndTopic>, notifications: Vec<Vec<Message>>) {
let protocol = ProtocolName::from("/my_protocol");
let remote_peer = PeerId::random();
let network = TestNetwork::default();
let sync = Arc::new(TestSync::default());
let (mut tx, rx) = unbounded();
let notification_service = Box::new(TestNotificationService { rx });
let num_channels_per_topic = channels.iter().fold(
HashMap::new(),
|mut acc, ChannelLengthAndTopic { topic, .. }| {
acc.entry(topic).and_modify(|e| *e += 1).or_insert(1);
acc
},
);
let expected_msgs_per_topic_all_chan = notifications
.iter()
.fold(HashMap::new(), |mut acc, messages| {
for message in messages {
acc.entry(message.topic).and_modify(|e| *e += 1).or_insert(1);
}
acc
})
.into_iter()
// Messages are cloned for each channel with the corresponding topic, thus multiply
// with the amount of channels per topic. If there is no channel for a given topic,
// don't expect any messages for the topic to be received.
.map(|(topic, num)| (topic, num_channels_per_topic.get(&topic).unwrap_or(&0) * num))
.collect::<HashMap<H256, _>>();
let mut gossip_engine = GossipEngine::<Block>::new(
network.clone(),
sync.clone(),
notification_service,
protocol.clone(),
Arc::new(TestValidator {}),
None,
);
// Create channels.
let (txs, mut rxs) = channels
.iter()
.map(|ChannelLengthAndTopic { length, topic }| (*topic, channel(*length)))
.fold((vec![], vec![]), |mut acc, (topic, (tx, rx))| {
acc.0.push((topic, tx));
acc.1.push((topic, rx));
acc
});
// Insert sender sides into `gossip_engine`.
for (topic, tx) in txs {
match gossip_engine.message_sinks.get_mut(&topic) {
Some(entry) => entry.push(tx),
None => {
gossip_engine.message_sinks.insert(topic, vec![tx]);
},
}
}
// Register the remote peer.
tx.start_send(NotificationEvent::NotificationStreamOpened {
peer: remote_peer,
direction: Direction::Inbound,
negotiated_fallback: None,
handshake: Roles::FULL.encode(),
})
.unwrap();
// Send messages into the network event stream.
for (i_notification, messages) in notifications.iter().enumerate() {
let messages: Vec<Vec<u8>> = messages
.into_iter()
.enumerate()
.map(|(i_message, Message { topic })| {
// Embed the topic in the first 256 bytes of the message to be extracted by
// the [`TestValidator`] later on.
let mut message = topic.as_bytes().to_vec();
// Make sure the message is unique via `i_notification` and `i_message` to
// ensure [`ConsensusBridge`] does not deduplicate it.
message.push(i_notification.try_into().unwrap());
message.push(i_message.try_into().unwrap());
message.into()
})
.collect();
for message in messages {
tx.start_send(NotificationEvent::NotificationReceived {
peer: remote_peer,
notification: message,
})
.unwrap();
}
}
let mut received_msgs_per_topic_all_chan = HashMap::<H256, _>::new();
// Poll both gossip engine and each receiver and track the amount of received messages.
block_on(poll_fn(|cx| {
loop {
if let Poll::Ready(()) = gossip_engine.poll_unpin(cx) {
unreachable!(
"Event stream sender side is not dropped, thus gossip engine does not \
terminate",
);
}
let mut progress = false;
for (topic, rx) in rxs.iter_mut() {
match rx.poll_next_unpin(cx) {
Poll::Ready(Some(_)) => {
progress = true;
received_msgs_per_topic_all_chan
.entry(*topic)
.and_modify(|e| *e += 1)
.or_insert(1);
},
Poll::Ready(None) => {
unreachable!("Sender side of channel is never dropped")
},
Poll::Pending => {},
}
}
if !progress {
break;
}
}
Poll::Ready(())
}));
// Compare amount of expected messages with amount of received messages.
for (expected_topic, expected_num) in expected_msgs_per_topic_all_chan.iter() {
assert_eq!(
received_msgs_per_topic_all_chan.get(&expected_topic).unwrap_or(&0),
expected_num,
);
}
for (received_topic, received_num) in expected_msgs_per_topic_all_chan.iter() {
assert_eq!(
expected_msgs_per_topic_all_chan.get(&received_topic).unwrap_or(&0),
received_num,
);
}
}
// Past regressions.
prop(vec![], vec![vec![Message { topic: H256::default() }]]);
prop(
vec![ChannelLengthAndTopic { length: 71, topic: H256::default() }],
vec![vec![Message { topic: H256::default() }]],
);
QuickCheck::new().quickcheck(prop as fn(_, _))
}
}
+105
View File
@@ -0,0 +1,105 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Polite gossiping.
//!
//! This crate provides gossiping capabilities on top of a network.
//!
//! Gossip messages are separated by two categories: "topics" and consensus engine ID.
//! The consensus engine ID is sent over the wire with the message, while the topic is not,
//! with the expectation that the topic can be derived implicitly from the content of the
//! message, assuming it is valid.
//!
//! Topics are a single 32-byte tag associated with a message, used to group those messages
//! in an opaque way. Consensus code can invoke [`ValidatorContext::broadcast_topic`] to
//! attempt to send all messages under a single topic to all peers who don't have them yet, and
//! [`ValidatorContext::send_topic`] to send all messages under a single topic to a specific peer.
//!
//! # Usage
//!
//! - Implement the [`Network`] trait, representing the low-level networking primitives. It is
//! already implemented on `pezsc_network::NetworkService`.
//! - Implement the [`Validator`] trait. See the section below.
//! - Decide on a protocol name. Each gossiping protocol should have a different one.
//! - Build a [`GossipEngine`] using these three elements.
//! - Use the methods of the [`GossipEngine`] in order to send out messages and receive incoming
//! messages.
//!
//! The [`GossipEngine`] will automatically use [`Network::add_set_reserved`] and
//! [`NetworkPeers::remove_peers_from_reserved_set`] to maintain a set of peers equal to the set of
//! peers the node is syncing from. See the documentation of `sc-network` for more explanations
//! about the concepts of peer sets.
//!
//! # What is a validator?
//!
//! The primary role of a [`Validator`] is to process incoming messages from peers, and decide
//! whether to discard them or process them. It also decides whether to re-broadcast the message.
//!
//! The secondary role of the [`Validator`] is to check if a message is allowed to be sent to a
//! given peer. All messages, before being sent, will be checked against this filter.
//! This enables the validator to use information it's aware of about connected peers to decide
//! whether to send messages to them at any given moment in time - In particular, to wait until
//! peers can accept and process the message before sending it.
//!
//! Lastly, the fact that gossip validators can decide not to rebroadcast messages
//! opens the door for neighbor status packets to be baked into the gossip protocol.
//! These status packets will typically contain light pieces of information
//! used to inform peers of a current view of protocol state.
pub use self::{
bridge::GossipEngine,
state_machine::TopicNotification,
validator::{DiscardAll, MessageIntent, ValidationResult, Validator, ValidatorContext},
};
use pezsc_network::{types::ProtocolName, NetworkBlock, NetworkEventStream, NetworkPeers};
use pezsc_network_sync::SyncEventStream;
use pezsc_network_types::{
multiaddr::{Multiaddr, Protocol},
PeerId,
};
use pezsp_runtime::traits::{Block as BlockT, NumberFor};
use std::iter;
mod bridge;
mod state_machine;
mod validator;
/// Abstraction over a network.
pub trait Network<B: BlockT>: NetworkPeers + NetworkEventStream {
fn add_set_reserved(&self, who: PeerId, protocol: ProtocolName) {
let addr = Multiaddr::empty().with(Protocol::P2p(*who.as_ref()));
let result = self.add_peers_to_reserved_set(protocol, iter::once(addr).collect());
if let Err(err) = result {
log::error!(target: "gossip", "add_set_reserved failed: {}", err);
}
}
fn remove_set_reserved(&self, who: PeerId, protocol: ProtocolName) {
let result = self.remove_peers_from_reserved_set(protocol, iter::once(who).collect());
if let Err(err) = result {
log::error!(target: "gossip", "remove_set_reserved failed: {}", err);
}
}
}
impl<T, B: BlockT> Network<B> for T where T: NetworkPeers + NetworkEventStream {}
/// Abstraction over the syncing subsystem.
pub trait Syncing<B: BlockT>: SyncEventStream + NetworkBlock<B::Hash, NumberFor<B>> {}
impl<T, B: BlockT> Syncing<B> for T where T: SyncEventStream + NetworkBlock<B::Hash, NumberFor<B>> {}
@@ -0,0 +1,927 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{MessageIntent, Network, ValidationResult, Validator, ValidatorContext};
use ahash::AHashSet;
use pezsc_network_types::PeerId;
use schnellru::{ByLength, LruMap};
use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64};
use pezsc_network::{types::ProtocolName, NotificationService};
use pezsc_network_common::role::ObservedRole;
use pezsp_runtime::traits::{Block as BlockT, Hash, HashingFor};
use std::{collections::HashMap, iter, sync::Arc, time, time::Instant};
// FIXME: Add additional spam/DoS attack protection: https://github.com/pezkuwichain/kurdistan-sdk/issues/7
// NOTE: The current value is adjusted based on largest production network deployment (Kusama) and
// the current main gossip user (GRANDPA). Currently there are ~800 validators on Kusama, as such,
// each GRANDPA round should generate ~1600 messages, and we currently keep track of the last 2
// completed rounds and the current live one. That makes it so that at any point we will be holding
// ~4800 live messages.
//
// Assuming that each known message is tracked with a 32 byte hash (common for `Block::Hash`), then
// this cache should take about 256 KB of memory.
const KNOWN_MESSAGES_CACHE_SIZE: u32 = 8192;
const REBROADCAST_INTERVAL: time::Duration = time::Duration::from_millis(750);
pub(crate) const PERIODIC_MAINTENANCE_INTERVAL: time::Duration = time::Duration::from_millis(1100);
mod rep {
use pezsc_network::ReputationChange as Rep;
/// Reputation change when a peer sends us a gossip message that we didn't know about.
pub const GOSSIP_SUCCESS: Rep = Rep::new(1 << 4, "Successful gossip");
/// Reputation change when a peer sends us a gossip message that we already knew about.
pub const DUPLICATE_GOSSIP: Rep = Rep::new(-(1 << 2), "Duplicate gossip");
}
struct PeerConsensus<H> {
known_messages: AHashSet<H>,
}
/// Topic stream message with sender.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TopicNotification {
/// Message data.
pub message: Vec<u8>,
/// Sender if available.
pub sender: Option<PeerId>,
}
struct MessageEntry<B: BlockT> {
message_hash: B::Hash,
topic: B::Hash,
message: Vec<u8>,
sender: Option<PeerId>,
}
/// Local implementation of `ValidatorContext`.
struct NetworkContext<'g, 'p, B: BlockT> {
gossip: &'g mut ConsensusGossip<B>,
notification_service: &'p mut Box<dyn NotificationService>,
}
impl<'g, 'p, B: BlockT> ValidatorContext<B> for NetworkContext<'g, 'p, B> {
/// Broadcast all messages with given topic to peers that do not have it yet.
fn broadcast_topic(&mut self, topic: B::Hash, force: bool) {
self.gossip.broadcast_topic(self.notification_service, topic, force);
}
/// Broadcast a message to all peers that have not received it previously.
fn broadcast_message(&mut self, topic: B::Hash, message: Vec<u8>, force: bool) {
self.gossip.multicast(self.notification_service, topic, message, force);
}
/// Send addressed message to a peer.
fn send_message(&mut self, who: &PeerId, message: Vec<u8>) {
self.notification_service.send_sync_notification(who, message);
}
/// Send all messages with given topic to a peer.
fn send_topic(&mut self, who: &PeerId, topic: B::Hash, force: bool) {
self.gossip.send_topic(self.notification_service, who, topic, force);
}
}
fn propagate<'a, B: BlockT, I>(
notification_service: &mut Box<dyn NotificationService>,
protocol: ProtocolName,
messages: I,
intent: MessageIntent,
peers: &mut HashMap<PeerId, PeerConsensus<B::Hash>>,
validator: &Arc<dyn Validator<B>>,
)
// (msg_hash, topic, message)
where
I: Clone + IntoIterator<Item = (&'a B::Hash, &'a B::Hash, &'a Vec<u8>)>,
{
let mut message_allowed = validator.message_allowed();
for (id, ref mut peer) in peers.iter_mut() {
for (message_hash, topic, message) in messages.clone() {
let intent = match intent {
MessageIntent::Broadcast { .. } =>
if peer.known_messages.contains(message_hash) {
continue;
} else {
MessageIntent::Broadcast
},
MessageIntent::PeriodicRebroadcast => {
if peer.known_messages.contains(message_hash) {
MessageIntent::PeriodicRebroadcast
} else {
// peer doesn't know message, so the logic should treat it as an
// initial broadcast.
MessageIntent::Broadcast
}
},
other => other,
};
if !message_allowed(id, intent, topic, message) {
continue;
}
peer.known_messages.insert(*message_hash);
tracing::trace!(
target: "gossip",
to = %id,
%protocol,
?message,
"Propagating message",
);
notification_service.send_sync_notification(id, message.clone());
}
}
}
/// Consensus network protocol handler. Manages statements and candidate requests.
pub struct ConsensusGossip<B: BlockT> {
peers: HashMap<PeerId, PeerConsensus<B::Hash>>,
messages: Vec<MessageEntry<B>>,
known_messages: LruMap<B::Hash, ()>,
protocol: ProtocolName,
validator: Arc<dyn Validator<B>>,
next_broadcast: Instant,
metrics: Option<Metrics>,
}
impl<B: BlockT> ConsensusGossip<B> {
/// Create a new instance using the given validator.
pub fn new(
validator: Arc<dyn Validator<B>>,
protocol: ProtocolName,
metrics_registry: Option<&Registry>,
) -> Self {
let metrics = match metrics_registry.map(Metrics::register) {
Some(Ok(metrics)) => Some(metrics),
Some(Err(e)) => {
tracing::debug!(target: "gossip", "Failed to register metrics: {:?}", e);
None
},
None => None,
};
ConsensusGossip {
peers: HashMap::new(),
messages: Default::default(),
known_messages: { LruMap::new(ByLength::new(KNOWN_MESSAGES_CACHE_SIZE)) },
protocol,
validator,
next_broadcast: Instant::now() + REBROADCAST_INTERVAL,
metrics,
}
}
/// Handle new connected peer.
pub fn new_peer(
&mut self,
notification_service: &mut Box<dyn NotificationService>,
who: PeerId,
role: ObservedRole,
) {
tracing::trace!(
target:"gossip",
%who,
protocol = %self.protocol,
?role,
"Registering peer",
);
self.peers.insert(who, PeerConsensus { known_messages: Default::default() });
let validator = self.validator.clone();
let mut context = NetworkContext { gossip: self, notification_service };
validator.new_peer(&mut context, &who, role);
}
fn register_message_hashed(
&mut self,
message_hash: B::Hash,
topic: B::Hash,
message: Vec<u8>,
sender: Option<PeerId>,
) {
if self.known_messages.insert(message_hash, ()) {
self.messages.push(MessageEntry { message_hash, topic, message, sender });
if let Some(ref metrics) = self.metrics {
metrics.registered_messages.inc();
}
}
}
/// Registers a message without propagating it to any peers. The message
/// becomes available to new peers or when the service is asked to gossip
/// the message's topic. No validation is performed on the message, if the
/// message is already expired it should be dropped on the next garbage
/// collection.
pub fn register_message(&mut self, topic: B::Hash, message: Vec<u8>) {
let message_hash = HashingFor::<B>::hash(&message[..]);
self.register_message_hashed(message_hash, topic, message, None);
}
/// Call when a peer has been disconnected to stop tracking gossip status.
pub fn peer_disconnected(
&mut self,
notification_service: &mut Box<dyn NotificationService>,
who: PeerId,
) {
let validator = self.validator.clone();
let mut context = NetworkContext { gossip: self, notification_service };
validator.peer_disconnected(&mut context, &who);
self.peers.remove(&who);
}
/// Perform periodic maintenance
pub fn tick(&mut self, notification_service: &mut Box<dyn NotificationService>) {
self.collect_garbage();
if Instant::now() >= self.next_broadcast {
self.rebroadcast(notification_service);
self.next_broadcast = Instant::now() + REBROADCAST_INTERVAL;
}
}
/// Rebroadcast all messages to all peers.
fn rebroadcast(&mut self, notification_service: &mut Box<dyn NotificationService>) {
let messages = self
.messages
.iter()
.map(|entry| (&entry.message_hash, &entry.topic, &entry.message));
propagate(
notification_service,
self.protocol.clone(),
messages,
MessageIntent::PeriodicRebroadcast,
&mut self.peers,
&self.validator,
);
}
/// Broadcast all messages with given topic.
pub fn broadcast_topic(
&mut self,
notification_service: &mut Box<dyn NotificationService>,
topic: B::Hash,
force: bool,
) {
let messages = self.messages.iter().filter_map(|entry| {
if entry.topic == topic {
Some((&entry.message_hash, &entry.topic, &entry.message))
} else {
None
}
});
let intent = if force { MessageIntent::ForcedBroadcast } else { MessageIntent::Broadcast };
propagate(
notification_service,
self.protocol.clone(),
messages,
intent,
&mut self.peers,
&self.validator,
);
}
/// Prune old or no longer relevant consensus messages. Provide a predicate
/// for pruning, which returns `false` when the items with a given topic should be pruned.
pub fn collect_garbage(&mut self) {
let known_messages = &mut self.known_messages;
let before = self.messages.len();
let mut message_expired = self.validator.message_expired();
self.messages.retain(|entry| !message_expired(entry.topic, &entry.message));
let expired_messages = before - self.messages.len();
if let Some(ref metrics) = self.metrics {
metrics.expired_messages.inc_by(expired_messages as u64)
}
tracing::trace!(
target: "gossip",
protocol = %self.protocol,
"Cleaned up {} stale messages, {} left ({} known)",
expired_messages,
self.messages.len(),
known_messages.len(),
);
for (_, ref mut peer) in self.peers.iter_mut() {
peer.known_messages.retain(|h| known_messages.get(h).is_some());
}
}
/// Get valid messages received in the past for a topic (might have expired meanwhile).
pub fn messages_for(&mut self, topic: B::Hash) -> impl Iterator<Item = TopicNotification> + '_ {
self.messages
.iter()
.filter(move |e| e.topic == topic)
.map(|entry| TopicNotification { message: entry.message.clone(), sender: entry.sender })
}
/// Register incoming messages and return the ones that are new and valid (according to a gossip
/// validator) and should thus be forwarded to the upper layers.
pub fn on_incoming(
&mut self,
network: &mut dyn Network<B>,
notification_service: &mut Box<dyn NotificationService>,
who: PeerId,
messages: Vec<Vec<u8>>,
) -> Vec<(B::Hash, TopicNotification)> {
let mut to_forward = vec![];
if !messages.is_empty() {
tracing::trace!(
target: "gossip",
messages_num = %messages.len(),
%who,
protocol = %self.protocol,
"Received messages from peer",
);
}
for message in messages {
let message_hash = HashingFor::<B>::hash(&message[..]);
if self.known_messages.get(&message_hash).is_some() {
tracing::trace!(
target: "gossip",
%who,
protocol = %self.protocol,
"Ignored already known message",
);
// If the peer already send us the message once, let's report them.
if self
.peers
.get_mut(&who)
.map_or(false, |p| !p.known_messages.insert(message_hash))
{
network.report_peer(who, rep::DUPLICATE_GOSSIP);
}
continue;
}
// validate the message
let validation = {
let validator = self.validator.clone();
let mut context = NetworkContext { gossip: self, notification_service };
validator.validate(&mut context, &who, &message)
};
let (topic, keep) = match validation {
ValidationResult::ProcessAndKeep(topic) => (topic, true),
ValidationResult::ProcessAndDiscard(topic) => (topic, false),
ValidationResult::Discard => {
tracing::trace!(
target: "gossip",
%who,
protocol = %self.protocol,
"Discard message from peer",
);
continue;
},
};
let peer = match self.peers.get_mut(&who) {
Some(peer) => peer,
None => {
tracing::error!(
target: "gossip",
%who,
protocol = %self.protocol,
"Got message from unregistered peer",
);
continue;
},
};
network.report_peer(who, rep::GOSSIP_SUCCESS);
peer.known_messages.insert(message_hash);
to_forward
.push((topic, TopicNotification { message: message.clone(), sender: Some(who) }));
if keep {
self.register_message_hashed(message_hash, topic, message, Some(who));
}
}
to_forward
}
/// Send all messages with given topic to a peer.
pub fn send_topic(
&mut self,
notification_service: &mut Box<dyn NotificationService>,
who: &PeerId,
topic: B::Hash,
force: bool,
) {
let mut message_allowed = self.validator.message_allowed();
if let Some(ref mut peer) = self.peers.get_mut(who) {
for entry in self.messages.iter().filter(|m| m.topic == topic) {
let intent =
if force { MessageIntent::ForcedBroadcast } else { MessageIntent::Broadcast };
if !force && peer.known_messages.contains(&entry.message_hash) {
continue;
}
if !message_allowed(who, intent, &entry.topic, &entry.message) {
continue;
}
peer.known_messages.insert(entry.message_hash);
tracing::trace!(
target: "gossip",
to = %who,
protocol = %self.protocol,
?entry.message,
"Sending topic message",
);
notification_service.send_sync_notification(who, entry.message.clone());
}
}
}
/// Multicast a message to all peers.
pub fn multicast(
&mut self,
notification_service: &mut Box<dyn NotificationService>,
topic: B::Hash,
message: Vec<u8>,
force: bool,
) {
let message_hash = HashingFor::<B>::hash(&message);
self.register_message_hashed(message_hash, topic, message.clone(), None);
let intent = if force { MessageIntent::ForcedBroadcast } else { MessageIntent::Broadcast };
propagate(
notification_service,
self.protocol.clone(),
iter::once((&message_hash, &topic, &message)),
intent,
&mut self.peers,
&self.validator,
);
}
/// Send addressed message to a peer. The message is not kept or multicast
/// later on.
pub fn send_message(
&mut self,
notification_service: &mut Box<dyn NotificationService>,
who: &PeerId,
message: Vec<u8>,
) {
let peer = match self.peers.get_mut(who) {
None => return,
Some(peer) => peer,
};
let message_hash = HashingFor::<B>::hash(&message);
tracing::trace!(
target: "gossip",
to = %who,
protocol = %self.protocol,
?message,
"Sending direct message",
);
peer.known_messages.insert(message_hash);
notification_service.send_sync_notification(who, message)
}
}
struct Metrics {
registered_messages: Counter<U64>,
expired_messages: Counter<U64>,
}
impl Metrics {
fn register(registry: &Registry) -> Result<Self, PrometheusError> {
Ok(Self {
registered_messages: register(
Counter::new(
"bizinikiwi_network_gossip_registered_messages_total",
"Number of registered messages by the gossip service.",
)?,
registry,
)?,
expired_messages: register(
Counter::new(
"bizinikiwi_network_gossip_expired_messages_total",
"Number of expired messages by the gossip service.",
)?,
registry,
)?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures::prelude::*;
use pezsc_network::{
config::MultiaddrWithPeerId, event::Event, service::traits::NotificationEvent, MessageSink,
NetworkBlock, NetworkEventStream, NetworkPeers, ReputationChange,
};
use pezsc_network_types::multiaddr::Multiaddr;
use pezsp_runtime::{
testing::{Block as RawBlock, MockCallU64, TestXt, H256},
traits::NumberFor,
};
use std::{
collections::HashSet,
pin::Pin,
sync::{Arc, Mutex},
};
type Block = RawBlock<TestXt<MockCallU64, ()>>;
macro_rules! push_msg {
($consensus:expr, $topic:expr, $hash: expr, $m:expr) => {
if $consensus.known_messages.insert($hash, ()) {
$consensus.messages.push(MessageEntry {
message_hash: $hash,
topic: $topic,
message: $m,
sender: None,
});
}
};
}
struct AllowAll;
impl Validator<Block> for AllowAll {
fn validate(
&self,
_context: &mut dyn ValidatorContext<Block>,
_sender: &PeerId,
_data: &[u8],
) -> ValidationResult<H256> {
ValidationResult::ProcessAndKeep(H256::default())
}
}
struct DiscardAll;
impl Validator<Block> for DiscardAll {
fn validate(
&self,
_context: &mut dyn ValidatorContext<Block>,
_sender: &PeerId,
_data: &[u8],
) -> ValidationResult<H256> {
ValidationResult::Discard
}
}
#[derive(Clone, Default)]
struct NoOpNetwork {
inner: Arc<Mutex<NoOpNetworkInner>>,
}
#[derive(Clone, Default)]
struct NoOpNetworkInner {
peer_reports: Vec<(PeerId, ReputationChange)>,
}
#[async_trait::async_trait]
impl NetworkPeers for NoOpNetwork {
fn set_authorized_peers(&self, _peers: HashSet<PeerId>) {
unimplemented!();
}
fn set_authorized_only(&self, _reserved_only: bool) {
unimplemented!();
}
fn add_known_address(&self, _peer_id: PeerId, _addr: Multiaddr) {
unimplemented!();
}
fn report_peer(&self, peer_id: PeerId, cost_benefit: ReputationChange) {
self.inner.lock().unwrap().peer_reports.push((peer_id, cost_benefit));
}
fn peer_reputation(&self, _peer_id: &PeerId) -> i32 {
unimplemented!()
}
fn disconnect_peer(&self, _peer_id: PeerId, _protocol: ProtocolName) {
unimplemented!();
}
fn accept_unreserved_peers(&self) {
unimplemented!();
}
fn deny_unreserved_peers(&self) {
unimplemented!();
}
fn add_reserved_peer(&self, _peer: MultiaddrWithPeerId) -> Result<(), String> {
unimplemented!();
}
fn remove_reserved_peer(&self, _peer_id: PeerId) {
unimplemented!();
}
fn set_reserved_peers(
&self,
_protocol: ProtocolName,
_peers: HashSet<Multiaddr>,
) -> Result<(), String> {
unimplemented!();
}
fn add_peers_to_reserved_set(
&self,
_protocol: ProtocolName,
_peers: HashSet<Multiaddr>,
) -> Result<(), String> {
unimplemented!();
}
fn remove_peers_from_reserved_set(
&self,
_protocol: ProtocolName,
_peers: Vec<PeerId>,
) -> Result<(), String> {
unimplemented!();
}
fn sync_num_connected(&self) -> usize {
unimplemented!();
}
fn peer_role(&self, _peer_id: PeerId, _handshake: Vec<u8>) -> Option<ObservedRole> {
None
}
async fn reserved_peers(&self) -> Result<Vec<PeerId>, ()> {
unimplemented!();
}
}
impl NetworkEventStream for NoOpNetwork {
fn event_stream(&self, _name: &'static str) -> Pin<Box<dyn Stream<Item = Event> + Send>> {
unimplemented!();
}
}
impl NetworkBlock<<Block as BlockT>::Hash, NumberFor<Block>> for NoOpNetwork {
fn announce_block(&self, _hash: <Block as BlockT>::Hash, _data: Option<Vec<u8>>) {
unimplemented!();
}
fn new_best_block_imported(
&self,
_hash: <Block as BlockT>::Hash,
_number: NumberFor<Block>,
) {
unimplemented!();
}
}
#[derive(Debug, Default)]
struct NoOpNotificationService {}
#[async_trait::async_trait]
impl NotificationService for NoOpNotificationService {
/// Instruct `Notifications` to open a new substream for `peer`.
async fn open_substream(&mut self, _peer: PeerId) -> Result<(), ()> {
unimplemented!();
}
/// Instruct `Notifications` to close substream for `peer`.
async fn close_substream(&mut self, _peer: PeerId) -> Result<(), ()> {
unimplemented!();
}
/// Send synchronous `notification` to `peer`.
fn send_sync_notification(&mut self, _peer: &PeerId, _notification: Vec<u8>) {
unimplemented!();
}
/// Send asynchronous `notification` to `peer`, allowing sender to exercise backpressure.
async fn send_async_notification(
&mut self,
_peer: &PeerId,
_notification: Vec<u8>,
) -> Result<(), pezsc_network::error::Error> {
unimplemented!();
}
/// Set handshake for the notification protocol replacing the old handshake.
async fn set_handshake(&mut self, _handshake: Vec<u8>) -> Result<(), ()> {
unimplemented!();
}
fn try_set_handshake(&mut self, _handshake: Vec<u8>) -> Result<(), ()> {
unimplemented!();
}
/// Get next event from the `Notifications` event stream.
async fn next_event(&mut self) -> Option<NotificationEvent> {
None
}
fn clone(&mut self) -> Result<Box<dyn NotificationService>, ()> {
unimplemented!();
}
fn protocol(&self) -> &ProtocolName {
unimplemented!();
}
fn message_sink(&self, _peer: &PeerId) -> Option<Box<dyn MessageSink>> {
unimplemented!();
}
}
#[test]
fn collects_garbage() {
struct AllowOne;
impl Validator<Block> for AllowOne {
fn validate(
&self,
_context: &mut dyn ValidatorContext<Block>,
_sender: &PeerId,
data: &[u8],
) -> ValidationResult<H256> {
if data[0] == 1 {
ValidationResult::ProcessAndKeep(H256::default())
} else {
ValidationResult::Discard
}
}
fn message_expired<'a>(&'a self) -> Box<dyn FnMut(H256, &[u8]) -> bool + 'a> {
Box::new(move |_topic, data| data[0] != 1)
}
}
let prev_hash = H256::random();
let best_hash = H256::random();
let mut consensus = ConsensusGossip::<Block>::new(Arc::new(AllowAll), "/foo".into(), None);
let m1_hash = H256::random();
let m2_hash = H256::random();
let m1 = vec![1, 2, 3];
let m2 = vec![4, 5, 6];
push_msg!(consensus, prev_hash, m1_hash, m1);
push_msg!(consensus, best_hash, m2_hash, m2);
consensus.known_messages.insert(m1_hash, ());
consensus.known_messages.insert(m2_hash, ());
consensus.collect_garbage();
assert_eq!(consensus.messages.len(), 2);
assert_eq!(consensus.known_messages.len(), 2);
consensus.validator = Arc::new(AllowOne);
// m2 is expired
consensus.collect_garbage();
assert_eq!(consensus.messages.len(), 1);
// known messages are only pruned based on size.
assert_eq!(consensus.known_messages.len(), 2);
assert!(consensus.known_messages.get(&m2_hash).is_some());
}
#[test]
fn message_stream_include_those_sent_before_asking() {
let mut consensus = ConsensusGossip::<Block>::new(Arc::new(AllowAll), "/foo".into(), None);
// Register message.
let message = vec![4, 5, 6];
let topic = HashingFor::<Block>::hash(&[1, 2, 3]);
consensus.register_message(topic, message.clone());
assert_eq!(
consensus.messages_for(topic).next(),
Some(TopicNotification { message, sender: None }),
);
}
#[test]
fn can_keep_multiple_messages_per_topic() {
let mut consensus = ConsensusGossip::<Block>::new(Arc::new(AllowAll), "/foo".into(), None);
let topic = [1; 32].into();
let msg_a = vec![1, 2, 3];
let msg_b = vec![4, 5, 6];
consensus.register_message(topic, msg_a);
consensus.register_message(topic, msg_b);
assert_eq!(consensus.messages.len(), 2);
}
#[test]
fn peer_is_removed_on_disconnect() {
let mut consensus = ConsensusGossip::<Block>::new(Arc::new(AllowAll), "/foo".into(), None);
let mut notification_service: Box<dyn NotificationService> =
Box::new(NoOpNotificationService::default());
let peer_id = PeerId::random();
consensus.new_peer(&mut notification_service, peer_id, ObservedRole::Full);
assert!(consensus.peers.contains_key(&peer_id));
consensus.peer_disconnected(&mut notification_service, peer_id);
assert!(!consensus.peers.contains_key(&peer_id));
}
#[test]
fn on_incoming_ignores_discarded_messages() {
let mut notification_service: Box<dyn NotificationService> =
Box::new(NoOpNotificationService::default());
let to_forward = ConsensusGossip::<Block>::new(Arc::new(DiscardAll), "/foo".into(), None)
.on_incoming(
&mut NoOpNetwork::default(),
&mut notification_service,
PeerId::random(),
vec![vec![1, 2, 3]],
);
assert!(
to_forward.is_empty(),
"Expected `on_incoming` to ignore discarded message but got {:?}",
to_forward,
);
}
#[test]
fn on_incoming_ignores_unregistered_peer() {
let mut network = NoOpNetwork::default();
let mut notification_service: Box<dyn NotificationService> =
Box::new(NoOpNotificationService::default());
let remote = PeerId::random();
let to_forward = ConsensusGossip::<Block>::new(Arc::new(AllowAll), "/foo".into(), None)
.on_incoming(
&mut network,
&mut notification_service,
// Unregistered peer.
remote,
vec![vec![1, 2, 3]],
);
assert!(
to_forward.is_empty(),
"Expected `on_incoming` to ignore message from unregistered peer but got {:?}",
to_forward,
);
}
// Two peers can send us the same gossip message. We should not report the second peer
// sending the gossip message as long as its the first time the peer send us this message.
#[test]
fn do_not_report_peer_for_first_time_duplicate_gossip_message() {
let mut consensus = ConsensusGossip::<Block>::new(Arc::new(AllowAll), "/foo".into(), None);
let mut network = NoOpNetwork::default();
let mut notification_service: Box<dyn NotificationService> =
Box::new(NoOpNotificationService::default());
let peer_id = PeerId::random();
consensus.new_peer(&mut notification_service, peer_id, ObservedRole::Full);
assert!(consensus.peers.contains_key(&peer_id));
let peer_id2 = PeerId::random();
consensus.new_peer(&mut notification_service, peer_id2, ObservedRole::Full);
assert!(consensus.peers.contains_key(&peer_id2));
let message = vec![vec![1, 2, 3]];
consensus.on_incoming(&mut network, &mut notification_service, peer_id, message.clone());
consensus.on_incoming(&mut network, &mut notification_service, peer_id2, message.clone());
assert_eq!(
vec![(peer_id, rep::GOSSIP_SUCCESS)],
network.inner.lock().unwrap().peer_reports
);
}
}
@@ -0,0 +1,109 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use pezsc_network_common::role::ObservedRole;
use pezsc_network_types::PeerId;
use pezsp_runtime::traits::Block as BlockT;
/// Validates consensus messages.
pub trait Validator<B: BlockT>: Send + Sync {
/// New peer is connected.
fn new_peer(&self, _context: &mut dyn ValidatorContext<B>, _who: &PeerId, _role: ObservedRole) {
}
/// New connection is dropped.
fn peer_disconnected(&self, _context: &mut dyn ValidatorContext<B>, _who: &PeerId) {}
/// Validate consensus message.
fn validate(
&self,
context: &mut dyn ValidatorContext<B>,
sender: &PeerId,
data: &[u8],
) -> ValidationResult<B::Hash>;
/// Produce a closure for validating messages on a given topic.
fn message_expired<'a>(&'a self) -> Box<dyn FnMut(B::Hash, &[u8]) -> bool + 'a> {
Box::new(move |_topic, _data| false)
}
/// Produce a closure for filtering egress messages.
fn message_allowed<'a>(
&'a self,
) -> Box<dyn FnMut(&PeerId, MessageIntent, &B::Hash, &[u8]) -> bool + 'a> {
Box::new(move |_who, _intent, _topic, _data| true)
}
}
/// Validation context. Allows reacting to incoming messages by sending out further messages.
pub trait ValidatorContext<B: BlockT> {
/// Broadcast all messages with given topic to peers that do not have it yet.
fn broadcast_topic(&mut self, topic: B::Hash, force: bool);
/// Broadcast a message to all peers that have not received it previously.
fn broadcast_message(&mut self, topic: B::Hash, message: Vec<u8>, force: bool);
/// Send addressed message to a peer.
fn send_message(&mut self, who: &PeerId, message: Vec<u8>);
/// Send all messages with given topic to a peer.
fn send_topic(&mut self, who: &PeerId, topic: B::Hash, force: bool);
}
/// The reason for sending out the message.
#[derive(Eq, PartialEq, Copy, Clone)]
#[cfg_attr(test, derive(Debug))]
pub enum MessageIntent {
/// Requested broadcast.
Broadcast,
/// Requested broadcast to all peers.
ForcedBroadcast,
/// Periodic rebroadcast of all messages to all peers.
PeriodicRebroadcast,
}
/// Message validation result.
pub enum ValidationResult<H> {
/// Message should be stored and propagated under given topic.
ProcessAndKeep(H),
/// Message should be processed, but not propagated.
ProcessAndDiscard(H),
/// Message should be ignored.
Discard,
}
/// A gossip message validator that discards all messages.
pub struct DiscardAll;
impl<B: BlockT> Validator<B> for DiscardAll {
fn validate(
&self,
_context: &mut dyn ValidatorContext<B>,
_sender: &PeerId,
_data: &[u8],
) -> ValidationResult<B::Hash> {
ValidationResult::Discard
}
fn message_expired<'a>(&'a self) -> Box<dyn FnMut(B::Hash, &[u8]) -> bool + 'a> {
Box::new(move |_topic, _data| true)
}
fn message_allowed<'a>(
&'a self,
) -> Box<dyn FnMut(&PeerId, MessageIntent, &B::Hash, &[u8]) -> bool + 'a> {
Box::new(move |_who, _intent, _topic, _data| false)
}
}