diff --git a/Cargo.toml b/Cargo.toml index f6f7f67e61..ab18c83da1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,9 +43,6 @@ members = [ exclude = [ "mm2src/adex_cli", - "mm2src/floodsub", - "mm2src/gossipsub", - "mm2src/mm2_libp2p", "mm2src/mm2_test_helpers", ] diff --git a/mm2src/floodsub/CHANGELOG.md b/mm2src/floodsub/CHANGELOG.md deleted file mode 100644 index b361796860..0000000000 --- a/mm2src/floodsub/CHANGELOG.md +++ /dev/null @@ -1,15 +0,0 @@ -# 0.22.0 [2020-09-09] - -- Update `libp2p-swarm` and `libp2p-core`. - -# 0.21.0 [2020-08-18] - -- Bump `libp2p-core` and `libp2p-swarm` dependency. - -# 0.20.0 [2020-07-01] - -- Updated dependencies. - -# 0.19.1 [2020-06-22] - -- Updated dependencies. diff --git a/mm2src/floodsub/Cargo.toml b/mm2src/floodsub/Cargo.toml deleted file mode 100644 index b54b60448c..0000000000 --- a/mm2src/floodsub/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "libp2p-floodsub" -edition = "2018" -description = "Floodsub protocol for libp2p" -version = "0.22.0" -authors = ["Parity Technologies "] -license = "MIT" -repository = "https://github.com/libp2p/rust-libp2p" -keywords = ["peer-to-peer", "libp2p", "networking"] -categories = ["network-programming", "asynchronous"] - -[lib] -doctest = false - -[dependencies] -cuckoofilter = "0.3.2" -futures = "0.3.1" -libp2p-core = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45.1" } -libp2p-swarm = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45.1" } -prost = "0.10" -rand = "0.7" -smallvec = "1.0" - -[build-dependencies] -prost-build = { version = "0.10.4", default-features = false } diff --git a/mm2src/floodsub/build.rs b/mm2src/floodsub/build.rs deleted file mode 100644 index 9671983699..0000000000 --- a/mm2src/floodsub/build.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -fn main() { prost_build::compile_protos(&["src/rpc.proto"], &["src"]).unwrap(); } diff --git a/mm2src/floodsub/src/layer.rs b/mm2src/floodsub/src/layer.rs deleted file mode 100644 index 7151948a95..0000000000 --- a/mm2src/floodsub/src/layer.rs +++ /dev/null @@ -1,401 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::protocol::{FloodsubMessage, FloodsubProtocol, FloodsubRpc, FloodsubSubscription, FloodsubSubscriptionAction}; -use crate::topic::Topic; -use crate::FloodsubConfig; -use cuckoofilter::CuckooFilter; -use libp2p_core::{connection::ConnectionId, ConnectedPoint, Multiaddr, PeerId}; -use libp2p_swarm::{IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, OneShotHandler, - PollParameters}; -use smallvec::SmallVec; -use std::collections::hash_map::{DefaultHasher, HashMap}; -use std::task::{Context, Poll}; -use std::{collections::VecDeque, iter}; - -/// Network behaviour that handles the floodsub protocol. -pub struct Floodsub { - /// Events that need to be yielded to the outside when polling. - events: - VecDeque>>, - - config: FloodsubConfig, - - /// List of peers the network is connected to, and the topics that they're subscribed to. - // TODO: filter out peers that don't support floodsub, so that we avoid hammering them with - // opened substreams - connected_peers: HashMap>, - - // List of topics we're subscribed to. Necessary to filter out messages that we receive - // erroneously. - subscribed_topics: SmallVec<[Topic; 16]>, - - // We keep track of the messages we received (in the format `hash(source ID, seq_no)`) so that - // we don't dispatch the same message twice if we receive it twice on the network. - received: CuckooFilter, -} - -impl Floodsub { - /// Creates a `Floodsub` with default configuration. - pub fn new(local_peer_id: PeerId, forward_messages: bool) -> Self { - Self::from_config(FloodsubConfig::new(local_peer_id, forward_messages)) - } - - /// Creates a `Floodsub` with the given configuration. - pub fn from_config(config: FloodsubConfig) -> Self { - Floodsub { - events: VecDeque::new(), - config, - connected_peers: HashMap::new(), - subscribed_topics: SmallVec::new(), - received: CuckooFilter::new(), - } - } - - /// Subscribes to a topic. - /// - /// Returns true if the subscription worked. Returns false if we were already subscribed. - pub fn subscribe(&mut self, topic: Topic) -> bool { - if self.subscribed_topics.iter().any(|t| t.id() == topic.id()) { - return false; - } - - for peer in self.connected_peers.keys() { - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: *peer, - handler: NotifyHandler::Any, - event: FloodsubRpc { - messages: Vec::new(), - subscriptions: vec![FloodsubSubscription { - topic: topic.clone(), - action: FloodsubSubscriptionAction::Subscribe, - }], - }, - }); - } - - self.subscribed_topics.push(topic); - true - } - - /// Unsubscribes from a topic. - /// - /// Note that this only requires the topic name. - /// - /// Returns true if we were subscribed to this topic. - pub fn unsubscribe(&mut self, topic: Topic) -> bool { - let pos = match self.subscribed_topics.iter().position(|t| *t == topic) { - Some(pos) => pos, - None => return false, - }; - - self.subscribed_topics.remove(pos); - - for peer in self.connected_peers.keys() { - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: *peer, - handler: NotifyHandler::Any, - event: FloodsubRpc { - messages: Vec::new(), - subscriptions: vec![FloodsubSubscription { - topic: topic.clone(), - action: FloodsubSubscriptionAction::Unsubscribe, - }], - }, - }); - } - - true - } - - /// Publishes a message to the network, if we're subscribed to the topic only. - pub fn publish(&mut self, topic: impl Into, data: impl Into>) { - self.publish_many(iter::once(topic), data) - } - - /// Publishes a message to the network, even if we're not subscribed to the topic. - pub fn publish_any(&mut self, topic: impl Into, data: impl Into>) { - self.publish_many_any(iter::once(topic), data) - } - - /// Publishes a message with multiple topics to the network. - /// - /// - /// > **Note**: Doesn't do anything if we're not subscribed to any of the topics. - pub fn publish_many(&mut self, topic: impl IntoIterator>, data: impl Into>) { - self.publish_many_inner(topic, data, true) - } - - /// Publishes a message with multiple topics to the network, even if we're not subscribed to any of the topics. - pub fn publish_many_any(&mut self, topic: impl IntoIterator>, data: impl Into>) { - self.publish_many_inner(topic, data, false) - } - - fn publish_many_inner( - &mut self, - topic: impl IntoIterator>, - data: impl Into>, - check_self_subscriptions: bool, - ) { - let message = FloodsubMessage { - source: self.config.local_peer_id, - data: data.into(), - // If the sequence numbers are predictable, then an attacker could flood the network - // with packets with the predetermined sequence numbers and absorb our legitimate - // messages. We therefore use a random number. - sequence_number: rand::random::<[u8; 20]>().to_vec(), - topics: topic.into_iter().map(Into::into).collect(), - }; - - let self_subscribed = self - .subscribed_topics - .iter() - .any(|t| message.topics.iter().any(|u| t == u)); - if self_subscribed { - self.received.add(&message); - if self.config.subscribe_local_messages { - self.events - .push_back(NetworkBehaviourAction::GenerateEvent(FloodsubEvent::Message( - message.clone(), - ))); - } - } - // Don't publish the message if we have to check subscriptions - // and we're not subscribed ourselves to any of the topics. - if check_self_subscriptions && !self_subscribed { - return; - } - - // Send to peers we know are subscribed to the topic. - for (peer_id, sub_topic) in self.connected_peers.iter() { - if !sub_topic.iter().any(|t| message.topics.iter().any(|u| t == u)) { - continue; - } - - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: *peer_id, - handler: NotifyHandler::Any, - event: FloodsubRpc { - subscriptions: Vec::new(), - messages: vec![message.clone()], - }, - }); - } - } -} - -impl NetworkBehaviour for Floodsub { - type ConnectionHandler = OneShotHandler; - type OutEvent = FloodsubEvent; - - fn new_handler(&mut self) -> Self::ConnectionHandler { Default::default() } - - fn addresses_of_peer(&mut self, _: &PeerId) -> Vec { Vec::new() } - - fn inject_connection_established( - &mut self, - id: &PeerId, - _: &ConnectionId, - _: &ConnectedPoint, - _: Option<&Vec>, - other_established: usize, - ) { - if other_established > 0 { - // We only care about the first time a peer connects. - return; - } - - // We need to send our subscriptions to the newly-connected node. - for topic in self.subscribed_topics.iter().cloned() { - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: *id, - handler: NotifyHandler::Any, - event: FloodsubRpc { - messages: Vec::new(), - subscriptions: vec![FloodsubSubscription { - topic, - action: FloodsubSubscriptionAction::Subscribe, - }], - }, - }); - } - - self.connected_peers.insert(*id, SmallVec::new()); - } - - fn inject_connection_closed( - &mut self, - id: &PeerId, - _: &ConnectionId, - _: &ConnectedPoint, - _: ::Handler, - remaining_established: usize, - ) { - if remaining_established > 0 { - // we only care about peer disconnections - return; - } - - let was_in = self.connected_peers.remove(id); - debug_assert!(was_in.is_some()); - } - - fn inject_event(&mut self, propagation_source: PeerId, _connection: ConnectionId, event: InnerMessage) { - // We ignore successful sends or timeouts. - let event = match event { - InnerMessage::Rx(event) => event, - InnerMessage::Sent => return, - }; - - // Update connected peers topics - for subscription in event.subscriptions { - let remote_peer_topics = self.connected_peers - .get_mut(&propagation_source) - .expect("connected_peers is kept in sync with the peers we are connected to; we are guaranteed to only receive events from connected peers; QED"); - match subscription.action { - FloodsubSubscriptionAction::Subscribe => { - if !remote_peer_topics.contains(&subscription.topic) { - remote_peer_topics.push(subscription.topic.clone()); - } - self.events - .push_back(NetworkBehaviourAction::GenerateEvent(FloodsubEvent::Subscribed { - peer_id: propagation_source, - topic: subscription.topic, - })); - }, - FloodsubSubscriptionAction::Unsubscribe => { - if let Some(pos) = remote_peer_topics.iter().position(|t| t == &subscription.topic) { - remote_peer_topics.remove(pos); - } - self.events - .push_back(NetworkBehaviourAction::GenerateEvent(FloodsubEvent::Unsubscribed { - peer_id: propagation_source, - topic: subscription.topic, - })); - }, - } - } - - // List of messages we're going to propagate on the network. - let mut rpcs_to_dispatch: Vec<(PeerId, FloodsubRpc)> = Vec::new(); - - for message in event.messages { - // Use `self.received` to skip the messages that we have already received in the past. - // Note that this can false positive. - if !self.received.test_and_add(&message) { - continue; - } - - // Add the message to be dispatched to the user. - if self - .subscribed_topics - .iter() - .any(|t| message.topics.iter().any(|u| t == u)) - { - let event = FloodsubEvent::Message(message.clone()); - self.events.push_back(NetworkBehaviourAction::GenerateEvent(event)); - } - - if self.config.forward_messages { - // Propagate the message to everyone else who is subscribed to any of the topics. - for (peer_id, subscr_topics) in self.connected_peers.iter() { - if peer_id == &propagation_source { - continue; - } - - if !subscr_topics.iter().any(|t| message.topics.iter().any(|u| t == u)) { - continue; - } - - if let Some(pos) = rpcs_to_dispatch.iter().position(|(p, _)| p == peer_id) { - rpcs_to_dispatch[pos].1.messages.push(message.clone()); - } else { - rpcs_to_dispatch.push((*peer_id, FloodsubRpc { - subscriptions: Vec::new(), - messages: vec![message.clone()], - })); - } - } - } - } - - for (peer_id, rpc) in rpcs_to_dispatch { - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id, - handler: NotifyHandler::Any, - event: rpc, - }); - } - } - - fn poll( - &mut self, - _: &mut Context<'_>, - _: &mut impl PollParameters, - ) -> Poll> { - if let Some(event) = self.events.pop_front() { - return Poll::Ready(event); - } - - Poll::Pending - } -} - -/// Transmission between the `OneShotHandler` and the `FloodsubHandler`. -#[derive(Debug)] -pub enum InnerMessage { - /// We received an RPC from a remote. - Rx(FloodsubRpc), - /// We successfully sent an RPC request. - Sent, -} - -impl From for InnerMessage { - #[inline] - fn from(rpc: FloodsubRpc) -> InnerMessage { InnerMessage::Rx(rpc) } -} - -impl From<()> for InnerMessage { - #[inline] - fn from(_: ()) -> InnerMessage { InnerMessage::Sent } -} - -/// Event that can happen on the floodsub behaviour. -#[derive(Debug)] -pub enum FloodsubEvent { - /// A message has been received. - Message(FloodsubMessage), - - /// A remote subscribed to a topic. - Subscribed { - /// Remote that has subscribed. - peer_id: PeerId, - /// The topic it has subscribed to. - topic: Topic, - }, - - /// A remote unsubscribed from a topic. - Unsubscribed { - /// Remote that has unsubscribed. - peer_id: PeerId, - /// The topic it has subscribed from. - topic: Topic, - }, -} diff --git a/mm2src/floodsub/src/lib.rs b/mm2src/floodsub/src/lib.rs deleted file mode 100644 index 90592a9fe5..0000000000 --- a/mm2src/floodsub/src/lib.rs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Implements the floodsub protocol, see also the: -//! [spec](https://github.com/libp2p/specs/tree/master/pubsub). -//! The implementation is customized for AtomicDEX purposes, all peers are considered as "target_peers" -//! So "target_peers" are removed from Floodsub behaviour - -use libp2p_core::PeerId; - -pub mod protocol; - -mod layer; -mod topic; - -mod rpc_proto { - include!(concat!(env!("OUT_DIR"), "/floodsub.pb.rs")); -} - -pub use self::layer::{Floodsub, FloodsubEvent}; -pub use self::protocol::{FloodsubMessage, FloodsubRpc}; -pub use self::topic::Topic; - -/// Configuration options for the Floodsub protocol. -pub struct FloodsubConfig { - /// Peer id of the local node. Used for the source of the messages that we publish. - pub local_peer_id: PeerId, - - /// `true` if messages published by local node should be propagated as messages received from - /// the network, `false` by default. - pub subscribe_local_messages: bool, - - pub forward_messages: bool, -} - -impl FloodsubConfig { - pub fn new(local_peer_id: PeerId, forward_messages: bool) -> Self { - Self { - local_peer_id, - subscribe_local_messages: false, - forward_messages, - } - } -} diff --git a/mm2src/floodsub/src/protocol.rs b/mm2src/floodsub/src/protocol.rs deleted file mode 100644 index bf24053ffb..0000000000 --- a/mm2src/floodsub/src/protocol.rs +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::rpc_proto; -use crate::topic::Topic; -use futures::{io::{AsyncRead, AsyncWrite}, - Future}; -use libp2p_core::{upgrade, InboundUpgrade, OutboundUpgrade, PeerId, UpgradeInfo}; -use prost::Message; -use std::{error, fmt, io, iter, pin::Pin}; - -/// Implementation of `ConnectionUpgrade` for the floodsub protocol. -#[derive(Debug, Clone, Default)] -pub struct FloodsubProtocol {} - -impl FloodsubProtocol { - /// Builds a new `FloodsubProtocol`. - pub fn new() -> FloodsubProtocol { FloodsubProtocol {} } -} - -impl UpgradeInfo for FloodsubProtocol { - type Info = &'static [u8]; - type InfoIter = iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { iter::once(b"/floodsub/1.0.0") } -} - -type PinBoxTryFut = Pin> + Send>>; - -impl InboundUpgrade for FloodsubProtocol -where - TSocket: AsyncRead + AsyncWrite + Send + Unpin + 'static, -{ - type Output = FloodsubRpc; - type Error = FloodsubDecodeError; - type Future = PinBoxTryFut; - - fn upgrade_inbound(self, mut socket: TSocket, _: Self::Info) -> Self::Future { - Box::pin(async move { - let packet = upgrade::read_length_prefixed(&mut socket, 2048).await?; - let rpc = rpc_proto::Rpc::decode(&packet[..])?; - - let mut messages = Vec::with_capacity(rpc.publish.len()); - for publish in rpc.publish.into_iter() { - messages.push(FloodsubMessage { - source: PeerId::from_bytes(&publish.from.unwrap_or_default()) - .map_err(|_| FloodsubDecodeError::InvalidPeerId)?, - data: publish.data.unwrap_or_default(), - sequence_number: publish.seqno.unwrap_or_default(), - topics: publish.topic_ids.into_iter().map(Topic::new).collect(), - }); - } - - Ok(FloodsubRpc { - messages, - subscriptions: rpc - .subscriptions - .into_iter() - .map(|sub| FloodsubSubscription { - action: if Some(true) == sub.subscribe { - FloodsubSubscriptionAction::Subscribe - } else { - FloodsubSubscriptionAction::Unsubscribe - }, - topic: Topic::new(sub.topic_id.unwrap_or_default()), - }) - .collect(), - }) - }) - } -} - -/// Reach attempt interrupt errors. -#[derive(Debug)] -pub enum FloodsubDecodeError { - /// Error when reading the packet from the socket. - ReadError(io::Error), - /// Error when decoding the raw buffer into a protobuf. - ProtobufError(prost::DecodeError), - /// Error when parsing the `PeerId` in the message. - InvalidPeerId, -} - -impl From for FloodsubDecodeError { - fn from(err: io::Error) -> Self { FloodsubDecodeError::ReadError(err) } -} - -impl From for FloodsubDecodeError { - fn from(err: prost::DecodeError) -> Self { FloodsubDecodeError::ProtobufError(err) } -} - -impl fmt::Display for FloodsubDecodeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - FloodsubDecodeError::ReadError(ref err) => write!(f, "Error while reading from socket: {}", err), - FloodsubDecodeError::ProtobufError(ref err) => write!(f, "Error while decoding protobuf: {}", err), - FloodsubDecodeError::InvalidPeerId => write!(f, "Error while decoding PeerId from message"), - } - } -} - -impl error::Error for FloodsubDecodeError { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - match *self { - FloodsubDecodeError::ReadError(ref err) => Some(err), - FloodsubDecodeError::ProtobufError(ref err) => Some(err), - FloodsubDecodeError::InvalidPeerId => None, - } - } -} - -/// An RPC received by the floodsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FloodsubRpc { - /// List of messages that were part of this RPC query. - pub messages: Vec, - /// List of subscriptions. - pub subscriptions: Vec, -} - -impl UpgradeInfo for FloodsubRpc { - type Info = &'static [u8]; - type InfoIter = iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { iter::once(b"/floodsub/1.0.0") } -} - -impl OutboundUpgrade for FloodsubRpc -where - TSocket: AsyncWrite + AsyncRead + Send + Unpin + 'static, -{ - type Output = (); - type Error = io::Error; - type Future = PinBoxTryFut; - - fn upgrade_outbound(self, mut socket: TSocket, _: Self::Info) -> Self::Future { - Box::pin(async move { - let bytes = self.into_bytes(); - upgrade::write_length_prefixed(&mut socket, bytes).await?; - Ok(()) - }) - } -} - -impl FloodsubRpc { - /// Turns this `FloodsubRpc` into a message that can be sent to a substream. - fn into_bytes(self) -> Vec { - let rpc = rpc_proto::Rpc { - publish: self - .messages - .into_iter() - .map(|msg| rpc_proto::Message { - from: Some(msg.source.to_bytes()), - data: Some(msg.data), - seqno: Some(msg.sequence_number), - topic_ids: msg.topics.into_iter().map(|topic| topic.into()).collect(), - }) - .collect(), - - subscriptions: self - .subscriptions - .into_iter() - .map(|topic| rpc_proto::rpc::SubOpts { - subscribe: Some(topic.action == FloodsubSubscriptionAction::Subscribe), - topic_id: Some(topic.topic.into()), - }) - .collect(), - }; - - let mut buf = Vec::with_capacity(rpc.encoded_len()); - rpc.encode(&mut buf).expect("Vec provides capacity as needed"); - buf - } -} - -/// A message received by the floodsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FloodsubMessage { - /// Id of the peer that published this message. - pub source: PeerId, - - /// Content of the message. Its meaning is out of scope of this library. - pub data: Vec, - - /// An incrementing sequence number. - pub sequence_number: Vec, - - /// List of topics this message belongs to. - /// - /// Each message can belong to multiple topics at once. - pub topics: Vec, -} - -/// A subscription received by the floodsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FloodsubSubscription { - /// Action to perform. - pub action: FloodsubSubscriptionAction, - /// The topic from which to subscribe or unsubscribe. - pub topic: Topic, -} - -/// Action that a subscription wants to perform. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum FloodsubSubscriptionAction { - /// The remote wants to subscribe to the given topic. - Subscribe, - /// The remote wants to unsubscribe from the given topic. - Unsubscribe, -} diff --git a/mm2src/floodsub/src/rpc.proto b/mm2src/floodsub/src/rpc.proto deleted file mode 100644 index 84f0ea5179..0000000000 --- a/mm2src/floodsub/src/rpc.proto +++ /dev/null @@ -1,20 +0,0 @@ -syntax = "proto2"; - -package floodsub.pb; - -message RPC { - repeated SubOpts subscriptions = 1; - repeated Message publish = 2; - - message SubOpts { - optional bool subscribe = 1; // subscribe or unsubcribe - optional string topic_id = 2; - } -} - -message Message { - optional bytes from = 1; - optional bytes data = 2; - optional bytes seqno = 3; - repeated string topic_ids = 4; -} diff --git a/mm2src/floodsub/src/topic.rs b/mm2src/floodsub/src/topic.rs deleted file mode 100644 index 41d2b253b8..0000000000 --- a/mm2src/floodsub/src/topic.rs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -/// Built topic. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Topic(String); - -impl Topic { - /// Returns the id of the topic. - #[inline] - pub fn id(&self) -> &str { &self.0 } - - pub fn new(name: S) -> Topic - where - S: Into, - { - Topic(name.into()) - } -} - -impl From for String { - fn from(topic: Topic) -> String { topic.0 } -} diff --git a/mm2src/gossipsub/CHANGELOG.md b/mm2src/gossipsub/CHANGELOG.md deleted file mode 100644 index d2a5c025a7..0000000000 --- a/mm2src/gossipsub/CHANGELOG.md +++ /dev/null @@ -1,11 +0,0 @@ -# 0.20.0 [2020-07-01] - -- Updated dependencies. - -# 0.19.3 [2020-06-23] - -- Maintenance release fixing linter warnings. - -# 0.19.2 [2020-06-22] - -- Updated dependencies. diff --git a/mm2src/gossipsub/Cargo.toml b/mm2src/gossipsub/Cargo.toml deleted file mode 100644 index b34f1911a3..0000000000 --- a/mm2src/gossipsub/Cargo.toml +++ /dev/null @@ -1,41 +0,0 @@ -[package] -name = "atomicdex-gossipsub" -edition = "2018" -description = "Gossipsub protocol for AtomicDEX, based on libp2p gossipsub" -version = "0.20.0" -authors = ["Age Manning "] -license = "MIT" -repository = "https://github.com/libp2p/rust-libp2p" -keywords = ["peer-to-peer", "libp2p", "networking"] -categories = ["network-programming", "asynchronous"] - -[lib] -doctest = false - -[dependencies] -base64 = "0.21.2" -bytes = "0.5.4" -byteorder = "1.3.2" -common = { path = "../common" } -fnv = "1.0.6" -futures = "0.3.1" -futures_codec = "0.4.0" -libp2p-swarm = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45.1" } -libp2p-core = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45.1" } -log = "0.4.17" -prost = "0.10" -rand = "0.7" -sha2 = "0.10" -smallvec = "1.1.0" -unsigned-varint = { version = "0.4.0", features = ["futures-codec"] } -wasm-timer = "0.2.4" - -[dev-dependencies] -async-std = "1.6.2" -env_logger = "0.9.3" -libp2p-plaintext = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45.1" } -libp2p-yamux = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45.1" } -quickcheck= { version = "0.9.2", default-features = false } - -[build-dependencies] -prost-build = { version = "0.10.4", default-features = false } diff --git a/mm2src/gossipsub/build.rs b/mm2src/gossipsub/build.rs deleted file mode 100644 index 9671983699..0000000000 --- a/mm2src/gossipsub/build.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -fn main() { prost_build::compile_protos(&["src/rpc.proto"], &["src"]).unwrap(); } diff --git a/mm2src/gossipsub/src/behaviour.rs b/mm2src/gossipsub/src/behaviour.rs deleted file mode 100644 index 19eb7a2627..0000000000 --- a/mm2src/gossipsub/src/behaviour.rs +++ /dev/null @@ -1,1526 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::config::GossipsubConfig; -use crate::handler::GossipsubHandler; -use crate::mcache::MessageCache; -use crate::protocol::{GossipsubControlAction, GossipsubMessage, GossipsubSubscription, GossipsubSubscriptionAction, - MessageId}; -use crate::topic::{Topic, TopicHash}; -use common::time_cache::{Entry as TimeCacheEntry, TimeCache}; -use futures::prelude::*; -use libp2p_core::{connection::ConnectionId, ConnectedPoint, Multiaddr, PeerId}; -use libp2p_swarm::{IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters}; -use log::{debug, error, info, trace, warn}; -use rand::seq::SliceRandom; -use smallvec::SmallVec; -use std::collections::hash_map::Entry; -use std::collections::{HashMap, HashSet, VecDeque}; -use std::iter; -use std::sync::Arc; -use std::task::{Context, Poll}; -use std::time::Duration; -use wasm_timer::{Instant, Interval}; - -mod tests; - -/// Network behaviour that handles the gossipsub protocol. -pub struct Gossipsub { - /// Configuration providing gossipsub performance parameters. - config: GossipsubConfig, - - /// Events that need to be yielded to the outside when polling. - events: VecDeque>>, - - /// Pools non-urgent control messages between heartbeats. - control_pool: HashMap>, - - /// Peer id of the local node. Used for the source of the messages that we publish. - local_peer_id: PeerId, - - /// A map of all connected peers - A map of topic hash to a list of gossipsub peer Ids. - topic_peers: HashMap>, - - /// A map of all connected peers to their subscribed topics. - peer_topics: HashMap>, - - /// The peer ids of connected relay nodes - connected_relays: HashSet, - - /// relays to which we forward the messages. Also tracks the relay mesh size of nodes in mesh. - relays_mesh: HashMap, - - /// Peers included our node to their relays mesh - included_to_relays_mesh: HashSet, - - /// Overlay network of connected peers - Maps topics to connected gossipsub peers. - mesh: HashMap>, - - /// Map of topics to list of peers that we publish to, but don't subscribe to. - fanout: HashMap>, - - /// The last publish time for fanout topics. - fanout_last_pub: HashMap, - - /// Message cache for the last few heartbeats. - mcache: MessageCache, - - /// We keep track of the messages we received (in the format `string(source ID, seq_no)`) so that - /// we don't dispatch the same message twice if we receive it twice on the network. - /// Also store the peers from which message was received so we don't manually propagate already known message to them - received: TimeCache>, - - /// Heartbeat interval stream. - heartbeat: Interval, - - /// Relay mesh maintenance interval stream. - relay_mesh_maintenance_interval: Interval, - - peer_connections: HashMap>, - - connected_addresses: Vec, - - /// The relay list which are forcefully kept in relay mesh - explicit_relay_list: Vec, -} - -impl Gossipsub { - /// Creates a `Gossipsub` struct given a set of parameters specified by `gs_config`. - pub fn new(local_peer_id: PeerId, gs_config: GossipsubConfig) -> Self { - let local_peer_id = if gs_config.no_source_id { - PeerId::from_bytes(&crate::config::IDENTITY_SOURCE).expect("Valid peer id") - } else { - local_peer_id - }; - - Gossipsub { - config: gs_config.clone(), - events: VecDeque::new(), - control_pool: HashMap::new(), - local_peer_id, - topic_peers: HashMap::new(), - peer_topics: HashMap::new(), - mesh: HashMap::new(), - fanout: HashMap::new(), - fanout_last_pub: HashMap::new(), - mcache: MessageCache::new( - gs_config.history_gossip, - gs_config.history_length, - gs_config.message_id_fn, - ), - received: TimeCache::new(gs_config.duplicate_cache_time), - heartbeat: Interval::new_at( - Instant::now() + gs_config.heartbeat_initial_delay, - gs_config.heartbeat_interval, - ), - relay_mesh_maintenance_interval: Interval::new_at( - Instant::now() + Duration::from_secs(10), - Duration::from_secs(10), - ), - peer_connections: HashMap::new(), - connected_relays: HashSet::new(), - relays_mesh: HashMap::new(), - included_to_relays_mesh: HashSet::new(), - connected_addresses: Vec::new(), - explicit_relay_list: Vec::new(), - } - } - - pub fn add_explicit_relay(&mut self, peer_id: PeerId) { - info!("Adding peer {} to explicit relay list", peer_id); - self.explicit_relay_list.push(peer_id); - } - - /// Subscribe to a topic. - /// - /// Returns true if the subscription worked. Returns false if we were already subscribed. - pub fn subscribe(&mut self, topic: Topic) -> bool { - debug!("Subscribing to topic: {}", topic); - if self.config.i_am_relay { - debug!("Relay is subscribed to all topics by default. Subscribe has no effect."); - return false; - } - let topic_hash = self.topic_hash(topic.clone()); - if self.mesh.get(&topic_hash).is_some() { - debug!("Topic: {} is already in the mesh.", topic); - return false; - } - - let peers: Vec<_> = self.peer_topics.keys().copied().collect(); - for peer_id in peers { - let mut fixed_event = None; // initialise the event once if needed - if fixed_event.is_none() { - fixed_event = Some(Arc::new(GossipsubRpc { - messages: Vec::new(), - subscriptions: vec![GossipsubSubscription { - topic_hash: topic_hash.clone(), - action: GossipsubSubscriptionAction::Subscribe, - }], - control_msgs: Vec::new(), - })); - } - - let event = fixed_event.expect("event has been initialised"); - - debug!("Sending SUBSCRIBE to peer: {:?}", peer_id); - self.notify_primary(peer_id, event); - } - - // call JOIN(topic) - // this will add new peers to the mesh for the topic - self.join(&topic_hash); - info!("Subscribed to topic: {}", topic); - true - } - - pub fn is_subscribed(&self, topic_hash: &TopicHash) -> bool { self.mesh.contains_key(topic_hash) } - - /// Unsubscribes from a topic. - /// - /// Returns true if we were subscribed to this topic. - pub fn unsubscribe(&mut self, topic: Topic) -> bool { - debug!("Unsubscribing from topic: {}", topic); - if self.config.i_am_relay { - debug!("Relay is subscribed to all topics by default. Unsubscribe has no effect"); - return false; - } - let topic_hash = &self.topic_hash(topic); - - if self.mesh.get(topic_hash).is_none() { - debug!("Already unsubscribed from topic: {:?}", topic_hash); - // we are not subscribed - return false; - } - - // announce to all peers in the topic - let mut fixed_event = None; // initialise the event once if needed - if let Some(peer_list) = self.topic_peers.get(topic_hash) { - if fixed_event.is_none() { - fixed_event = Some(Arc::new(GossipsubRpc { - messages: Vec::new(), - subscriptions: vec![GossipsubSubscription { - topic_hash: topic_hash.clone(), - action: GossipsubSubscriptionAction::Unsubscribe, - }], - control_msgs: Vec::new(), - })); - } - - let event = fixed_event.expect("event has been initialised"); - - for peer in peer_list.clone() { - debug!("Sending UNSUBSCRIBE to peer: {:?}", peer); - self.notify_primary(peer, event.clone()); - } - } - - // call LEAVE(topic) - // this will remove the topic from the mesh - self.leave(topic_hash); - - info!("Unsubscribed from topic: {:?}", topic_hash); - true - } - - /// Publishes a message to the network. - pub fn publish(&mut self, topic: &Topic, data: impl Into>) { - self.publish_many_from(iter::once(topic.clone()), data, self.local_peer_id) - } - - /// Publishes a message with multiple topics to the network. - pub fn publish_many(&mut self, topic: impl IntoIterator, data: impl Into>) { - self.publish_many_from(topic, data, self.local_peer_id); - } - - /// Publishes a message with multiple topics to the network. - pub fn publish_many_from( - &mut self, - topic: impl IntoIterator, - data: impl Into>, - source: PeerId, - ) { - let message = GossipsubMessage { - source, - data: data.into(), - // To be interoperable with the go-implementation this is treated as a 64-bit - // big-endian uint. - sequence_number: rand::random(), - topics: topic.into_iter().map(|t| self.topic_hash(t)).collect(), - }; - - debug!("Publishing message: {:?}", (self.config.message_id_fn)(&message)); - - // forward the message to mesh peers - self.forward_msg(message.clone(), &source); - - let mut recipient_peers = HashSet::new(); - for topic_hash in &message.topics { - // if not subscribed to the topic, use fanout peers - if self.mesh.get(topic_hash).is_none() { - debug!("Topic: {:?} not in the mesh", topic_hash); - // build a list of peers to forward the message to - // if we have fanout peers add them to the map - if self.fanout.contains_key(topic_hash) { - for peer in self.fanout.get(topic_hash).expect("Topic must exist") { - recipient_peers.insert(*peer); - } - } else { - // we have no fanout peers, select mesh_n of them and add them to the fanout - let mesh_n = self.config.mesh_n; - let new_peers = Self::get_random_peers(&self.topic_peers, topic_hash, mesh_n, |_| true); - // add the new peers to the fanout and recipient peers - self.fanout.insert(topic_hash.clone(), new_peers.clone()); - for peer in new_peers { - debug!("Peer added to fanout: {:?}", peer); - recipient_peers.insert(peer); - } - } - // we are publishing to fanout peers - update the time we published - self.fanout_last_pub.insert(topic_hash.clone(), Instant::now()); - } - } - - // add published message to our received caches - let msg_id = (self.config.message_id_fn)(&message); - self.mcache.put(message.clone()); - self.received.insert(msg_id.clone(), SmallVec::from_elem(source, 1)); - - debug!("Published message: {:?}", msg_id); - - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: vec![message], - control_msgs: Vec::new(), - }); - // Send to peers we know are subscribed to the topic. - for peer_id in recipient_peers.iter() { - debug!("Sending message to peer: {:?}", peer_id); - self.notify_primary(*peer_id, event.clone()); - } - } - - /// This function should be called when `config.manual_propagation` is `true` in order to - /// propagate messages. Messages are stored in the ['Memcache'] and validation is expected to be - /// fast enough that the messages should still exist in the cache. - /// - /// Calling this function will propagate a message stored in the cache, if it still exists. - /// If the message still exists in the cache, it will be forwarded and this function will return true, - /// otherwise it will return false. - pub fn propagate_message(&mut self, message_id: &MessageId, propagation_source: &PeerId) -> bool { - let message = match self.mcache.get(message_id) { - Some(message) => message.clone(), - None => { - warn!( - "Message not in cache. Ignoring forwarding. Message Id: {}", - message_id.0 - ); - return false; - }, - }; - self.forward_msg(message, propagation_source); - true - } - - /// Gossipsub JOIN(topic) - adds topic peers to mesh and sends them GRAFT messages. - fn join(&mut self, topic_hash: &TopicHash) { - debug!("Running JOIN for topic: {:?}", topic_hash); - - // if we are already in the mesh, return - if self.mesh.contains_key(topic_hash) { - info!("JOIN: The topic is already in the mesh, ignoring JOIN"); - return; - } - - let mut added_peers = vec![]; - - // check if we have mesh_n peers in fanout[topic] and add them to the mesh if we do, - // removing the fanout entry. - if let Some((_, peers)) = self.fanout.remove_entry(topic_hash) { - debug!("JOIN: Removing peers from the fanout for topic: {:?}", topic_hash); - // add up to mesh_n of them them to the mesh - // Note: These aren't randomly added, currently FIFO - let add_peers = std::cmp::min(peers.len(), self.config.mesh_n); - debug!( - "JOIN: Adding {:?} peers from the fanout for topic: {:?}", - add_peers, topic_hash - ); - added_peers.extend_from_slice(&peers[..add_peers]); - self.mesh.insert(topic_hash.clone(), peers[..add_peers].to_vec()); - // remove the last published time - self.fanout_last_pub.remove(topic_hash); - } - - // check if we need to get more peers, which we randomly select - if added_peers.len() < self.config.mesh_n { - // get the peers - let new_peers = Self::get_random_peers( - &self.topic_peers, - topic_hash, - self.config.mesh_n - added_peers.len(), - |_| true, - ); - added_peers.extend_from_slice(&new_peers); - // add them to the mesh - debug!("JOIN: Inserting {:?} random peers into the mesh", new_peers.len()); - let mesh_peers = self.mesh.entry(topic_hash.clone()).or_insert_with(Vec::new); - mesh_peers.extend_from_slice(&new_peers); - } - - for peer_id in added_peers { - // Send a GRAFT control message - info!("JOIN: Sending Graft message to peer: {:?}", peer_id); - Self::control_pool_add(&mut self.control_pool, peer_id, GossipsubControlAction::Graft { - topic_hash: topic_hash.clone(), - }); - } - debug!("Completed JOIN for topic: {:?}", topic_hash); - } - - /// Gossipsub LEAVE(topic) - Notifies mesh\[topic\] peers with PRUNE messages. - fn leave(&mut self, topic_hash: &TopicHash) { - debug!("Running LEAVE for topic {:?}", topic_hash); - - // if our mesh contains the topic, send prune to peers and delete it from the mesh - if let Some((_, peers)) = self.mesh.remove_entry(topic_hash) { - for peer in peers { - // Send a PRUNE control message - info!("LEAVE: Sending PRUNE to peer: {:?}", peer); - Self::control_pool_add(&mut self.control_pool, peer, GossipsubControlAction::Prune { - topic_hash: topic_hash.clone(), - }); - } - } - debug!("Completed LEAVE for topic: {:?}", topic_hash); - } - - /// Handles an IHAVE control message. Checks our cache of messages. If the message is unknown, - /// requests it with an IWANT control message. - fn handle_ihave(&mut self, peer_id: &PeerId, ihave_msgs: Vec<(TopicHash, Vec)>) { - debug!("Handling IHAVE for peer: {:?}", peer_id); - // use a hashset to avoid duplicates efficiently - let mut iwant_ids = HashSet::new(); - - for (topic, ids) in ihave_msgs { - // only process the message if we are subscribed - if !self.mesh.contains_key(&topic) { - debug!("IHAVE: Ignoring IHAVE - Not subscribed to topic: {:?}", topic); - continue; - } - - for id in ids { - if !self.received.contains_key(&id) { - // have not seen this message, request it - iwant_ids.insert(id); - } - } - } - - if !iwant_ids.is_empty() { - // Send the list of IWANT control messages - debug!("IHAVE: Sending IWANT message"); - Self::control_pool_add(&mut self.control_pool, *peer_id, GossipsubControlAction::IWant { - message_ids: iwant_ids.iter().cloned().collect(), - }); - } - debug!("Completed IHAVE handling for peer: {:?}", peer_id); - } - - /// Handles an IWANT control message. Checks our cache of messages. If the message exists it is - /// forwarded to the requesting peer. - fn handle_iwant(&mut self, peer_id: &PeerId, iwant_msgs: Vec) { - debug!("Handling IWANT for peer: {:?}", peer_id); - // build a hashmap of available messages - let mut cached_messages = HashMap::new(); - - for id in iwant_msgs { - // if we have it, add it do the cached_messages mapping - if let Some(msg) = self.mcache.get(&id) { - cached_messages.insert(id.clone(), msg.clone()); - } - } - - if !cached_messages.is_empty() { - debug!("IWANT: Sending cached messages to peer: {:?}", peer_id); - // Send the messages to the peer - let message_list = cached_messages.into_iter().map(|entry| entry.1).collect(); - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: message_list, - control_msgs: Vec::new(), - }); - self.notify_primary(*peer_id, event); - } - debug!("Completed IWANT handling for peer: {:?}", peer_id); - } - - /// Handles GRAFT control messages. If subscribed to the topic, adds the peer to mesh, if not, - /// responds with PRUNE messages. - fn handle_graft(&mut self, peer_id: &PeerId, topics: Vec) { - debug!("Handling GRAFT message for peer: {:?}", peer_id); - - let mut to_prune_topics = HashSet::new(); - for topic_hash in topics { - if let Some(peers) = self.mesh.get_mut(&topic_hash) { - // if we are subscribed, add peer to the mesh, if not already added - info!( - "GRAFT: Mesh link added for peer: {:?} in topic: {:?}", - peer_id, topic_hash - ); - // ensure peer is not already added - if !peers.contains(peer_id) { - peers.push(*peer_id); - } - } else { - to_prune_topics.insert(topic_hash.clone()); - } - } - - if !to_prune_topics.is_empty() { - // build the prune messages to send - let prune_messages = to_prune_topics - .iter() - .map(|t| GossipsubControlAction::Prune { topic_hash: t.clone() }) - .collect(); - // Send the prune messages to the peer - info!( - "GRAFT: Not subscribed to topics - Sending PRUNE to peer: {:?}", - peer_id - ); - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: Vec::new(), - control_msgs: prune_messages, - }); - self.notify_primary(*peer_id, event); - } - debug!("Completed GRAFT handling for peer: {:?}", peer_id); - } - - /// Handles PRUNE control messages. Removes peer from the mesh. - fn handle_prune(&mut self, peer_id: &PeerId, topics: Vec) { - debug!("Handling PRUNE message for peer: {:?}", peer_id); - for topic_hash in topics { - if let Some(peers) = self.mesh.get_mut(&topic_hash) { - // remove the peer if it exists in the mesh - info!( - "PRUNE: Removing peer: {:?} from the mesh for topic: {:?}", - peer_id, topic_hash - ); - peers.retain(|p| p != peer_id); - } - } - debug!("Completed PRUNE handling for peer: {:?}", peer_id); - } - - /// Handles IAmrelay control message, does nothing if remote peer already subscribed to some topic - fn handle_i_am_relay(&mut self, peer_id: &PeerId, is_relay: bool) { - debug!("Handling IAmrelay message for peer: {:?}", peer_id); - if self.peer_topics.entry(*peer_id).or_insert_with(Vec::new).is_empty() && is_relay { - info!("IAmrelay: Adding peer: {:?} to the relays list", peer_id); - self.connected_relays.insert(*peer_id); - if self.relays_mesh.len() < self.config.mesh_n_low { - info!("IAmrelay: Adding peer: {:?} to the relay mesh", peer_id); - self.add_peers_to_relays_mesh(vec![*peer_id]); - } - } - debug!("Completed IAmrelay handling for peer: {:?}", peer_id); - } - - /// Handles IncludedTorelaysMesh message - fn handle_included_to_relays_mesh(&mut self, peer_id: &PeerId, is_included: bool, other_mesh_size: usize) { - if self.is_relay() { - debug!( - "Handling IncludedTorelaysMesh message for peer: {:?}, is_included: {}", - peer_id, is_included - ); - if is_included { - if self.connected_relays.contains(peer_id) { - if self.relays_mesh.len() > self.config.mesh_n_high { - self.notify_excluded_from_relay_mesh(*peer_id); - } else { - debug!("Adding peer {:?} to relays_mesh", peer_id); - self.relays_mesh.insert(*peer_id, other_mesh_size); - } - } else { - debug!("Adding peer {:?} to included_to_relays_mesh", peer_id); - self.included_to_relays_mesh.insert(*peer_id); - } - } else { - debug!( - "Removing peer {:?} from included_to_relays_mesh and relays mesh", - peer_id - ); - self.included_to_relays_mesh.remove(peer_id); - self.relays_mesh.remove(peer_id); - } - } else { - debug!( - "Ignoring IncludedTorelaysMesh message for peer: {:?}, is_included: {}", - peer_id, is_included - ); - } - } - - /// Handles a newly received GossipsubMessage. - /// Forwards the message to all peers in the mesh. - fn handle_received_message(&mut self, msg: GossipsubMessage, propagation_source: &PeerId) { - let msg_id = (self.config.message_id_fn)(&msg); - debug!("Handling message: {:?} from peer: {:?}", msg_id, propagation_source); - match self.received.entry(msg_id.clone()) { - TimeCacheEntry::Occupied(entry) => { - debug!("Message already received, ignoring. Message: {:?}", msg_id); - entry.into_mut().push(*propagation_source); - return; - }, - TimeCacheEntry::Vacant(entry) => { - entry.insert(SmallVec::from_elem(*propagation_source, 1)); - }, - } - // add to the memcache - self.mcache.put(msg.clone()); - - // dispatch the message to the user - debug!("Sending received message to user"); - self.events - .push_back(NetworkBehaviourAction::GenerateEvent(GossipsubEvent::Message( - *propagation_source, - msg_id, - msg.clone(), - ))); - - // forward the message to mesh peers, if no validation is required - if !self.config.manual_propagation { - let message_id = (self.config.message_id_fn)(&msg); - self.forward_msg(msg, propagation_source); - debug!("Completed message handling for message: {:?}", message_id); - } - } - - /// Handles received subscriptions. - fn handle_received_subscriptions(&mut self, subscriptions: &[GossipsubSubscription], propagation_source: &PeerId) { - debug!( - "Handling subscriptions: {:?}, from source: {:?}", - subscriptions, propagation_source - ); - let subscribed_topics = match self.peer_topics.get_mut(propagation_source) { - Some(topics) => topics, - None => { - error!("Subscription by unknown peer: {:?}", &propagation_source); - return; - }, - }; - - for subscription in subscriptions { - // get the peers from the mapping, or insert empty lists if topic doesn't exist - let peer_list = self - .topic_peers - .entry(subscription.topic_hash.clone()) - .or_insert_with(Vec::new); - - match subscription.action { - GossipsubSubscriptionAction::Subscribe => { - if !peer_list.contains(propagation_source) { - debug!( - "SUBSCRIPTION: topic_peer: Adding gossip peer: {:?} to topic: {:?}", - propagation_source, subscription.topic_hash - ); - peer_list.push(*propagation_source); - } - - // add to the peer_topics mapping - if !subscribed_topics.contains(&subscription.topic_hash) { - info!( - "SUBSCRIPTION: Adding peer: {:?} to topic: {:?}", - propagation_source, subscription.topic_hash - ); - subscribed_topics.push(subscription.topic_hash.clone()); - } - - // if the mesh needs peers add the peer to the mesh - if let Some(peers) = self.mesh.get_mut(&subscription.topic_hash) { - if peers.len() < self.config.mesh_n_low { - debug!("SUBSCRIPTION: Adding peer {:?} to the mesh", propagation_source,); - } - peers.push(*propagation_source); - } - // generates a subscription event to be polled - self.events - .push_back(NetworkBehaviourAction::GenerateEvent(GossipsubEvent::Subscribed { - peer_id: *propagation_source, - topic: subscription.topic_hash.clone(), - })); - }, - GossipsubSubscriptionAction::Unsubscribe => { - if let Some(pos) = peer_list.iter().position(|p| p == propagation_source) { - info!( - "SUBSCRIPTION: Removing gossip peer: {:?} from topic: {:?}", - propagation_source, subscription.topic_hash - ); - peer_list.remove(pos); - } - // remove topic from the peer_topics mapping - if let Some(pos) = subscribed_topics.iter().position(|t| t == &subscription.topic_hash) { - subscribed_topics.remove(pos); - } - // remove the peer from the mesh if it exists - if let Some(peers) = self.mesh.get_mut(&subscription.topic_hash) { - peers.retain(|peer| peer != propagation_source); - } - - // generate an unsubscribe event to be polled - self.events - .push_back(NetworkBehaviourAction::GenerateEvent(GossipsubEvent::Unsubscribed { - peer_id: *propagation_source, - topic: subscription.topic_hash.clone(), - })); - }, - } - } - trace!("Completed handling subscriptions from source: {:?}", propagation_source); - } - - /// Heartbeat function which shifts the memcache and updates the mesh. - fn heartbeat(&mut self) { - debug!("Starting heartbeat"); - - let mut to_graft = HashMap::new(); - let mut to_prune = HashMap::new(); - - // maintain the mesh for each topic - for (topic_hash, peers) in self.mesh.iter_mut() { - // too little peers - add some - if peers.len() < self.config.mesh_n_low { - debug!( - "HEARTBEAT: Mesh low. Topic: {:?} Contains: {:?} needs: {:?}", - topic_hash.clone().into_string(), - peers.len(), - self.config.mesh_n_low - ); - // not enough peers - get mesh_n - current_length more - let desired_peers = self.config.mesh_n - peers.len(); - let peer_list = Self::get_random_peers(&self.topic_peers, topic_hash, desired_peers, { - |peer| !peers.contains(peer) - }); - for peer in &peer_list { - let current_topic = to_graft.entry(*peer).or_insert_with(Vec::new); - current_topic.push(topic_hash.clone()); - } - // update the mesh - debug!("Updating mesh, new mesh: {:?}", peer_list); - peers.extend(peer_list); - } - - // too many peers - remove some - if peers.len() > self.config.mesh_n_high { - debug!( - "HEARTBEAT: Mesh high. Topic: {:?} Contains: {:?} needs: {:?}", - topic_hash, - peers.len(), - self.config.mesh_n_high - ); - let excess_peer_no = peers.len() - self.config.mesh_n; - // shuffle the peers - let mut rng = rand::thread_rng(); - peers.shuffle(&mut rng); - // remove the first excess_peer_no peers adding them to to_prune - for _ in 0..excess_peer_no { - let peer = peers.pop().expect("There should always be enough peers to remove"); - let current_topic = to_prune.entry(peer).or_insert_with(Vec::new); - current_topic.push(topic_hash.clone()); - } - } - } - - // remove expired fanout topics - { - let fanout = &mut self.fanout; // help the borrow checker - let fanout_ttl = self.config.fanout_ttl; - self.fanout_last_pub.retain(|topic_hash, last_pub_time| { - if *last_pub_time + fanout_ttl < Instant::now() { - debug!( - "HEARTBEAT: Fanout topic removed due to timeout. Topic: {:?}", - topic_hash - ); - fanout.remove(topic_hash); - return false; - } - true - }); - } - - // maintain fanout - // check if our peers are still a part of the topic - for (topic_hash, peers) in self.fanout.iter_mut() { - let mut to_remove_peers = Vec::new(); - for peer in peers.iter() { - // is the peer still subscribed to the topic? - match self.peer_topics.get(peer) { - Some(topics) => { - if !topics.contains(topic_hash) { - debug!("HEARTBEAT: Peer removed from fanout for topic: {:?}", topic_hash); - to_remove_peers.push(*peer); - } - }, - None => { - // remove if the peer has disconnected - to_remove_peers.push(*peer); - }, - } - } - peers.retain(|peer| !to_remove_peers.contains(peer)); - - // not enough peers - if peers.len() < self.config.mesh_n { - debug!( - "HEARTBEAT: Fanout low. Contains: {:?} needs: {:?}", - peers.len(), - self.config.mesh_n - ); - let needed_peers = self.config.mesh_n - peers.len(); - let new_peers = Self::get_random_peers(&self.topic_peers, topic_hash, needed_peers, |peer| { - !peers.contains(peer) - }); - peers.extend(new_peers); - } - } - - self.emit_gossip(); - - // send graft/prunes - if !to_graft.is_empty() | !to_prune.is_empty() { - self.send_graft_prune(to_graft, to_prune); - } - - // piggyback pooled control messages - self.flush_control_pool(); - - // shift the memcache - self.mcache.shift(); - debug!("Completed Heartbeat"); - } - - /// Emits gossip - Send IHAVE messages to a random set of gossip peers. This is applied to mesh - /// and fanout peers - fn emit_gossip(&mut self) { - debug!("Started gossip"); - for (topic_hash, peers) in self.mesh.iter().chain(self.fanout.iter()) { - let message_ids = self.mcache.get_gossip_ids(topic_hash); - if message_ids.is_empty() { - return; - } - - // get gossip_lazy random peers - let to_msg_peers = Self::get_random_peers(&self.topic_peers, topic_hash, self.config.gossip_lazy, |peer| { - !peers.contains(peer) - }); - for peer in to_msg_peers { - // send an IHAVE message - Self::control_pool_add(&mut self.control_pool, peer, GossipsubControlAction::IHave { - topic_hash: topic_hash.clone(), - message_ids: message_ids.clone(), - }); - } - } - debug!("Completed gossip"); - } - - /// Handles multiple GRAFT/PRUNE messages and coalesces them into chunked gossip control - /// messages. - fn send_graft_prune( - &mut self, - to_graft: HashMap>, - mut to_prune: HashMap>, - ) { - // handle the grafts and overlapping prunes - for (peer, topics) in to_graft.iter() { - let mut grafts: Vec = topics - .iter() - .map(|topic_hash| GossipsubControlAction::Graft { - topic_hash: topic_hash.clone(), - }) - .collect(); - let mut prunes: Vec = to_prune - .remove(peer) - .unwrap_or_default() - .iter() - .map(|topic_hash| GossipsubControlAction::Prune { - topic_hash: topic_hash.clone(), - }) - .collect(); - grafts.append(&mut prunes); - - // send the control messages - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: Vec::new(), - control_msgs: grafts, - }); - self.notify_primary(*peer, event); - } - - // handle the remaining prunes - for (peer, topics) in to_prune.iter() { - let remaining_prunes = topics - .iter() - .map(|topic_hash| GossipsubControlAction::Prune { - topic_hash: topic_hash.clone(), - }) - .collect(); - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: Vec::new(), - control_msgs: remaining_prunes, - }); - self.notify_primary(*peer, event); - } - } - - /// Helper function which forwards a message to mesh\[topic\] peers. - fn forward_msg(&mut self, message: GossipsubMessage, source: &PeerId) { - let msg_id = (self.config.message_id_fn)(&message); - debug!("Forwarding message: {:?}", msg_id); - let mut recipient_peers = HashSet::new(); - - if self.config.i_am_relay { - // relay simply forwards the message to topic peers that included the relay to their relays mesh - for topic in &message.topics { - if let Some(topic_peers) = self.topic_peers.get(topic) { - for peer_id in topic_peers { - if peer_id != source - && peer_id != &message.source - && self.included_to_relays_mesh.contains(peer_id) - { - recipient_peers.insert(*peer_id); - } - } - } - } - } else { - // add mesh peers if the node is not relay - for topic in &message.topics { - if let Some(mesh_peers) = self.mesh.get(topic) { - for peer_id in mesh_peers { - if peer_id != source && peer_id != &message.source { - recipient_peers.insert(*peer_id); - } - } - } - } - } - - // forward the message to peers - if !recipient_peers.is_empty() { - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: vec![message.clone()], - control_msgs: Vec::new(), - }); - - for peer in recipient_peers.iter() { - if let Some(received_from_peers) = self.received.get(&msg_id) { - if received_from_peers.contains(peer) { - continue; - } - } - - debug!("Sending message: {:?} to peer {:?}", msg_id, peer); - self.notify_primary(*peer, event.clone()); - } - } - - if !self.relays_mesh.is_empty() { - debug!("Forwarding message to relays: {:?}", msg_id); - let message_source = message.source; - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: vec![message], - control_msgs: Vec::new(), - }); - - let relays: Vec<_> = self.relays_mesh.keys().copied().collect(); - for relay in relays { - if let Some(received_from_peers) = self.received.get(&msg_id) { - if received_from_peers.contains(&relay) { - continue; - } - } - - if relay != *source && relay != message_source { - debug!("Sending message: {:?} to relay {:?}", msg_id, relay); - self.notify_primary(relay, event.clone()); - } - } - debug!("Completed forwarding message to relays"); - } - debug!("Completed forwarding message"); - } - - /// Helper function to get a set of `n` random gossipsub peers for a `topic_hash` - /// filtered by the function `f`. - fn get_random_peers( - topic_peers: &HashMap>, - topic_hash: &TopicHash, - n: usize, - mut f: impl FnMut(&PeerId) -> bool, - ) -> Vec { - let mut gossip_peers = match topic_peers.get(topic_hash) { - // if they exist, filter the peers by `f` - Some(peer_list) => peer_list.iter().cloned().filter(|p| f(p)).collect(), - None => Vec::new(), - }; - - // if we have less than needed, return them - if gossip_peers.len() <= n { - debug!("RANDOM PEERS: Got {:?} peers", gossip_peers.len()); - return gossip_peers.to_vec(); - } - - // we have more peers than needed, shuffle them and return n of them - let mut rng = rand::thread_rng(); - gossip_peers.partial_shuffle(&mut rng, n); - - debug!("RANDOM PEERS: Got {:?} peers", n); - - gossip_peers[..n].to_vec() - } - - /// The helper function to get a set of `n` random peers from the `relays` hashset - /// filtered by the function `f`. - fn get_random_relays(relays: &HashSet, n: usize, mut f: impl FnMut(&PeerId) -> bool) -> Vec { - let mut relays: Vec<_> = relays.iter().cloned().filter(|p| f(p)).collect(); - - // if we have less than needed, return them - if relays.len() <= n { - debug!("RANDOM RELAYS: Got {:?} peers", relays.len()); - return relays; - } - - // we have more peers than needed, shuffle them and return n of them - let mut rng = rand::thread_rng(); - relays.partial_shuffle(&mut rng, n); - debug!("RANDOM RELAYS: Got {:?} peers", n); - - relays[..n].to_vec() - } - - // adds a control action to control_pool - fn control_pool_add( - control_pool: &mut HashMap>, - peer: PeerId, - control: GossipsubControlAction, - ) { - control_pool.entry(peer).or_insert_with(Vec::new).push(control); - } - - /// Produces a `TopicHash` for a topic given the gossipsub configuration. - fn topic_hash(&self, topic: Topic) -> TopicHash { - if self.config.hash_topics { - topic.sha256_hash() - } else { - topic.no_hash() - } - } - - /// Takes each control action mapping and turns it into a message - fn flush_control_pool(&mut self) { - let control_pool: Vec<_> = self.control_pool.drain().collect(); - for (peer, controls) in control_pool { - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: Vec::new(), - control_msgs: controls, - }); - self.notify_primary(peer, event); - } - } - - pub fn get_mesh_peers(&self, topic: &TopicHash) -> Vec { self.mesh.get(topic).cloned().unwrap_or_default() } - - pub fn get_topic_peers(&self, topic: &TopicHash) -> Vec { - self.topic_peers.get(topic).cloned().unwrap_or_default() - } - - pub fn get_num_peers(&self) -> usize { self.peer_topics.len() } - - pub fn get_peers_connections(&self) -> HashMap> { - self.peer_connections.clone() - } - - pub fn get_mesh(&self) -> &HashMap> { &self.mesh } - - pub fn get_relay_mesh(&self) -> Vec { self.relays_mesh.keys().cloned().collect() } - - pub fn relay_mesh_len(&self) -> usize { self.relays_mesh.len() } - - pub fn get_all_topic_peers(&self) -> &HashMap> { &self.topic_peers } - - pub fn get_all_peer_topics(&self) -> &HashMap> { &self.peer_topics } - - /// Get count of received messages in the [`GossipsubConfig::duplicate_cache_time`] period. - pub fn get_received_messages_in_period(&self) -> (Duration, usize) { (self.received.ttl(), self.received.len()) } - - pub fn get_config(&self) -> &GossipsubConfig { &self.config } - - /// Adds peers to relays mesh and notifies them they are added - fn add_peers_to_relays_mesh(&mut self, peers: Vec) { - for peer in &peers { - // other mesh size is unknown at this point - self.relays_mesh.insert(*peer, 0); - } - for peer in peers { - self.notify_included_to_relay_mesh(peer); - } - } - - #[allow(dead_code)] - fn remove_peer_from_relay_mesh(&mut self, peer: &PeerId) { - if self.relays_mesh.remove(peer).is_some() { - self.notify_excluded_from_relay_mesh(*peer) - } - } - - /// Cleans up relays mesh so it remains mesh_n peers - fn clean_up_relays_mesh(&mut self) { - let mesh_n = self.config.mesh_n; - let mut removed = Vec::with_capacity(self.relays_mesh.len() - mesh_n); - let explicit_relay_list = self.explicit_relay_list.clone(); - // perform 2 filter iterations to not keep excessive number of explicit peers in mesh - self.relays_mesh = self - .relays_mesh - .drain() - .enumerate() - .filter_map(|(i, peer)| { - if i < mesh_n || explicit_relay_list.contains(&peer.0) { - Some(peer) - } else { - removed.push(peer); - None - } - }) - .collect(); - - self.relays_mesh = self - .relays_mesh - .drain() - .enumerate() - .filter_map(|(i, peer)| { - if i < mesh_n { - Some(peer) - } else { - removed.push(peer); - None - } - }) - .collect(); - - for (peer, _) in removed { - self.notify_excluded_from_relay_mesh(peer) - } - } - - fn notify_included_to_relay_mesh(&mut self, peer: PeerId) { - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: Vec::new(), - control_msgs: vec![GossipsubControlAction::IncludedToRelaysMesh { - included: true, - mesh_size: self.relay_mesh_len(), - }], - }); - self.notify_primary(peer, event); - } - - fn notify_excluded_from_relay_mesh(&mut self, peer: PeerId) { - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: Vec::new(), - control_msgs: vec![GossipsubControlAction::IncludedToRelaysMesh { - included: false, - mesh_size: self.relay_mesh_len(), - }], - }); - self.notify_primary(peer, event); - } - - /// Notify the primary connection (the first connected point) of the peer. - /// Since `NotifyHandler::All` has been removed, the original `libp2p_gossipsub` notifies the connected peers using their primary connections. - /// See an example: https://github.com/libp2p/rust-libp2p/blob/v0.38.0/protocols/gossipsub/src/behaviour.rs#L3013 - fn notify_primary(&mut self, peer_id: PeerId, event: Arc) { - if let Some(points) = self.peer_connections.get(&peer_id) { - if !points.is_empty() { - let conn_id = points[0].0; - return self.notify_one(peer_id, conn_id, event); - } - } - warn!("Expected at least one connection of the peer '{}'", peer_id); - self.notify_any(peer_id, event); - } - - #[allow(dead_code)] - fn notify_all(&mut self, peer_id: PeerId, event: Arc) { - match self.peer_connections.get(&peer_id) { - Some(connected_points) => { - let connections: Vec<_> = connected_points.iter().map(|(conn_id, _point)| *conn_id).collect(); - for conn_id in connections { - self.notify_one(peer_id, conn_id, event.clone()); - } - }, - None => { - warn!( - "An attempt to notify a peer '{:?}' that is not in 'Gossipsub::peer_connections'", - peer_id - ) - }, - } - } - - fn notify_any(&mut self, peer_id: PeerId, event: Arc) { - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id, - handler: NotifyHandler::Any, - event, - }); - } - - fn notify_one(&mut self, peer_id: PeerId, conn_id: ConnectionId, event: Arc) { - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id, - handler: NotifyHandler::One(conn_id), - event, - }); - } - - fn maintain_relays_mesh(&mut self) { - if self.relays_mesh.len() < self.config.mesh_n_low { - info!( - "HEARTBEAT: relays low. Contains: {:?} needs: {:?}", - self.relays_mesh.len(), - self.config.mesh_n_low, - ); - // add peers 1 by 1 to avoid overloading peaks when node connects to several other nodes at once - let required = 1; - // get `n` relays that are not in the `relays_mesh` - let to_add = - Self::get_random_relays(&self.connected_relays, required, |p| !self.relays_mesh.contains_key(p)); - self.add_peers_to_relays_mesh(to_add); - } - - if self.relays_mesh.len() > self.config.mesh_n_high { - info!( - "HEARTBEAT: relays high. Contains: {:?} needs: {:?}", - self.relays_mesh.len(), - self.config.mesh_n, - ); - self.clean_up_relays_mesh(); - } - - let size = self.relays_mesh.len(); - for relay in self.relays_mesh.keys() { - Self::control_pool_add(&mut self.control_pool, *relay, GossipsubControlAction::MeshSize(size)); - } - } - - pub fn is_relay(&self) -> bool { self.config.i_am_relay } - - pub fn connected_relays(&self) -> Vec { self.connected_relays.iter().cloned().collect() } - - pub fn connected_relays_len(&self) -> usize { self.connected_relays.len() } - - pub fn is_connected_to_addr(&self, addr: &Multiaddr) -> bool { self.connected_addresses.contains(addr) } -} - -impl NetworkBehaviour for Gossipsub { - type ConnectionHandler = GossipsubHandler; - type OutEvent = GossipsubEvent; - - fn new_handler(&mut self) -> Self::ConnectionHandler { - GossipsubHandler::new(self.config.protocol_id.clone(), self.config.max_transmit_size) - } - - fn addresses_of_peer(&mut self, _: &PeerId) -> Vec { Vec::new() } - - fn inject_connection_established( - &mut self, - id: &PeerId, - conn_id: &ConnectionId, - point: &ConnectedPoint, - _: Option<&Vec>, - other_established: usize, - ) { - self.peer_connections - .entry(*id) - .or_insert_with(Default::default) - .push((*conn_id, point.clone())); - self.connected_addresses.push(point.get_remote_address().clone()); - - if other_established > 0 { - // For other actions, we only care about the first time a peer connects. - return; - } - - info!("New peer connected: {:?}", id); - // We need to send our subscriptions to the newly-connected node if we are not relay. - // Notify peer that we act as relay otherwise - if self.config.i_am_relay { - debug!("Sending IAmRelay to peer {:?}", id); - let event = Arc::new(GossipsubRpc { - messages: Vec::new(), - subscriptions: Vec::new(), - control_msgs: vec![GossipsubControlAction::IAmRelay(true)], - }); - self.notify_primary(*id, event); - } else { - let mut subscriptions = vec![]; - for topic_hash in self.mesh.keys() { - subscriptions.push(GossipsubSubscription { - topic_hash: topic_hash.clone(), - action: GossipsubSubscriptionAction::Subscribe, - }); - } - - if !subscriptions.is_empty() { - // send our subscriptions to the peer - let event = Arc::new(GossipsubRpc { - messages: Vec::new(), - subscriptions, - control_msgs: Vec::new(), - }); - self.notify_primary(*id, event); - } - } - // For the time being assume all gossipsub peers - self.peer_topics.insert(*id, Vec::new()); - } - - fn inject_connection_closed( - &mut self, - peer_id: &PeerId, - disconnected_conn_id: &ConnectionId, - disconnected_point: &ConnectedPoint, - _: ::Handler, - remaining_established: usize, - ) { - if let Entry::Occupied(mut o) = self.peer_connections.entry(*peer_id) { - let connected_points = o.get_mut(); - connected_points.retain(|(conn_id, _point)| conn_id != disconnected_conn_id); - if connected_points.is_empty() { - o.remove_entry(); - } - } - - self.connected_addresses - .retain(|addr| addr != disconnected_point.get_remote_address()); - - if remaining_established > 0 { - return; - } - - // remove from mesh, topic_peers, peer_topic and fanout - debug!("Peer disconnected: {:?}", peer_id); - { - let topics = match self.peer_topics.get(peer_id) { - Some(topics) => topics, - None => { - warn!("Disconnected node, not in connected nodes"); - return; - }, - }; - - // remove peer from all mappings - for topic in topics { - // check the mesh for the topic - if let Some(mesh_peers) = self.mesh.get_mut(topic) { - // check if the peer is in the mesh and remove it - if let Some(pos) = mesh_peers.iter().position(|p| p == peer_id) { - mesh_peers.remove(pos); - } - } - - // remove from topic_peers - if let Some(peer_list) = self.topic_peers.get_mut(topic) { - if let Some(pos) = peer_list.iter().position(|p| p == peer_id) { - peer_list.remove(pos); - } - // debugging purposes - else { - warn!("Disconnected node: {:?} not in topic_peers peer list", peer_id); - } - } else { - warn!( - "Disconnected node: {:?} with topic: {:?} not in topic_peers", - &peer_id, &topic - ); - } - - // remove from fanout - if let Some(peers) = self.fanout.get_mut(topic) { - peers.retain(|p| p != peer_id) - } - } - } - - self.relays_mesh.remove(peer_id); - self.connected_relays.remove(peer_id); - self.included_to_relays_mesh.remove(peer_id); - self.peer_connections.remove(peer_id); - // remove peer from peer_topics - let was_in = self.peer_topics.remove(peer_id); - debug_assert!(was_in.is_some()); - } - - fn inject_event(&mut self, propagation_source: PeerId, _: ConnectionId, event: GossipsubRpc) { - // Handle subscriptions - // Update connected peers topics - debug!("Event injected {:?}, source {:?}", event, propagation_source); - self.handle_received_subscriptions(&event.subscriptions, &propagation_source); - - // Handle messages - for message in event.messages { - self.handle_received_message(message, &propagation_source); - } - - // Handle control messages - // group some control messages, this minimises SendEvents (code is simplified to handle each event at a time however) - let mut ihave_msgs = vec![]; - let mut graft_msgs = vec![]; - let mut prune_msgs = vec![]; - for control_msg in event.control_msgs { - match control_msg { - GossipsubControlAction::IHave { - topic_hash, - message_ids, - } => { - ihave_msgs.push((topic_hash, message_ids)); - }, - GossipsubControlAction::IWant { message_ids } => self.handle_iwant(&propagation_source, message_ids), - GossipsubControlAction::Graft { topic_hash } => graft_msgs.push(topic_hash), - GossipsubControlAction::Prune { topic_hash } => prune_msgs.push(topic_hash), - GossipsubControlAction::IAmRelay(is_relay) => self.handle_i_am_relay(&propagation_source, is_relay), - GossipsubControlAction::IncludedToRelaysMesh { included, mesh_size } => { - self.handle_included_to_relays_mesh(&propagation_source, included, mesh_size) - }, - GossipsubControlAction::MeshSize(size) => { - if let Some(old_size) = self.relays_mesh.get_mut(&propagation_source) { - *old_size = size; - } - }, - } - } - if !ihave_msgs.is_empty() { - self.handle_ihave(&propagation_source, ihave_msgs); - } - if !graft_msgs.is_empty() { - self.handle_graft(&propagation_source, graft_msgs); - } - if !prune_msgs.is_empty() { - self.handle_prune(&propagation_source, prune_msgs); - } - } - - fn poll( - &mut self, - cx: &mut Context, - _: &mut impl PollParameters, - ) -> Poll> { - if let Some(event) = self.events.pop_front() { - // clone send event reference if others references are present - match event { - NetworkBehaviourAction::NotifyHandler { - peer_id, - handler, - event: send_event, - } => match Arc::try_unwrap(send_event) { - Ok(event) => { - return Poll::Ready(NetworkBehaviourAction::NotifyHandler { - peer_id, - event, - handler, - }); - }, - Err(event) => { - return Poll::Ready(NetworkBehaviourAction::NotifyHandler { - peer_id, - event: (*event).clone(), - handler, - }); - }, - }, - NetworkBehaviourAction::GenerateEvent(e) => { - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(e)); - }, - NetworkBehaviourAction::Dial { opts, handler } => { - return Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }); - }, - NetworkBehaviourAction::ReportObservedAddr { address, score } => { - return Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { address, score }); - }, - NetworkBehaviourAction::CloseConnection { peer_id, connection } => { - return Poll::Ready(NetworkBehaviourAction::CloseConnection { peer_id, connection }); - }, - } - } - - while let Poll::Ready(Some(())) = self.heartbeat.poll_next_unpin(cx) { - self.heartbeat(); - } - - while let Poll::Ready(Some(())) = self.relay_mesh_maintenance_interval.poll_next_unpin(cx) { - self.maintain_relays_mesh(); - } - - Poll::Pending - } -} - -/// An RPC received/sent. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct GossipsubRpc { - /// List of messages that were part of this RPC query. - pub messages: Vec, - /// List of subscriptions. - pub subscriptions: Vec, - /// List of Gossipsub control messages. - pub control_msgs: Vec, -} - -/// Event that can happen on the gossipsub behaviour. -#[derive(Debug)] -pub enum GossipsubEvent { - /// A message has been received. This contains the PeerId that we received the message from, - /// the message id (used if the application layer needs to propagate the message) and the - /// message itself. - Message(PeerId, MessageId, GossipsubMessage), - - /// A remote subscribed to a topic. - Subscribed { - /// Remote that has subscribed. - peer_id: PeerId, - /// The topic it has subscribed to. - topic: TopicHash, - }, - - /// A remote unsubscribed from a topic. - Unsubscribed { - /// Remote that has unsubscribed. - peer_id: PeerId, - /// The topic it has subscribed from. - topic: TopicHash, - }, -} diff --git a/mm2src/gossipsub/src/behaviour/tests.rs b/mm2src/gossipsub/src/behaviour/tests.rs deleted file mode 100644 index 1c1923df0b..0000000000 --- a/mm2src/gossipsub/src/behaviour/tests.rs +++ /dev/null @@ -1,900 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -// collection of tests for the gossipsub network behaviour - -#[cfg(test)] -#[allow(clippy::module_inception)] -mod tests { - use super::super::*; - use crate::GossipsubConfigBuilder; - use libp2p_core::Endpoint; - use std::net::IpAddr; - use std::str::FromStr; - - // helper functions for testing - - // This function generates `peer_no` random PeerId's, subscribes to `topics` and subscribes the - // injected nodes to all topics if `to_subscribe` is set. All nodes are considered gossipsub nodes. - fn build_and_inject_nodes( - peer_no: usize, - topics: Vec, - gs_config: GossipsubConfig, - to_subscribe: bool, - ) -> (Gossipsub, Vec, Vec) { - // create a gossipsub struct - let mut gs: Gossipsub = Gossipsub::new(PeerId::random(), gs_config); - - let mut topic_hashes = vec![]; - - // subscribe to the topics - for t in topics { - let topic = Topic::new(t); - gs.subscribe(topic.clone()); - topic_hashes.push(topic.no_hash().clone()); - } - - // build and connect peer_no random peers - let mut peers = vec![]; - - for i in 0..peer_no { - let peer = PeerId::random(); - peers.push(peer); - ::inject_connection_established( - &mut gs, - &peer, - &ConnectionId::new(i), - &ConnectedPoint::Dialer { - address: Multiaddr::from(IpAddr::from_str("127.0.0.1").unwrap()), - role_override: Endpoint::Dialer, - }, - None, - 0, - ); - if to_subscribe { - gs.handle_received_subscriptions( - &topic_hashes - .iter() - .cloned() - .map(|t| GossipsubSubscription { - action: GossipsubSubscriptionAction::Subscribe, - topic_hash: t, - }) - .collect::>(), - &peer, - ); - }; - } - - (gs, peers, topic_hashes) - } - - #[test] - /// Test local node subscribing to a topic - fn test_subscribe() { - // The node should: - // - Create an empty vector in mesh[topic] - // - Send subscription request to all peers - // - run JOIN(topic) - - let subscribe_topic = vec![String::from("test_subscribe")]; - let (gs, _, topic_hashes) = build_and_inject_nodes(20, subscribe_topic, GossipsubConfig::default(), true); - - assert!( - gs.mesh.get(&topic_hashes[0]).is_some(), - "Subscribe should add a new entry to the mesh[topic] hashmap" - ); - - // collect all the subscriptions - let subscriptions = gs.events.iter().fold(vec![], |mut collected_subscriptions, e| match e { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - for s in &event.subscriptions { - if s.action == GossipsubSubscriptionAction::Subscribe { - collected_subscriptions.push(s.clone()) - } - } - collected_subscriptions - }, - _ => collected_subscriptions, - }); - - // we sent a subscribe to all known peers - assert!( - subscriptions.len() == 20, - "Should send a subscription to all known peers" - ); - } - - #[test] - /// Test unsubscribe. - fn test_unsubscribe() { - // Unsubscribe should: - // - Remove the mesh entry for topic - // - Send UNSUBSCRIBE to all known peers - // - Call Leave - - let topic_strings = vec![String::from("topic1"), String::from("topic2")]; - let topics = topic_strings - .iter() - .map(|t| Topic::new(t.clone())) - .collect::>(); - - // subscribe to topic_strings - let (mut gs, _, topic_hashes) = build_and_inject_nodes(20, topic_strings, GossipsubConfig::default(), true); - - for topic_hash in &topic_hashes { - assert!( - gs.topic_peers.get(topic_hash).is_some(), - "Topic_peers contain a topic entry" - ); - assert!(gs.mesh.get(topic_hash).is_some(), "mesh should contain a topic entry"); - } - - // unsubscribe from both topics - assert!( - gs.unsubscribe(topics[0].clone()), - "should be able to unsubscribe successfully from each topic", - ); - assert!( - gs.unsubscribe(topics[1].clone()), - "should be able to unsubscribe successfully from each topic", - ); - - let subscriptions = gs.events.iter().fold(vec![], |mut collected_subscriptions, e| match e { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - for s in &event.subscriptions { - if s.action == GossipsubSubscriptionAction::Unsubscribe { - collected_subscriptions.push(s.clone()) - } - } - collected_subscriptions - }, - _ => collected_subscriptions, - }); - - // we sent a unsubscribe to all known peers, for two topics - assert!( - subscriptions.len() == 40, - "Should send an unsubscribe event to all known peers" - ); - - // check we clean up internal structures - for topic_hash in &topic_hashes { - assert!( - gs.mesh.get(topic_hash).is_none(), - "All topics should have been removed from the mesh" - ); - } - } - - #[test] - /// Test JOIN(topic) functionality. - fn test_join() { - // The Join function should: - // - Remove peers from fanout[topic] - // - Add any fanout[topic] peers to the mesh (up to mesh_n) - // - Fill up to mesh_n peers from known gossipsub peers in the topic - // - Send GRAFT messages to all nodes added to the mesh - - // This test is not an isolated unit test, rather it uses higher level, - // subscribe/unsubscribe to perform the test. - - let topic_strings = vec![String::from("topic1"), String::from("topic2")]; - let topics = topic_strings - .iter() - .map(|t| Topic::new(t.clone())) - .collect::>(); - - let (mut gs, _, topic_hashes) = build_and_inject_nodes(20, topic_strings, GossipsubConfig::default(), true); - - // unsubscribe, then call join to invoke functionality - assert!( - gs.unsubscribe(topics[0].clone()), - "should be able to unsubscribe successfully" - ); - assert!( - gs.unsubscribe(topics[1].clone()), - "should be able to unsubscribe successfully" - ); - - // re-subscribe - there should be peers associated with the topic - assert!( - gs.subscribe(topics[0].clone()), - "should be able to subscribe successfully" - ); - - // should have added mesh_n nodes to the mesh - assert!( - gs.mesh.get(&topic_hashes[0]).unwrap().len() == 6, - "Should have added 6 nodes to the mesh" - ); - - // there should be mesh_n GRAFT messages. - let graft_messages = gs - .control_pool - .iter() - .fold(vec![], |mut collected_grafts, (_, controls)| { - for c in controls.iter() { - if let GossipsubControlAction::Graft { topic_hash: _ } = c { - collected_grafts.push(c.clone()) - } - } - collected_grafts - }); - - assert_eq!( - graft_messages.len(), - 6, - "There should be 6 grafts messages sent to peers" - ); - - // verify fanout nodes - // add 3 random peers to the fanout[topic1] - gs.fanout.insert(topic_hashes[1].clone(), vec![]); - let new_peers = vec![]; - for _ in 0..3 { - let fanout_peers = gs.fanout.get_mut(&topic_hashes[1]).unwrap(); - fanout_peers.push(PeerId::random()); - } - - // subscribe to topic1 - gs.subscribe(topics[1].clone()); - - // the three new peers should have been added, along with 3 more from the pool. - assert!( - gs.mesh.get(&topic_hashes[1]).unwrap().len() == 6, - "Should have added 6 nodes to the mesh" - ); - let mesh_peers = gs.mesh.get(&topic_hashes[1]).unwrap(); - for new_peer in new_peers { - assert!( - mesh_peers.contains(new_peer), - "Fanout peer should be included in the mesh" - ); - } - - // there should now be 12 graft messages to be sent - let graft_messages = gs - .control_pool - .iter() - .fold(vec![], |mut collected_grafts, (_, controls)| { - for c in controls.iter() { - if let GossipsubControlAction::Graft { topic_hash: _ } = c { - collected_grafts.push(c.clone()) - } - } - collected_grafts - }); - - assert!( - graft_messages.len() == 12, - "There should be 12 grafts messages sent to peers" - ); - } - - /// Test local node publish to subscribed topic - #[test] - fn test_publish() { - // node should: - // - Send publish message to all peers - // - Insert message into gs.mcache and gs.received - - let publish_topic = String::from("test_publish"); - let (mut gs, _, topic_hashes) = - build_and_inject_nodes(20, vec![publish_topic.clone()], GossipsubConfig::default(), true); - - assert!( - gs.mesh.get(&topic_hashes[0]).is_some(), - "Subscribe should add a new entry to the mesh[topic] hashmap" - ); - - // publish on topic - let publish_data = vec![0; 42]; - gs.publish(&Topic::new(publish_topic), publish_data); - - // Collect all publish messages - let publishes = gs.events.iter().fold(vec![], |mut collected_publish, e| match e { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - for s in &event.messages { - collected_publish.push(s.clone()); - } - collected_publish - }, - _ => collected_publish, - }); - - let msg_id = (gs.config.message_id_fn)(publishes.first().expect("Should contain > 0 entries")); - - assert!( - publishes.len() == 20, - "Should send a publish message to all known peers" - ); - - assert!( - gs.mcache.get(&msg_id).is_some(), - "Message cache should contain published message" - ); - assert!( - gs.received.get(&msg_id).is_some(), - "Received cache should contain published message" - ); - } - - /// Test local node publish to unsubscribed topic - #[test] - fn test_fanout() { - // node should: - // - Populate fanout peers - // - Send publish message to fanout peers - // - Insert message into gs.mcache and gs.received - let fanout_topic = String::from("test_fanout"); - let (mut gs, _, topic_hashes) = - build_and_inject_nodes(20, vec![fanout_topic.clone()], GossipsubConfig::default(), true); - - assert!( - gs.mesh.get(&topic_hashes[0]).is_some(), - "Subscribe should add a new entry to the mesh[topic] hashmap" - ); - // Unsubscribe from topic - assert!( - gs.unsubscribe(Topic::new(fanout_topic.clone())), - "should be able to unsubscribe successfully from topic" - ); - - // Publish on unsubscribed topic - let publish_data = vec![0; 42]; - gs.publish(&Topic::new(fanout_topic.clone()), publish_data); - - assert_eq!( - gs.fanout.get(&TopicHash::from_raw(fanout_topic)).unwrap().len(), - gs.config.mesh_n, - "Fanout should contain `mesh_n` peers for fanout topic" - ); - - // Collect all publish messages - let publishes = gs.events.iter().fold(vec![], |mut collected_publish, e| match e { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - for s in &event.messages { - collected_publish.push(s.clone()); - } - collected_publish - }, - _ => collected_publish, - }); - - let msg_id = (gs.config.message_id_fn)(publishes.first().expect("Should contain > 0 entries")); - - assert_eq!( - publishes.len(), - gs.config.mesh_n, - "Should send a publish message to `mesh_n` fanout peers" - ); - - assert!( - gs.mcache.get(&msg_id).is_some(), - "Message cache should contain published message" - ); - assert!( - gs.received.get(&msg_id).is_some(), - "Received cache should contain published message" - ); - } - - #[test] - /// Test the gossipsub NetworkBehaviour peer connection logic. - fn test_inject_connected() { - let (gs, peers, topic_hashes) = build_and_inject_nodes( - 20, - vec![String::from("topic1"), String::from("topic2")], - GossipsubConfig::default(), - true, - ); - - // check that our subscriptions are sent to each of the peers - // collect all the SendEvents - let send_events: Vec<&NetworkBehaviourAction>> = gs - .events - .iter() - .filter(|e| matches!(e, NetworkBehaviourAction::NotifyHandler { .. })) - .collect(); - - // check that there are two subscriptions sent to each peer - for sevent in send_events.clone() { - if let NetworkBehaviourAction::NotifyHandler { event, .. } = sevent { - assert!( - event.subscriptions.len() == 2, - "There should be two subscriptions sent to each peer (1 for each topic)." - ); - }; - } - - // check that there are 20 send events created - assert!( - send_events.len() == 20, - "There should be a subscription event sent to each peer." - ); - - // should add the new peers to `peer_topics` with an empty vec as a gossipsub node - for peer in peers { - let known_topics = gs.peer_topics.get(&peer).unwrap(); - assert!( - known_topics == &topic_hashes, - "The topics for each node should all topics" - ); - } - } - - #[test] - /// Test subscription handling - fn test_handle_received_subscriptions() { - // For every subscription: - // SUBSCRIBE: - Add subscribed topic to peer_topics for peer. - // - Add peer to topics_peer. - // UNSUBSCRIBE - Remove topic from peer_topics for peer. - // - Remove peer from topic_peers. - - let topics = vec!["topic1", "topic2", "topic3", "topic4"] - .iter() - .map(|&t| String::from(t)) - .collect(); - let (mut gs, peers, topic_hashes) = build_and_inject_nodes(20, topics, GossipsubConfig::default(), false); - - // The first peer sends 3 subscriptions and 1 unsubscription - let mut subscriptions = topic_hashes[..3] - .iter() - .map(|topic_hash| GossipsubSubscription { - action: GossipsubSubscriptionAction::Subscribe, - topic_hash: topic_hash.clone(), - }) - .collect::>(); - - subscriptions.push(GossipsubSubscription { - action: GossipsubSubscriptionAction::Unsubscribe, - topic_hash: topic_hashes[topic_hashes.len() - 1].clone(), - }); - - let unknown_peer = PeerId::random(); - // process the subscriptions - // first and second peers send subscriptions - gs.handle_received_subscriptions(&subscriptions, &peers[0]); - gs.handle_received_subscriptions(&subscriptions, &peers[1]); - // unknown peer sends the same subscriptions - gs.handle_received_subscriptions(&subscriptions, &unknown_peer); - - // verify the result - - let peer_topics = gs.peer_topics.get(&peers[0]).unwrap().clone(); - assert!( - peer_topics == topic_hashes[..3].to_vec(), - "First peer should be subscribed to three topics" - ); - let peer_topics = gs.peer_topics.get(&peers[1]).unwrap().clone(); - assert!( - peer_topics == topic_hashes[..3].to_vec(), - "Second peer should be subscribed to three topics" - ); - - assert!( - gs.peer_topics.get(&unknown_peer).is_none(), - "Unknown peer should not have been added" - ); - - for topic_hash in topic_hashes[..3].iter() { - let topic_peers = gs.topic_peers.get(topic_hash).unwrap().clone(); - assert!( - topic_peers == peers[..2].to_vec(), - "Two peers should be added to the first three topics" - ); - } - - // Peer 0 unsubscribes from the first topic - - gs.handle_received_subscriptions( - &[GossipsubSubscription { - action: GossipsubSubscriptionAction::Unsubscribe, - topic_hash: topic_hashes[0].clone(), - }], - &peers[0], - ); - - let peer_topics = gs.peer_topics.get(&peers[0]).unwrap().clone(); - assert!( - peer_topics == topic_hashes[1..3].to_vec(), - "Peer should be subscribed to two topics" - ); - - let topic_peers = gs.topic_peers.get(&topic_hashes[0]).unwrap().clone(); // only gossipsub at the moment - assert!( - topic_peers == peers[1..2].to_vec(), - "Only the second peers should be in the first topic" - ); - } - - #[test] - /// Test Gossipsub.get_random_peers() function - fn test_get_random_peers() { - // generate a default GossipsubConfig - let gs_config = GossipsubConfig::default(); - // create a gossipsub struct - let mut gs: Gossipsub = Gossipsub::new(PeerId::random(), gs_config); - - // create a topic and fill it with some peers - let topic_hash = Topic::new("Test".into()).no_hash(); - let mut peers = vec![]; - for _ in 0..20 { - peers.push(PeerId::random()) - } - - gs.topic_peers.insert(topic_hash.clone(), peers.clone()); - - let random_peers = Gossipsub::get_random_peers(&gs.topic_peers, &topic_hash, 5, |_| true); - assert!(random_peers.len() == 5, "Expected 5 peers to be returned"); - let random_peers = Gossipsub::get_random_peers(&gs.topic_peers, &topic_hash, 30, |_| true); - assert!(random_peers.len() == 20, "Expected 20 peers to be returned"); - assert!(random_peers == peers, "Expected no shuffling"); - let random_peers = Gossipsub::get_random_peers(&gs.topic_peers, &topic_hash, 20, |_| true); - assert!(random_peers.len() == 20, "Expected 20 peers to be returned"); - assert!(random_peers == peers, "Expected no shuffling"); - let random_peers = Gossipsub::get_random_peers(&gs.topic_peers, &topic_hash, 0, |_| true); - assert!(random_peers.is_empty(), "Expected 0 peers to be returned"); - // test the filter - let random_peers = Gossipsub::get_random_peers(&gs.topic_peers, &topic_hash, 5, |_| false); - assert!(random_peers.is_empty(), "Expected 0 peers to be returned"); - let random_peers = Gossipsub::get_random_peers(&gs.topic_peers, &topic_hash, 10, |peer| peers.contains(peer)); - assert!(random_peers.len() == 10, "Expected 10 peers to be returned"); - } - - /// Tests that the correct message is sent when a peer asks for a message in our cache. - #[test] - fn test_handle_iwant_msg_cached() { - let (mut gs, peers, _) = build_and_inject_nodes(20, Vec::new(), GossipsubConfig::default(), true); - - let id = gs.config.message_id_fn; - - let message = GossipsubMessage { - source: peers[11], - data: vec![1, 2, 3, 4], - sequence_number: 1u64, - topics: Vec::new(), - }; - let msg_id = id(&message); - gs.mcache.put(message); - - gs.handle_iwant(&peers[7], vec![msg_id.clone()]); - - // the messages we are sending - let sent_messages = gs.events.iter().fold(vec![], |mut collected_messages, e| match e { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - for c in &event.messages { - collected_messages.push(c.clone()) - } - collected_messages - }, - _ => collected_messages, - }); - - assert!( - sent_messages.iter().any(|msg| id(msg) == msg_id), - "Expected the cached message to be sent to an IWANT peer" - ); - } - - /// Tests that messages are sent correctly depending on the shifting of the message cache. - #[test] - fn test_handle_iwant_msg_cached_shifted() { - let (mut gs, peers, _) = build_and_inject_nodes(20, Vec::new(), GossipsubConfig::default(), true); - - let id = gs.config.message_id_fn; - // perform 10 memshifts and check that it leaves the cache - for shift in 1..10 { - let message = GossipsubMessage { - source: peers[11], - data: vec![1, 2, 3, 4], - sequence_number: shift, - topics: Vec::new(), - }; - let msg_id = id(&message); - gs.mcache.put(message.clone()); - for _ in 0..shift { - gs.mcache.shift(); - } - - gs.handle_iwant(&peers[7], vec![msg_id.clone()]); - - // is the message is being sent? - let message_exists = gs.events.iter().any(|e| match e { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - event.messages.iter().any(|msg| id(msg) == msg_id) - }, - _ => false, - }); - // default history_length is 5, expect no messages after shift > 5 - if shift < 5 { - assert!( - message_exists, - "Expected the cached message to be sent to an IWANT peer before 5 shifts" - ); - } else { - assert!( - !message_exists, - "Expected the cached message to not be sent to an IWANT peer after 5 shifts" - ); - } - } - } - - #[test] - // tests that an event is not created when a peers asks for a message not in our cache - fn test_handle_iwant_msg_not_cached() { - let (mut gs, peers, _) = build_and_inject_nodes(20, Vec::new(), GossipsubConfig::default(), true); - - let events_before = gs.events.len(); - gs.handle_iwant(&peers[7], vec![MessageId(String::from("unknown id"))]); - let events_after = gs.events.len(); - - assert_eq!(events_before, events_after, "Expected event count to stay the same"); - } - - #[test] - // tests that an event is created when a peer shares that it has a message we want - fn test_handle_ihave_subscribed_and_msg_not_cached() { - let (mut gs, peers, topic_hashes) = - build_and_inject_nodes(20, vec![String::from("topic1")], GossipsubConfig::default(), true); - - gs.handle_ihave(&peers[7], vec![(topic_hashes[0].clone(), vec![MessageId( - String::from("unknown id"), - )])]); - - // check that we sent an IWANT request for `unknown id` - let iwant_exists = match gs.control_pool.get(&peers[7]) { - Some(controls) => controls.iter().any(|c| match c { - GossipsubControlAction::IWant { message_ids } => - { - #[allow(clippy::cmp_owned)] - message_ids.iter().any(|m| *m.0 == String::from("unknown id")) - }, - _ => false, - }), - _ => false, - }; - - assert!( - iwant_exists, - "Expected to send an IWANT control message for unkown message id" - ); - } - - #[test] - // tests that an event is not created when a peer shares that it has a message that - // we already have - fn test_handle_ihave_subscribed_and_msg_cached() { - let (mut gs, peers, topic_hashes) = - build_and_inject_nodes(20, vec![String::from("topic1")], GossipsubConfig::default(), true); - - let msg_id = MessageId(String::from("known id")); - gs.received.insert(msg_id.clone(), SmallVec::new()); - - let events_before = gs.events.len(); - gs.handle_ihave(&peers[7], vec![(topic_hashes[0].clone(), vec![msg_id])]); - let events_after = gs.events.len(); - - assert_eq!(events_before, events_after, "Expected event count to stay the same") - } - - #[test] - // test that an event is not created when a peer shares that it has a message in - // a topic that we are not subscribed to - fn test_handle_ihave_not_subscribed() { - let (mut gs, peers, _) = build_and_inject_nodes(20, vec![], GossipsubConfig::default(), true); - - let events_before = gs.events.len(); - gs.handle_ihave(&peers[7], vec![( - TopicHash::from_raw(String::from("unsubscribed topic")), - vec![MessageId(String::from("irrelevant id"))], - )]); - let events_after = gs.events.len(); - - assert_eq!(events_before, events_after, "Expected event count to stay the same") - } - - #[test] - // tests that a peer is added to our mesh when we are both subscribed - // to the same topic - fn test_handle_graft_is_subscribed() { - let (mut gs, peers, topic_hashes) = - build_and_inject_nodes(20, vec![String::from("topic1")], GossipsubConfig::default(), true); - - gs.handle_graft(&peers[7], topic_hashes.clone()); - - assert!( - gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]), - "Expected peer to have been added to mesh" - ); - } - - #[test] - // tests that a peer is not added to our mesh when they are subscribed to - // a topic that we are not - fn test_handle_graft_is_not_subscribed() { - let (mut gs, peers, topic_hashes) = - build_and_inject_nodes(20, vec![String::from("topic1")], GossipsubConfig::default(), true); - - gs.handle_graft(&peers[7], vec![TopicHash::from_raw(String::from("unsubscribed topic"))]); - - assert!( - gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]), - "Expected peer to have been added to mesh" - ); - } - - #[test] - // tests multiple topics in a single graft message - fn test_handle_graft_multiple_topics() { - let topics: Vec = vec!["topic1", "topic2", "topic3", "topic4"] - .iter() - .map(|&t| String::from(t)) - .collect(); - - let (mut gs, peers, topic_hashes) = build_and_inject_nodes(20, topics, GossipsubConfig::default(), true); - - let mut their_topics = topic_hashes.clone(); - // their_topics = [topic1, topic2, topic3] - // our_topics = [topic1, topic2, topic4] - their_topics.pop(); - gs.leave(&their_topics[2]); - - gs.handle_graft(&peers[7], their_topics.clone()); - - for item in topic_hashes.iter().take(2) { - assert!( - gs.mesh.get(item).unwrap().contains(&peers[7]), - "Expected peer to be in the mesh for the first 2 topics" - ); - } - - assert!( - gs.mesh.get(&topic_hashes[2]).is_none(), - "Expected the second topic to not be in the mesh" - ); - } - - #[test] - // tests that a peer is removed from our mesh - fn test_handle_prune_peer_in_mesh() { - let (mut gs, peers, topic_hashes) = - build_and_inject_nodes(20, vec![String::from("topic1")], GossipsubConfig::default(), true); - - // insert peer into our mesh for 'topic1' - gs.mesh.insert(topic_hashes[0].clone(), peers.clone()); - assert!( - gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]), - "Expected peer to be in mesh" - ); - - gs.handle_prune(&peers[7], topic_hashes.clone()); - assert!( - !gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]), - "Expected peer to be removed from mesh" - ); - } - - #[test] - fn test_maintain_relays_mesh_n_high() { - let peer_no = 20; - let (mut gs, peers, _) = build_and_inject_nodes(peer_no, vec![], GossipsubConfig::default(), false); - for peer in peers { - gs.relays_mesh.insert(peer, 0); - } - assert_eq!(peer_no, gs.relays_mesh.len(), "relays mesh must contain 20 peers"); - gs.maintain_relays_mesh(); - assert_eq!( - gs.config.mesh_n, - gs.relays_mesh.len(), - "relays mesh must contain mesh_n peers after maintenance" - ); - assert_eq!(gs.events.len(), 14); - for event in gs.events { - match event { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - assert_eq!(event.control_msgs, vec![GossipsubControlAction::IncludedToRelaysMesh { - included: false, - mesh_size: gs.relays_mesh.len(), - }]); - }, - _ => panic!("Invalid NetworkBehaviourAction variant"), - } - } - } - - #[test] - fn test_maintain_relays_mesh_n_low() { - let peer_no = 20; - let (mut gs, peers, _) = build_and_inject_nodes(peer_no, vec![], GossipsubConfig::default(), false); - for (i, peer) in peers.into_iter().enumerate() { - if i < 3 { - gs.relays_mesh.insert(peer, 0); - } else { - gs.connected_relays.insert(peer); - } - } - assert_eq!(3, gs.relays_mesh.len(), "relays mesh must contain 3 peers"); - gs.maintain_relays_mesh(); - assert_eq!( - 4, - gs.relays_mesh.len(), - "relays mesh must contain 1 more peer after maintenance (4 total)" - ); - assert_eq!(gs.events.len(), 1); - for event in gs.events { - match event { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - assert_eq!(event.control_msgs, vec![GossipsubControlAction::IncludedToRelaysMesh { - included: true, - mesh_size: gs.relays_mesh.len(), - }]); - }, - _ => panic!("Invalid NetworkBehaviourAction variant"), - } - } - } - - #[test] - fn test_process_included_to_relays_mesh() { - let peer_no = 2; - let config = GossipsubConfigBuilder::default().i_am_relay(true).build(); - let (mut gs, peers, _) = build_and_inject_nodes(peer_no, vec![], config, false); - for peer in &peers { - gs.connected_relays.insert(*peer); - } - - gs.handle_included_to_relays_mesh(&peers[0], true, 1); - assert!(gs.relays_mesh.contains_key(&peers[0])); - - gs.handle_included_to_relays_mesh(&peers[0], false, 1); - assert!(!gs.relays_mesh.contains_key(&peers[0])); - } - - #[test] - fn test_process_included_to_relays_mesh_n_high_exceeded() { - let peer_no = 14; - let config = GossipsubConfigBuilder::default().i_am_relay(true).build(); - let (mut gs, peers, _) = build_and_inject_nodes(peer_no, vec![], config, false); - for (i, peer) in peers.iter().enumerate() { - gs.connected_relays.insert(*peer); - if i < 13 { - gs.relays_mesh.insert(*peer, 0); - } - } - - gs.handle_included_to_relays_mesh(&peers[13], true, 1); - assert!(!gs.relays_mesh.contains_key(&peers[13])); - - match gs.events.pop_back().unwrap() { - NetworkBehaviourAction::NotifyHandler { event, peer_id, .. } => { - assert_eq!(event.control_msgs, vec![GossipsubControlAction::IncludedToRelaysMesh { - included: false, - mesh_size: gs.relay_mesh_len(), - }]); - assert_eq!(peer_id, peers[13]); - }, - _ => panic!("Invalid NetworkBehaviourAction variant"), - } - } -} diff --git a/mm2src/gossipsub/src/config.rs b/mm2src/gossipsub/src/config.rs deleted file mode 100644 index f74343e21d..0000000000 --- a/mm2src/gossipsub/src/config.rs +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::protocol::{GossipsubMessage, MessageId}; -use std::borrow::Cow; -use std::time::Duration; - -/// If the `no_source_id` flag is set, the IDENTITY_SOURCE value is used as the source of the -/// packet. -pub const IDENTITY_SOURCE: [u8; 3] = [0, 1, 0]; - -/// Configuration parameters that define the performance of the gossipsub network. -#[derive(Clone)] -pub struct GossipsubConfig { - /// The protocol id to negotiate this protocol (default is `/meshsub/1.0.0`). - pub protocol_id: Cow<'static, [u8]>, - - // Overlay network parameters. - /// Number of heartbeats to keep in the `memcache` (default is 5). - pub history_length: usize, - - /// Number of past heartbeats to gossip about (default is 3). - pub history_gossip: usize, - - /// Target number of peers for the mesh network (D in the spec, default is 6). - pub mesh_n: usize, - - /// Minimum number of peers in mesh network before adding more (D_lo in the spec, default is 4). - pub mesh_n_low: usize, - - /// Maximum number of peers in mesh network before removing some (D_high in the spec, default - /// is 12). - pub mesh_n_high: usize, - - /// Number of peers to emit gossip to during a heartbeat (D_lazy in the spec, default is 6). - pub gossip_lazy: usize, - - /// Initial delay in each heartbeat (default is 5 seconds). - pub heartbeat_initial_delay: Duration, - - /// Time between each heartbeat (default is 1 second). - pub heartbeat_interval: Duration, - - /// Time to live for fanout peers (default is 60 seconds). - pub fanout_ttl: Duration, - - /// The maximum byte size for each gossip (default is 2048 bytes). - pub max_transmit_size: usize, - - /// Duplicates are prevented by storing message id's of known messages in an LRU time cache. - /// This settings sets the time period that messages are stored in the cache. Duplicates can be - /// received if duplicate messages are sent at a time greater than this setting apart. The - /// default is 1 minute. - pub duplicate_cache_time: Duration, - - /// Flag determining if gossipsub topics are hashed or sent as plain strings (default is false). - pub hash_topics: bool, - - /// When set, all published messages will have a 0 source `PeerId` (default is false). - pub no_source_id: bool, - - /// When set to `true`, prevents automatic forwarding of all received messages. This setting - /// allows a user to validate the messages before propagating them to their peers. If set to - /// true, the user must manually call `propagate_message()` on the behaviour to forward message - /// once validated (default is false). - pub manual_propagation: bool, - - /// A user-defined function allowing the user to specify the message id of a gossipsub message. - /// The default value is to concatenate the source peer id with a sequence number. Setting this - /// parameter allows the user to address packets arbitrarily. One example is content based - /// addressing, where this function may be set to `hash(message)`. This would prevent messages - /// of the same content from being duplicated. - /// - /// The function takes a `GossipsubMessage` as input and outputs a String to be interpreted as - /// the message id. - pub message_id_fn: fn(&GossipsubMessage) -> MessageId, - - pub i_am_relay: bool, -} - -impl Default for GossipsubConfig { - fn default() -> GossipsubConfig { - GossipsubConfig { - protocol_id: Cow::Borrowed(b"/meshsub/1.0.0"), - history_length: 5, - history_gossip: 3, - mesh_n: 6, - mesh_n_low: 4, - mesh_n_high: 12, - gossip_lazy: 6, // default to mesh_n - heartbeat_initial_delay: Duration::from_secs(5), - heartbeat_interval: Duration::from_secs(1), - fanout_ttl: Duration::from_secs(60), - max_transmit_size: 2048, - duplicate_cache_time: Duration::from_secs(60), - hash_topics: false, // default compatibility with floodsub - no_source_id: false, - manual_propagation: false, - message_id_fn: |message| { - // default message id is: source + sequence number - let mut source_string = message.source.to_base58(); - source_string.push_str(&message.sequence_number.to_string()); - MessageId(source_string) - }, - i_am_relay: false, - } - } -} - -#[derive(Default)] -pub struct GossipsubConfigBuilder { - config: GossipsubConfig, -} - -impl GossipsubConfigBuilder { - // set default values - pub fn new() -> GossipsubConfigBuilder { GossipsubConfigBuilder::default() } - - pub fn protocol_id(&mut self, protocol_id: impl Into>) -> &mut Self { - self.config.protocol_id = protocol_id.into(); - self - } - - pub fn history_length(&mut self, history_length: usize) -> &mut Self { - assert!( - history_length >= self.config.history_gossip, - "The history_length must be greater than or equal to the history_gossip length" - ); - self.config.history_length = history_length; - self - } - - pub fn history_gossip(&mut self, history_gossip: usize) -> &mut Self { - assert!( - self.config.history_length >= history_gossip, - "The history_length must be greater than or equal to the history_gossip length" - ); - self.config.history_gossip = history_gossip; - self - } - - pub fn mesh_n(&mut self, mesh_n: usize) -> &mut Self { - assert!( - self.config.mesh_n_low <= mesh_n && mesh_n <= self.config.mesh_n_high, - "The following equality doesn't hold mesh_n_low <= mesh_n <= mesh_n_high" - ); - self.config.mesh_n = mesh_n; - self - } - - pub fn mesh_n_low(&mut self, mesh_n_low: usize) -> &mut Self { - assert!( - mesh_n_low <= self.config.mesh_n && self.config.mesh_n <= self.config.mesh_n_high, - "The following equality doesn't hold mesh_n_low <= mesh_n <= mesh_n_high" - ); - self.config.mesh_n_low = mesh_n_low; - self - } - - pub fn mesh_n_high(&mut self, mesh_n_high: usize) -> &mut Self { - assert!( - self.config.mesh_n_low <= self.config.mesh_n && self.config.mesh_n <= mesh_n_high, - "The following equality doesn't hold mesh_n_low <= mesh_n <= mesh_n_high" - ); - self.config.mesh_n_high = mesh_n_high; - self - } - - pub fn gossip_lazy(&mut self, gossip_lazy: usize) -> &mut Self { - self.config.gossip_lazy = gossip_lazy; - self - } - - pub fn heartbeat_initial_delay(&mut self, heartbeat_initial_delay: Duration) -> &mut Self { - self.config.heartbeat_initial_delay = heartbeat_initial_delay; - self - } - pub fn heartbeat_interval(&mut self, heartbeat_interval: Duration) -> &mut Self { - self.config.heartbeat_interval = heartbeat_interval; - self - } - pub fn fanout_ttl(&mut self, fanout_ttl: Duration) -> &mut Self { - self.config.fanout_ttl = fanout_ttl; - self - } - pub fn max_transmit_size(&mut self, max_transmit_size: usize) -> &mut Self { - self.config.max_transmit_size = max_transmit_size; - self - } - - pub fn hash_topics(&mut self) -> &mut Self { - self.config.hash_topics = true; - self - } - - pub fn no_source_id(&mut self) -> &mut Self { - self.config.no_source_id = true; - self - } - - pub fn manual_propagation(&mut self) -> &mut Self { - self.config.manual_propagation = true; - self - } - - pub fn message_id_fn(&mut self, id_fn: fn(&GossipsubMessage) -> MessageId) -> &mut Self { - self.config.message_id_fn = id_fn; - self - } - - pub fn i_am_relay(&mut self, i_am_relay: bool) -> &mut Self { - self.config.i_am_relay = i_am_relay; - self - } - - pub fn build(&self) -> GossipsubConfig { self.config.clone() } -} - -impl std::fmt::Debug for GossipsubConfig { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let mut builder = f.debug_struct("GossipsubConfig"); - let _ = builder.field("protocol_id", &self.protocol_id); - let _ = builder.field("history_length", &self.history_length); - let _ = builder.field("history_gossip", &self.history_gossip); - let _ = builder.field("mesh_n", &self.mesh_n); - let _ = builder.field("mesh_n_low", &self.mesh_n_low); - let _ = builder.field("mesh_n_high", &self.mesh_n_high); - let _ = builder.field("gossip_lazy", &self.gossip_lazy); - let _ = builder.field("heartbeat_initial_delay", &self.heartbeat_initial_delay); - let _ = builder.field("heartbeat_interval", &self.heartbeat_interval); - let _ = builder.field("fanout_ttl", &self.fanout_ttl); - let _ = builder.field("max_transmit_size", &self.max_transmit_size); - let _ = builder.field("hash_topics", &self.hash_topics); - let _ = builder.field("no_source_id", &self.no_source_id); - let _ = builder.field("manual_propagation", &self.manual_propagation); - let _ = builder.field("i_am_relay", &self.i_am_relay); - builder.finish() - } -} diff --git a/mm2src/gossipsub/src/handler.rs b/mm2src/gossipsub/src/handler.rs deleted file mode 100644 index 2b71cf13dd..0000000000 --- a/mm2src/gossipsub/src/handler.rs +++ /dev/null @@ -1,315 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::behaviour::GossipsubRpc; -use crate::protocol::{GossipsubCodec, ProtocolConfig}; -use futures::prelude::*; -use futures_codec::Framed; -use libp2p_core::upgrade::{InboundUpgrade, OutboundUpgrade}; -use libp2p_swarm::handler::{ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerUpgrErr, KeepAlive, - SubstreamProtocol}; -use libp2p_swarm::NegotiatedSubstream; -use log::{debug, error, trace, warn}; -use smallvec::SmallVec; -use std::{borrow::Cow, - io, - pin::Pin, - task::{Context, Poll}}; - -/// Protocol Handler that manages a single long-lived substream with a peer. -pub struct GossipsubHandler { - /// Upgrade configuration for the gossipsub protocol. - listen_protocol: SubstreamProtocol, - - /// The single long-lived outbound substream. - outbound_substream: Option, - - /// The single long-lived inbound substream. - inbound_substream: Option, - - /// Queue of values that we want to send to the remote. - send_queue: SmallVec<[GossipsubRpc; 16]>, - - /// Flag indicating that an outbound substream is being established to prevent duplicate - /// requests. - outbound_substream_establishing: bool, - - /// Flag determining whether to maintain the connection to the peer. - keep_alive: KeepAlive, -} - -/// State of the inbound substream, opened either by us or by the remote. -#[allow(clippy::large_enum_variant)] -enum InboundSubstreamState { - /// Waiting for a message from the remote. The idle state for an inbound substream. - WaitingInput(Framed), - /// The substream is being closed. - Closing(Framed), - /// An error occurred during processing. - Poisoned, -} - -/// State of the outbound substream, opened either by us or by the remote. -#[allow(clippy::large_enum_variant)] -enum OutboundSubstreamState { - /// Waiting for the user to send a message. The idle state for an outbound substream. - WaitingOutput(Framed), - /// Waiting to send a message to the remote. - PendingSend(Framed, GossipsubRpc), - /// Waiting to flush the substream so that the data arrives to the remote. - PendingFlush(Framed), - /// The substream is being closed. Used by either substream. - _Closing(Framed), - /// An error occurred during processing. - Poisoned, -} - -impl GossipsubHandler { - /// Builds a new `GossipsubHandler`. - pub fn new(protocol_id: impl Into>, max_transmit_size: usize) -> Self { - GossipsubHandler { - listen_protocol: SubstreamProtocol::new(ProtocolConfig::new(protocol_id, max_transmit_size), ()), - inbound_substream: None, - outbound_substream: None, - send_queue: SmallVec::new(), - keep_alive: KeepAlive::Yes, - outbound_substream_establishing: false, - } - } -} - -impl Default for GossipsubHandler { - fn default() -> Self { - GossipsubHandler { - listen_protocol: SubstreamProtocol::new(ProtocolConfig::default(), ()), - inbound_substream: None, - outbound_substream: None, - send_queue: SmallVec::new(), - keep_alive: KeepAlive::Yes, - outbound_substream_establishing: false, - } - } -} - -impl ConnectionHandler for GossipsubHandler { - type InEvent = GossipsubRpc; - type OutEvent = GossipsubRpc; - type Error = io::Error; - type InboundProtocol = ProtocolConfig; - type OutboundProtocol = ProtocolConfig; - type OutboundOpenInfo = GossipsubRpc; - type InboundOpenInfo = (); - - fn listen_protocol(&self) -> SubstreamProtocol { - self.listen_protocol.clone() - } - - fn inject_fully_negotiated_inbound( - &mut self, - substream: >::Output, - _info: Self::InboundOpenInfo, - ) { - // new inbound substream. Replace the current one, if it exists. - trace!("New inbound substream request"); - self.inbound_substream = Some(InboundSubstreamState::WaitingInput(substream)); - } - - fn inject_fully_negotiated_outbound( - &mut self, - substream: >::Output, - message: Self::OutboundOpenInfo, - ) { - self.outbound_substream_establishing = false; - // Should never establish a new outbound substream if one already exists. - // If this happens, an outbound message is not sent. - if self.outbound_substream.is_some() { - warn!("Established an outbound substream with one already available"); - // Add the message back to the send queue - self.send_queue.push(message); - } else { - self.outbound_substream = Some(OutboundSubstreamState::PendingSend(substream, message)); - } - } - - fn inject_event(&mut self, message: GossipsubRpc) { self.send_queue.push(message); } - - fn inject_dial_upgrade_error( - &mut self, - _: Self::OutboundOpenInfo, - _: ConnectionHandlerUpgrErr<>::Error>, - ) { - self.outbound_substream_establishing = false; - // Ignore upgrade errors for now. - // If a peer doesn't support this protocol, this will just ignore them, but not disconnect - // them. - } - - fn connection_keep_alive(&self) -> KeepAlive { self.keep_alive } - - #[allow(clippy::type_complexity)] - fn poll( - &mut self, - cx: &mut Context, - ) -> Poll> { - // determine if we need to create the stream - if !self.send_queue.is_empty() && self.outbound_substream.is_none() && !self.outbound_substream_establishing { - let message = self.send_queue.remove(0); - self.send_queue.shrink_to_fit(); - self.outbound_substream_establishing = true; - return Poll::Ready(ConnectionHandlerEvent::OutboundSubstreamRequest { - protocol: self.listen_protocol.clone().map_info(|()| message), - }); - } - - loop { - match std::mem::replace(&mut self.inbound_substream, Some(InboundSubstreamState::Poisoned)) { - // inbound idle state - Some(InboundSubstreamState::WaitingInput(mut substream)) => { - match substream.poll_next_unpin(cx) { - Poll::Ready(Some(Ok(message))) => { - self.inbound_substream = Some(InboundSubstreamState::WaitingInput(substream)); - return Poll::Ready(ConnectionHandlerEvent::Custom(message)); - }, - Poll::Ready(Some(Err(e))) => { - debug!("Inbound substream error while awaiting input: {:?}", e); - self.inbound_substream = Some(InboundSubstreamState::Closing(substream)); - }, - // peer closed the stream - Poll::Ready(None) => { - self.inbound_substream = Some(InboundSubstreamState::Closing(substream)); - }, - Poll::Pending => { - self.inbound_substream = Some(InboundSubstreamState::WaitingInput(substream)); - break; - }, - } - }, - Some(InboundSubstreamState::Closing(mut substream)) => { - match Sink::poll_close(Pin::new(&mut substream), cx) { - Poll::Ready(res) => { - if let Err(e) = res { - // Don't close the connection but just drop the inbound substream. - // In case the remote has more to send, they will open up a new - // substream. - debug!("Inbound substream error while closing: {:?}", e); - } - - self.inbound_substream = None; - if self.outbound_substream.is_none() { - self.keep_alive = KeepAlive::No; - } - break; - }, - Poll::Pending => { - self.inbound_substream = Some(InboundSubstreamState::Closing(substream)); - break; - }, - } - }, - None => { - self.inbound_substream = None; - break; - }, - Some(InboundSubstreamState::Poisoned) => panic!("Error occurred during inbound stream processing"), - } - } - - loop { - match std::mem::replace(&mut self.outbound_substream, Some(OutboundSubstreamState::Poisoned)) { - // outbound idle state - Some(OutboundSubstreamState::WaitingOutput(substream)) => { - if !self.send_queue.is_empty() { - let message = self.send_queue.remove(0); - self.send_queue.shrink_to_fit(); - self.outbound_substream = Some(OutboundSubstreamState::PendingSend(substream, message)); - } else { - self.outbound_substream = Some(OutboundSubstreamState::WaitingOutput(substream)); - break; - } - }, - Some(OutboundSubstreamState::PendingSend(mut substream, message)) => { - match Sink::poll_ready(Pin::new(&mut substream), cx) { - Poll::Ready(Ok(())) => match Sink::start_send(Pin::new(&mut substream), message) { - Ok(()) => self.outbound_substream = Some(OutboundSubstreamState::PendingFlush(substream)), - Err(e) => { - if let io::ErrorKind::PermissionDenied = e.kind() { - error!("Message over the maximum transmission limit was not sent."); - self.outbound_substream = Some(OutboundSubstreamState::WaitingOutput(substream)); - } else { - return Poll::Ready(ConnectionHandlerEvent::Close(e)); - } - }, - }, - Poll::Ready(Err(e)) => { - debug!("Outbound substream error while sending output: {:?}", e); - return Poll::Ready(ConnectionHandlerEvent::Close(e)); - }, - Poll::Pending => { - self.outbound_substream = Some(OutboundSubstreamState::PendingSend(substream, message)); - break; - }, - } - }, - Some(OutboundSubstreamState::PendingFlush(mut substream)) => { - match Sink::poll_flush(Pin::new(&mut substream), cx) { - Poll::Ready(Ok(())) => { - self.outbound_substream = Some(OutboundSubstreamState::WaitingOutput(substream)) - }, - Poll::Ready(Err(e)) => return Poll::Ready(ConnectionHandlerEvent::Close(e)), - Poll::Pending => { - self.outbound_substream = Some(OutboundSubstreamState::PendingFlush(substream)); - break; - }, - } - }, - // Currently never used - manual shutdown may implement this in the future - Some(OutboundSubstreamState::_Closing(mut substream)) => { - match Sink::poll_close(Pin::new(&mut substream), cx) { - Poll::Ready(Ok(())) => { - self.outbound_substream = None; - if self.inbound_substream.is_none() { - self.keep_alive = KeepAlive::No; - } - break; - }, - Poll::Ready(Err(e)) => { - debug!("Outbound substream error while closing: {:?}", e); - return Poll::Ready(ConnectionHandlerEvent::Close(io::Error::new( - io::ErrorKind::BrokenPipe, - "Failed to close outbound substream", - ))); - }, - Poll::Pending => { - self.outbound_substream = Some(OutboundSubstreamState::_Closing(substream)); - break; - }, - } - }, - None => { - self.outbound_substream = None; - break; - }, - Some(OutboundSubstreamState::Poisoned) => panic!("Error occurred during outbound stream processing"), - } - } - - Poll::Pending - } -} diff --git a/mm2src/gossipsub/src/lib.rs b/mm2src/gossipsub/src/lib.rs deleted file mode 100644 index e0efa95571..0000000000 --- a/mm2src/gossipsub/src/lib.rs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Gossipsub is a P2P pubsub (publish/subscription) routing layer designed to extend upon -//! flooodsub and meshsub routing protocols. -//! -//! # Overview -//! -//! *Note: The gossipsub protocol specifications -//! (https://github.com/libp2p/specs/tree/master/pubsub/gossipsub) provide an outline for the -//! routing protocol. They should be consulted for further detail.* -//! -//! Gossipsub is a blend of meshsub for data and randomsub for mesh metadata. It provides bounded -//! degree and amplification factor with the meshsub construction and augments it using gossip -//! propagation of metadata with the randomsub technique. -//! -//! The router maintains an overlay mesh network of peers on which to efficiently send messages and -//! metadata. Peers use control messages to broadcast and request known messages and -//! subscribe/unsubscribe from topics in the mesh network. -//! -//! # Important Discrepancies -//! -//! This section outlines the current implementation's potential discrepancies from that of other -//! implementations, due to undefined elements in the current specification. -//! -//! - **Topics** - In gossipsub, topics configurable by the `hash_topics` configuration parameter. -//! Topics are of type `TopicHash`. The current go implementation uses raw utf-8 strings, and this -//! is default configuration in rust-libp2p. Topics can be hashed (SHA256 hashed then base64 -//! encoded) by setting the `hash_topics` configuration parameter to true. -//! -//! - **Sequence Numbers** - A message on the gossipsub network is identified by the source -//! `PeerId` and a nonce (sequence number) of the message. The sequence numbers in this -//! implementation are sent as raw bytes across the wire. They are 64-bit big-endian unsigned -//! integers. They are chosen at random in this implementation of gossipsub, but are sequential in -//! the current go implementation. -//! -//! # Using Gossipsub -//! -//! ## GossipsubConfig -//! -//! The [`GossipsubConfig`] struct specifies various network performance/tuning configuration -//! parameters. Specifically it specifies: -//! -//! [`GossipsubConfig`]: struct.GossipsubConfig.html -//! -//! - `protocol_id` - The protocol id that this implementation will accept connections on. -//! - `history_length` - The number of heartbeats which past messages are kept in cache (default: 5). -//! - `history_gossip` - The number of past heartbeats that the node will send gossip metadata -//! about (default: 3). -//! - `mesh_n` - The target number of peers store in the local mesh network. -//! (default: 6). -//! - `mesh_n_low` - The minimum number of peers in the local mesh network before. -//! trying to add more peers to the mesh from the connected peer pool (default: 4). -//! - `mesh_n_high` - The maximum number of peers in the local mesh network before removing peers to -//! reach `mesh_n` peers (default: 12). -//! - `gossip_lazy` - The number of peers that the local node will gossip to during a heartbeat (default: `mesh_n` = 6). -//! - `heartbeat_initial_delay - The initial time delay before starting the first heartbeat (default: 5 seconds). -//! - `heartbeat_interval` - The time between each heartbeat (default: 1 second). -//! - `fanout_ttl` - The fanout time to live time period. The timeout required before removing peers from the fanout -//! for a given topic (default: 1 minute). -//! - `max_transmit_size` - This sets the maximum transmission size for total gossipsub messages on the network. -//! - `hash_topics` - Whether to hash the topics using base64(SHA256(topic)) or to leave as plain utf-8 strings. -//! - `manual_propagation` - Whether gossipsub should immediately forward received messages on the -//! network. For applications requiring message validation, this should be set to false, then the -//! application should call `propagate_message(message_id, propagation_source)` once validated, to -//! propagate the message to peers. -//! -//! This struct implements the `Default` trait and can be initialised via -//! `GossipsubConfig::default()`. -//! -//! -//! ## Gossipsub -//! -//! The [`Gossipsub`] struct implements the `NetworkBehaviour` trait allowing it to act as the -//! routing behaviour in a `Swarm`. This struct requires an instance of `PeerId` and -//! [`GossipsubConfig`]. -//! -//! [`Gossipsub`]: struct.Gossipsub.html - -//! ## Example -//! -//! An example of initialising a gossipsub compatible swarm: -//! -//! ```ignore -//! #extern crate libp2p; -//! #extern crate futures; -//! #extern crate tokio; -//! #use libp2p::gossipsub::GossipsubEvent; -//! #use libp2p::{gossipsub, secio, -//! # tokio_codec::{FramedRead, LinesCodec}, -//! #}; -//! let local_key = secio::SecioKeyPair::ed25519_generated().unwrap(); -//! let local_pub_key = local_key.to_public_key(); -//! -//! // Set up an encrypted TCP Transport over the Mplex and Yamux protocols -//! let transport = libp2p::build_development_transport(local_key); -//! -//! // Create a Floodsub/Gossipsub topic -//! let topic = libp2p::floodsub::TopicBuilder::new("example").build(); -//! -//! // Create a Swarm to manage peers and events -//! let mut swarm = { -//! // set default parameters for gossipsub -//! let gossipsub_config = gossipsub::GossipsubConfig::default(); -//! // build a gossipsub network behaviour -//! let mut gossipsub = -//! gossipsub::Gossipsub::new(local_pub_key.clone().into_peer_id(), gossipsub_config); -//! gossipsub.subscribe(topic.clone()); -//! libp2p::Swarm::new( -//! transport, -//! gossipsub, -//! libp2p::core::topology::MemoryTopology::empty(local_pub_key), -//! ) -//! }; -//! -//! // Listen on all interfaces and whatever port the OS assigns -//! let addr = libp2p::Swarm::listen_on(&mut swarm, "/ip4/0.0.0.0/tcp/0".parse().unwrap()).unwrap(); -//! println!("Listening on {:?}", addr); -//! ``` - -pub mod protocol; - -mod behaviour; -mod config; -mod handler; -mod mcache; -mod topic; - -mod rpc_proto { - include!(concat!(env!("OUT_DIR"), "/gossipsub.pb.rs")); -} - -pub use self::behaviour::{Gossipsub, GossipsubEvent, GossipsubRpc}; -pub use self::config::{GossipsubConfig, GossipsubConfigBuilder}; -pub use self::protocol::{GossipsubMessage, MessageId}; -pub use self::topic::{Topic, TopicHash}; diff --git a/mm2src/gossipsub/src/mcache.rs b/mm2src/gossipsub/src/mcache.rs deleted file mode 100644 index fe92cd3c93..0000000000 --- a/mm2src/gossipsub/src/mcache.rs +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -extern crate fnv; - -use crate::protocol::{GossipsubMessage, MessageId}; -use crate::topic::TopicHash; -use std::collections::HashMap; - -/// CacheEntry stored in the history. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct CacheEntry { - mid: MessageId, - topics: Vec, -} - -/// MessageCache struct holding history of messages. -#[derive(Clone)] -pub struct MessageCache { - msgs: HashMap, - history: Vec>, - gossip: usize, - msg_id: fn(&GossipsubMessage) -> MessageId, -} - -/// Implementation of the MessageCache. -impl MessageCache { - pub fn new(gossip: usize, history_capacity: usize, msg_id: fn(&GossipsubMessage) -> MessageId) -> MessageCache { - MessageCache { - gossip, - msgs: HashMap::default(), - history: vec![Vec::new(); history_capacity], - msg_id, - } - } - - /// Creates a `MessageCache` with a default message id function. - #[allow(dead_code)] - pub fn new_default(gossip: usize, history_capacity: usize) -> MessageCache { - let default_id = |message: &GossipsubMessage| { - // default message id is: source + sequence number - let mut source_string = message.source.to_base58(); - source_string.push_str(&message.sequence_number.to_string()); - MessageId(source_string) - }; - MessageCache { - gossip, - msgs: HashMap::default(), - history: vec![Vec::new(); history_capacity], - msg_id: default_id, - } - } - - /// Put a message into the memory cache - pub fn put(&mut self, msg: GossipsubMessage) { - let message_id = (self.msg_id)(&msg); - let cache_entry = CacheEntry { - mid: message_id.clone(), - topics: msg.topics.clone(), - }; - - self.msgs.insert(message_id, msg); - - self.history[0].push(cache_entry); - } - - /// Get a message with `message_id` - pub fn get(&self, message_id: &MessageId) -> Option<&GossipsubMessage> { self.msgs.get(message_id) } - - /// Get a list of GossipIds for a given topic - pub fn get_gossip_ids(&self, topic: &TopicHash) -> Vec { - self.history[..self.gossip] - .iter() - .fold(vec![], |mut current_entries, entries| { - // search for entries with desired topic - let mut found_entries: Vec = entries - .iter() - .filter_map(|entry| { - if entry.topics.iter().any(|t| t == topic) { - Some(entry.mid.clone()) - } else { - None - } - }) - .collect(); - - // generate the list - current_entries.append(&mut found_entries); - current_entries - }) - } - - /// Shift the history array down one and delete messages associated with the - /// last entry - pub fn shift(&mut self) { - for entry in self.history.pop().expect("history is always > 1") { - self.msgs.remove(&entry.mid); - } - - // Insert an empty vec in position 0 - self.history.insert(0, Vec::new()); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{Topic, TopicHash}; - use libp2p_core::PeerId; - - fn gen_testm(x: u64, topics: Vec) -> GossipsubMessage { - let u8x: u8 = x as u8; - let source = PeerId::random(); - let data: Vec = vec![u8x]; - let sequence_number = x; - - GossipsubMessage { - source, - data, - sequence_number, - topics, - } - } - - #[test] - /// Test that the message cache can be created. - fn test_new_cache() { - let default_id = |message: &GossipsubMessage| { - // default message id is: source + sequence number - let mut source_string = message.source.to_base58(); - source_string.push_str(&message.sequence_number.to_string()); - MessageId(source_string) - }; - let x: usize = 3; - let mc = MessageCache::new(x, 5, default_id); - - assert_eq!(mc.gossip, x); - } - - #[test] - /// Test you can put one message and get one. - fn test_put_get_one() { - let mut mc = MessageCache::new_default(10, 15); - - let topic1_hash = Topic::new("topic1".into()).no_hash(); - let topic2_hash = Topic::new("topic2".into()).no_hash(); - - let m = gen_testm(10, vec![topic1_hash, topic2_hash]); - - mc.put(m.clone()); - - assert!(mc.history[0].len() == 1); - - let fetched = mc.get(&(mc.msg_id)(&m)); - - assert!(fetched.is_some()); - - // Make sure it is the same fetched message - match fetched { - Some(x) => assert_eq!(*x, m), - _ => panic!("expected {:?}", m), - } - } - - #[test] - /// Test attempting to 'get' with a wrong id. - fn test_get_wrong() { - let mut mc = MessageCache::new_default(10, 15); - - let topic1_hash = Topic::new("topic1".into()).no_hash(); - let topic2_hash = Topic::new("topic2".into()).no_hash(); - - let m = gen_testm(10, vec![topic1_hash, topic2_hash]); - - mc.put(m); - - // Try to get an incorrect ID - let wrong_id = MessageId(String::from("wrongid")); - let fetched = mc.get(&wrong_id); - assert!(fetched.is_none()); - } - - #[test] - /// Test attempting to 'get' empty message cache. - fn test_get_empty() { - let mc = MessageCache::new_default(10, 15); - - // Try to get an incorrect ID - let wrong_string = MessageId(String::from("imempty")); - let fetched = mc.get(&wrong_string); - assert!(fetched.is_none()); - } - - #[test] - /// Test adding a message with no topics. - fn test_no_topic_put() { - let mut mc = MessageCache::new_default(3, 5); - - // Build the message - let m = gen_testm(1, vec![]); - mc.put(m.clone()); - - let fetched = mc.get(&(mc.msg_id)(&m)); - - // Make sure it is the same fetched message - match fetched { - Some(x) => assert_eq!(*x, m), - _ => panic!("expected {:?}", m), - } - } - - #[test] - /// Test shift mechanism. - fn test_shift() { - let mut mc = MessageCache::new_default(1, 5); - - let topic1_hash = Topic::new("topic1".into()).no_hash(); - let topic2_hash = Topic::new("topic2".into()).no_hash(); - - // Build the message - for i in 0..10 { - let m = gen_testm(i, vec![topic1_hash.clone(), topic2_hash.clone()]); - mc.put(m.clone()); - } - - mc.shift(); - - // Ensure the shift occurred - assert!(mc.history[0].is_empty()); - assert!(mc.history[1].len() == 10); - - // Make sure no messages deleted - assert!(mc.msgs.len() == 10); - } - - #[test] - /// Test Shift with no additions. - fn test_empty_shift() { - let mut mc = MessageCache::new_default(1, 5); - - let topic1_hash = Topic::new("topic1".into()).no_hash(); - let topic2_hash = Topic::new("topic2".into()).no_hash(); - // Build the message - for i in 0..10 { - let m = gen_testm(i, vec![topic1_hash.clone(), topic2_hash.clone()]); - mc.put(m.clone()); - } - - mc.shift(); - - // Ensure the shift occurred - assert!(mc.history[0].is_empty()); - assert!(mc.history[1].len() == 10); - - mc.shift(); - - assert!(mc.history[2].len() == 10); - assert!(mc.history[1].is_empty()); - assert!(mc.history[0].is_empty()); - } - - #[test] - /// Test shift to see if the last history messages are removed. - fn test_remove_last_from_shift() { - let mut mc = MessageCache::new_default(4, 5); - - let topic1_hash = Topic::new("topic1".into()).no_hash(); - let topic2_hash = Topic::new("topic2".into()).no_hash(); - // Build the message - for i in 0..10 { - let m = gen_testm(i, vec![topic1_hash.clone(), topic2_hash.clone()]); - mc.put(m.clone()); - } - - // Shift right until deleting messages - mc.shift(); - mc.shift(); - mc.shift(); - mc.shift(); - - assert_eq!(mc.history[mc.history.len() - 1].len(), 10); - - // Shift and delete the messages - mc.shift(); - assert_eq!(mc.history[mc.history.len() - 1].len(), 0); - assert_eq!(mc.history[0].len(), 0); - assert_eq!(mc.msgs.len(), 0); - } -} diff --git a/mm2src/gossipsub/src/protocol.rs b/mm2src/gossipsub/src/protocol.rs deleted file mode 100644 index 1173a89b22..0000000000 --- a/mm2src/gossipsub/src/protocol.rs +++ /dev/null @@ -1,397 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::behaviour::GossipsubRpc; -use crate::rpc_proto; -use crate::topic::TopicHash; -use byteorder::{BigEndian, ByteOrder}; -use bytes::Bytes; -use bytes::BytesMut; -use common::some_or_return_ok_none; -use futures::future; -use futures::prelude::*; -use futures_codec::{Decoder, Encoder, Framed}; -use libp2p_core::{InboundUpgrade, OutboundUpgrade, PeerId, UpgradeInfo}; -use prost::Message as ProtobufMessage; -use std::{borrow::Cow, io, iter, pin::Pin}; -use unsigned_varint::codec; - -/// Implementation of the `ConnectionUpgrade` for the Gossipsub protocol. -#[derive(Debug, Clone)] -pub struct ProtocolConfig { - protocol_id: Cow<'static, [u8]>, - max_transmit_size: usize, -} - -impl Default for ProtocolConfig { - fn default() -> Self { - Self { - protocol_id: Cow::Borrowed(b"/meshsub/1.0.0"), - max_transmit_size: 2048, - } - } -} - -impl ProtocolConfig { - /// Builds a new `ProtocolConfig`. - /// Sets the maximum gossip transmission size. - pub fn new(protocol_id: impl Into>, max_transmit_size: usize) -> ProtocolConfig { - ProtocolConfig { - protocol_id: protocol_id.into(), - max_transmit_size, - } - } -} - -impl UpgradeInfo for ProtocolConfig { - type Info = Cow<'static, [u8]>; - type InfoIter = iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { iter::once(self.protocol_id.clone()) } -} - -type PinBoxFut = Pin> + Send>>; - -impl InboundUpgrade for ProtocolConfig -where - TSocket: AsyncRead + AsyncWrite + Unpin + Send + 'static, -{ - type Output = Framed; - type Error = io::Error; - type Future = PinBoxFut; - - fn upgrade_inbound(self, socket: TSocket, _: Self::Info) -> Self::Future { - let mut length_codec = codec::UviBytes::default(); - length_codec.set_max_len(self.max_transmit_size); - Box::pin(future::ok(Framed::new(socket, GossipsubCodec { length_codec }))) - } -} - -impl OutboundUpgrade for ProtocolConfig -where - TSocket: AsyncWrite + AsyncRead + Unpin + Send + 'static, -{ - type Output = Framed; - type Error = io::Error; - type Future = PinBoxFut; - - fn upgrade_outbound(self, socket: TSocket, _: Self::Info) -> Self::Future { - let mut length_codec = codec::UviBytes::default(); - length_codec.set_max_len(self.max_transmit_size); - Box::pin(future::ok(Framed::new(socket, GossipsubCodec { length_codec }))) - } -} - -/* Gossip codec for the framing */ - -pub struct GossipsubCodec { - /// Codec to encode/decode the Unsigned varint length prefix of the frames. - length_codec: codec::UviBytes, -} - -impl Encoder for GossipsubCodec { - type Item = GossipsubRpc; - type Error = io::Error; - - fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { - // messages - let publish = item - .messages - .into_iter() - .map(|message| rpc_proto::Message { - from: Some(message.source.to_bytes()), - data: Some(message.data), - seqno: Some(message.sequence_number.to_be_bytes().to_vec()), - topic_ids: message.topics.into_iter().map(TopicHash::into_string).collect(), - }) - .collect::>(); - - // subscriptions - let subscriptions = item - .subscriptions - .into_iter() - .map(|sub| rpc_proto::rpc::SubOpts { - subscribe: Some(sub.action == GossipsubSubscriptionAction::Subscribe), - topic_id: Some(sub.topic_hash.into_string()), - }) - .collect::>(); - - // control messages - let mut control = rpc_proto::ControlMessage { - ihave: Vec::new(), - iwant: Vec::new(), - graft: Vec::new(), - prune: Vec::new(), - iamrelay: None, - included_to_relays_mesh: None, - mesh_size: None, - }; - - let empty_control_msg = item.control_msgs.is_empty(); - - for action in item.control_msgs { - match action { - // collect all ihave messages - GossipsubControlAction::IHave { - topic_hash, - message_ids, - } => { - let rpc_ihave = rpc_proto::ControlIHave { - topic_id: Some(topic_hash.into_string()), - message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), - }; - control.ihave.push(rpc_ihave); - }, - GossipsubControlAction::IWant { message_ids } => { - let rpc_iwant = rpc_proto::ControlIWant { - message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), - }; - control.iwant.push(rpc_iwant); - }, - GossipsubControlAction::Graft { topic_hash } => { - let rpc_graft = rpc_proto::ControlGraft { - topic_id: Some(topic_hash.into_string()), - }; - control.graft.push(rpc_graft); - }, - GossipsubControlAction::Prune { topic_hash } => { - let rpc_prune = rpc_proto::ControlPrune { - topic_id: Some(topic_hash.into_string()), - }; - control.prune.push(rpc_prune); - }, - GossipsubControlAction::IAmRelay(is_relay) => { - control.iamrelay = Some(is_relay); - }, - GossipsubControlAction::IncludedToRelaysMesh { included, mesh_size } => { - control.included_to_relays_mesh = Some(rpc_proto::IncludedToRelaysMesh { - included, - mesh_size: mesh_size as u32, - }); - }, - GossipsubControlAction::MeshSize(size) => { - control.mesh_size = Some(size as u32); - }, - } - } - - let rpc = rpc_proto::Rpc { - subscriptions, - publish, - control: if empty_control_msg { None } else { Some(control) }, - }; - - let mut buf = Vec::with_capacity(rpc.encoded_len()); - - rpc.encode(&mut buf).expect("Buffer has sufficient capacity"); - - // length prefix the protobuf message, ensuring the max limit is not hit - self.length_codec.encode(Bytes::from(buf), dst) - } -} - -impl Decoder for GossipsubCodec { - type Item = GossipsubRpc; - type Error = io::Error; - - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - let packet = some_or_return_ok_none!(self.length_codec.decode(src)?); - - let rpc = rpc_proto::Rpc::decode(&packet[..])?; - - let mut messages = Vec::with_capacity(rpc.publish.len()); - for publish in rpc.publish.into_iter() { - // ensure the sequence number is a u64 - let seq_no = publish - .seqno - .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "sequence number was not provided"))?; - if seq_no.len() != 8 { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "sequence number has an incorrect size", - )); - } - messages.push(GossipsubMessage { - source: PeerId::from_bytes(&publish.from.unwrap_or_default()) - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid Peer Id"))?, - data: publish.data.unwrap_or_default(), - sequence_number: BigEndian::read_u64(&seq_no), - topics: publish.topic_ids.into_iter().map(TopicHash::from_raw).collect(), - }); - } - - let mut control_msgs = Vec::new(); - - if let Some(rpc_control) = rpc.control { - // Collect the gossipsub control messages - let ihave_msgs: Vec = rpc_control - .ihave - .into_iter() - .map(|ihave| GossipsubControlAction::IHave { - topic_hash: TopicHash::from_raw(ihave.topic_id.unwrap_or_default()), - message_ids: ihave.message_ids.into_iter().map(MessageId).collect::>(), - }) - .collect(); - - let iwant_msgs: Vec = rpc_control - .iwant - .into_iter() - .map(|iwant| GossipsubControlAction::IWant { - message_ids: iwant.message_ids.into_iter().map(MessageId).collect::>(), - }) - .collect(); - - let graft_msgs: Vec = rpc_control - .graft - .into_iter() - .map(|graft| GossipsubControlAction::Graft { - topic_hash: TopicHash::from_raw(graft.topic_id.unwrap_or_default()), - }) - .collect(); - - let prune_msgs: Vec = rpc_control - .prune - .into_iter() - .map(|prune| GossipsubControlAction::Prune { - topic_hash: TopicHash::from_raw(prune.topic_id.unwrap_or_default()), - }) - .collect(); - - control_msgs.extend(ihave_msgs); - control_msgs.extend(iwant_msgs); - control_msgs.extend(graft_msgs); - control_msgs.extend(prune_msgs); - - if let Some(is_relay) = rpc_control.iamrelay { - control_msgs.extend(iter::once(GossipsubControlAction::IAmRelay(is_relay))); - } - - if let Some(mesh_size) = rpc_control.mesh_size { - control_msgs.extend(iter::once(GossipsubControlAction::MeshSize(mesh_size as usize))); - } - - if let Some(msg) = rpc_control.included_to_relays_mesh { - control_msgs.extend(iter::once(GossipsubControlAction::IncludedToRelaysMesh { - included: msg.included, - mesh_size: msg.mesh_size as usize, - })); - } - } - - Ok(Some(GossipsubRpc { - messages, - subscriptions: rpc - .subscriptions - .into_iter() - .map(|sub| GossipsubSubscription { - action: if Some(true) == sub.subscribe { - GossipsubSubscriptionAction::Subscribe - } else { - GossipsubSubscriptionAction::Unsubscribe - }, - topic_hash: TopicHash::from_raw(sub.topic_id.unwrap_or_default()), - }) - .collect(), - control_msgs, - })) - } -} - -/// A type for gossipsub message ids. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct MessageId(pub String); - -impl std::fmt::Display for MessageId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } -} - -impl From for String { - fn from(mid: MessageId) -> Self { mid.0 } -} - -/// A message received by the gossipsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct GossipsubMessage { - /// Id of the peer that published this message. - pub source: PeerId, - - /// Content of the message. Its meaning is out of scope of this library. - pub data: Vec, - - /// A random sequence number. - pub sequence_number: u64, - - /// List of topics this message belongs to. - /// - /// Each message can belong to multiple topics at once. - pub topics: Vec, -} - -/// A subscription received by the gossipsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct GossipsubSubscription { - /// Action to perform. - pub action: GossipsubSubscriptionAction, - /// The topic from which to subscribe or unsubscribe. - pub topic_hash: TopicHash, -} - -/// Action that a subscription wants to perform. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum GossipsubSubscriptionAction { - /// The remote wants to subscribe to the given topic. - Subscribe, - /// The remote wants to unsubscribe from the given topic. - Unsubscribe, -} - -/// A Control message received by the gossipsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum GossipsubControlAction { - /// Node broadcasts known messages per topic - IHave control message. - IHave { - /// The topic of the messages. - topic_hash: TopicHash, - /// A list of known message ids (peer_id + sequence _number) as a string. - message_ids: Vec, - }, - /// The node requests specific message ids (peer_id + sequence _number) - IWant control message. - IWant { - /// A list of known message ids (peer_id + sequence _number) as a string. - message_ids: Vec, - }, - /// The node has been added to the mesh - Graft control message. - Graft { - /// The mesh topic the peer should be added to. - topic_hash: TopicHash, - }, - /// The node has been removed from the mesh - Prune control message. - Prune { - /// The mesh topic the peer should be removed from. - topic_hash: TopicHash, - }, - IAmRelay(bool), - /// Whether the node included or excluded from other node relays mesh - IncludedToRelaysMesh { - included: bool, - mesh_size: usize, - }, - MeshSize(usize), -} diff --git a/mm2src/gossipsub/src/rpc.proto b/mm2src/gossipsub/src/rpc.proto deleted file mode 100644 index 8a194011a9..0000000000 --- a/mm2src/gossipsub/src/rpc.proto +++ /dev/null @@ -1,87 +0,0 @@ -syntax = "proto2"; - -package gossipsub.pb; - -message RPC { - repeated SubOpts subscriptions = 1; - repeated Message publish = 2; - - message SubOpts { - optional bool subscribe = 1; // subscribe or unsubscribe - optional string topic_id = 2; - } - - optional ControlMessage control = 3; -} - -message Message { - optional bytes from = 1; - optional bytes data = 2; - optional bytes seqno = 3; - repeated string topic_ids = 4; -} - -message IncludedToRelaysMesh { - required bool included = 1; - required uint32 mesh_size = 2; -} - -message ControlMessage { - repeated ControlIHave ihave = 1; - repeated ControlIWant iwant = 2; - repeated ControlGraft graft = 3; - repeated ControlPrune prune = 4; - optional bool iamrelay = 5; - optional IncludedToRelaysMesh included_to_relays_mesh = 6; - optional uint32 mesh_size = 7; -} - -message ControlIHave { - optional string topic_id = 1; - repeated string message_ids = 2; -} - -message ControlIWant { - repeated string message_ids= 1; -} - -message ControlGraft { - optional string topic_id = 1; -} - -message ControlGraftRelay {} - -message ControlPrune { - optional string topic_id = 1; -} - -message ControlPruneRelay {} - -// topicID = hash(topicDescriptor); (not the topic.name) -message TopicDescriptor { - optional string name = 1; - optional AuthOpts auth = 2; - optional EncOpts enc = 3; - - message AuthOpts { - optional AuthMode mode = 1; - repeated bytes keys = 2; // root keys to trust - - enum AuthMode { - NONE = 0; // no authentication, anyone can publish - KEY = 1; // only messages signed by keys in the topic descriptor are accepted - WOT = 2; // web of trust, certificates can allow publisher set to grow - } - } - - message EncOpts { - optional EncMode mode = 1; - repeated bytes key_hashes = 2; // the hashes of the shared keys used (salted) - - enum EncMode { - NONE = 0; // no encryption, anyone can read - SHAREDKEY = 1; // messages are encrypted with shared key - WOT = 2; // web of trust, certificates can allow publisher set to grow - } - } -} diff --git a/mm2src/gossipsub/src/topic.rs b/mm2src/gossipsub/src/topic.rs deleted file mode 100644 index 970ea8947a..0000000000 --- a/mm2src/gossipsub/src/topic.rs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::rpc_proto; -use base64::encode; -use prost::Message; -use sha2::{Digest, Sha256}; -use std::fmt; - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct TopicHash { - /// The topic hash. Stored as a string to align with the protobuf API. - hash: String, -} - -impl TopicHash { - pub fn from_raw(hash: impl Into) -> TopicHash { TopicHash { hash: hash.into() } } - - pub fn into_string(self) -> String { self.hash } - - pub fn as_str(&self) -> &str { &self.hash } -} - -/// A gossipsub topic. -#[derive(Debug, Clone)] -pub struct Topic { - topic: String, -} - -impl Topic { - pub fn new(topic: String) -> Self { Topic { topic } } - - /// Creates a `TopicHash` by SHA256 hashing the topic then base64 encoding the - /// hash. - pub fn sha256_hash(&self) -> TopicHash { - let topic_descripter = rpc_proto::TopicDescriptor { - name: Some(self.topic.clone()), - auth: None, - enc: None, - }; - let mut bytes = Vec::with_capacity(topic_descripter.encoded_len()); - topic_descripter.encode(&mut bytes).expect("buffer is large enough"); - let hash = encode(Sha256::digest(&bytes).as_slice()); - - TopicHash { hash } - } - - /// Creates a `TopicHash` as a raw string. - pub fn no_hash(&self) -> TopicHash { - TopicHash { - hash: self.topic.clone(), - } - } -} - -impl fmt::Display for Topic { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.topic) } -} - -impl fmt::Display for TopicHash { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.hash) } -} diff --git a/mm2src/gossipsub/tests/smoke.rs b/mm2src/gossipsub/tests/smoke.rs deleted file mode 100644 index 44c11416fb..0000000000 --- a/mm2src/gossipsub/tests/smoke.rs +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright 2019 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use futures::prelude::*; -use log::debug; -use quickcheck::{QuickCheck, TestResult}; -use rand::{random, seq::SliceRandom, SeedableRng}; -use std::{pin::Pin, - task::{Context, Poll}, - time::Duration}; - -use atomicdex_gossipsub::{Gossipsub, GossipsubConfigBuilder, GossipsubEvent, Topic}; -use futures::StreamExt; -use libp2p_core::{identity, multiaddr::Protocol, transport::MemoryTransport, upgrade, Multiaddr, Transport}; -use libp2p_plaintext::PlainText2Config; -use libp2p_swarm::{Swarm, SwarmEvent}; -use libp2p_yamux as yamux; - -struct Graph { - pub nodes: Vec<(Multiaddr, Swarm)>, -} - -impl Future for Graph { - type Output = (Multiaddr, GossipsubEvent); - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - for (addr, node) in &mut self.nodes { - loop { - match node.poll_next_unpin(cx) { - Poll::Ready(Some(SwarmEvent::Behaviour(event))) => return Poll::Ready((addr.clone(), event)), - Poll::Ready(Some(_)) => {}, - Poll::Ready(None) => panic!("unexpected None when polling nodes"), - Poll::Pending => break, - } - } - } - - Poll::Pending - } -} - -impl Graph { - fn new_connected(num_nodes: usize, seed: u64) -> Graph { - if num_nodes == 0 { - panic!("expecting at least one node"); - } - - let mut rng = rand::rngs::StdRng::seed_from_u64(seed); - - let mut not_connected_nodes = std::iter::once(()) - .cycle() - .take(num_nodes) - .map(|_| build_node()) - .collect::)>>(); - - let mut connected_nodes = vec![not_connected_nodes.pop().unwrap()]; - - while !not_connected_nodes.is_empty() { - connected_nodes.shuffle(&mut rng); - not_connected_nodes.shuffle(&mut rng); - - let mut next = not_connected_nodes.pop().unwrap(); - let connected_addr = &connected_nodes[0].0; - - // Memory transport can not handle addresses with `/p2p` suffix. - let mut connected_addr_no_p2p = connected_addr.clone(); - let p2p_suffix_connected = connected_addr_no_p2p.pop(); - - debug!( - "Connect: {} -> {}", - next.0.clone().pop().unwrap(), - p2p_suffix_connected.unwrap() - ); - - Swarm::dial(&mut next.1, connected_addr_no_p2p).unwrap(); - - connected_nodes.push(next); - } - - Graph { nodes: connected_nodes } - } - - /// Polls the graph and passes each event into the provided FnMut until the closure returns - /// `true`. - /// - /// Returns [`true`] on success and [`false`] on timeout. - fn wait_for bool>(&mut self, mut f: F) -> bool { - let fut = futures::future::poll_fn(move |cx| match self.poll_unpin(cx) { - Poll::Ready((_addr, ev)) if f(&ev) => Poll::Ready(()), - _ => Poll::Pending, - }); - - let fut = async_std::future::timeout(Duration::from_secs(10), fut); - - futures::executor::block_on(fut).is_ok() - } - - /// Polls the graph until Poll::Pending is obtained, completing the underlying polls. - fn drain_poll(self) -> Self { - // The future below should return self. Given that it is a FnMut and not a FnOnce, one needs - // to wrap `self` in an Option, leaving a `None` behind after the final `Poll::Ready`. - let mut this = Some(self); - - let fut = futures::future::poll_fn(move |cx| match &mut this { - Some(graph) => loop { - match graph.poll_unpin(cx) { - Poll::Ready(_) => {}, - Poll::Pending => return Poll::Ready(this.take().unwrap()), - } - }, - None => panic!("future called after final return"), - }); - let fut = async_std::future::timeout(Duration::from_secs(10), fut); - futures::executor::block_on(fut).unwrap() - } -} - -fn build_node() -> (Multiaddr, Swarm) { - let key = identity::Keypair::generate_ed25519(); - let public_key = key.public(); - - let transport = MemoryTransport::default() - .upgrade(upgrade::Version::V1) - .authenticate(PlainText2Config { - local_public_key: public_key.clone(), - }) - .multiplex(yamux::YamuxConfig::default()) - .boxed(); - - let peer_id = public_key.to_peer_id(); - - // NOTE: The graph of created nodes can be disconnected from the mesh point of view as nodes - // can reach their d_lo value and not add other nodes to their mesh. To speed up this test, we - // reduce the default values of the heartbeat, so that all nodes will receive gossip in a - // timely fashion. - - let config = GossipsubConfigBuilder::default() - .heartbeat_initial_delay(Duration::from_millis(100)) - .heartbeat_interval(Duration::from_millis(200)) - .history_length(10) - .history_gossip(10) - .build(); - let behaviour = Gossipsub::new(peer_id, config); - let mut swarm = Swarm::new(transport, behaviour, peer_id); - - let port = 1 + random::(); - let mut addr: Multiaddr = Protocol::Memory(port).into(); - swarm.listen_on(addr.clone()).unwrap(); - - addr = addr.with(libp2p_core::multiaddr::Protocol::P2p(public_key.to_peer_id().into())); - - (addr, swarm) -} - -#[test] -fn multi_hop_propagation() { - let _ = env_logger::try_init(); - - fn prop(num_nodes: u8, seed: u64) -> TestResult { - if !(2..=50).contains(&num_nodes) { - return TestResult::discard(); - } - - debug!("number nodes: {:?}, seed: {:?}", num_nodes, seed); - - let mut graph = Graph::new_connected(num_nodes as usize, seed); - let number_nodes = graph.nodes.len(); - - // Subscribe each node to the same topic. - let topic = Topic::new("test-net".into()); - for (_addr, node) in &mut graph.nodes { - node.behaviour_mut().subscribe(topic.clone()); - } - - // Wait for all nodes to be subscribed. - let mut subscribed = 0; - let all_subscribed = graph.wait_for(move |ev| { - if let GossipsubEvent::Subscribed { .. } = ev { - subscribed += 1; - if subscribed == (number_nodes - 1) * 2 { - return true; - } - } - - false - }); - if !all_subscribed { - return TestResult::error(format!( - "Timed out waiting for all nodes to subscribe but only have {:?}/{:?}.", - subscribed, num_nodes, - )); - } - - // It can happen that the publish occurs before all grafts have completed causing this test - // to fail. We drain all the poll messages before publishing. - graph = graph.drain_poll(); - - // Publish a single message. - graph.nodes[0].1.behaviour_mut().publish(&topic, vec![1, 2, 3]); - - // Wait for all nodes to receive the published message. - let mut received_msgs = 0; - let all_received = graph.wait_for(move |ev| { - if let GossipsubEvent::Message { .. } = ev { - received_msgs += 1; - if received_msgs == number_nodes - 1 { - return true; - } - } - - false - }); - if !all_received { - return TestResult::error(format!( - "Timed out waiting for all nodes to receive the msg but only have {:?}/{:?}.", - received_msgs, num_nodes, - )); - } - - TestResult::passed() - } - - QuickCheck::new() - .max_tests(5) - .quickcheck(prop as fn(u8, u64) -> TestResult) -} diff --git a/mm2src/mm2_libp2p/Cargo.toml b/mm2src/mm2_libp2p/Cargo.toml deleted file mode 100644 index bd01045799..0000000000 --- a/mm2src/mm2_libp2p/Cargo.toml +++ /dev/null @@ -1,43 +0,0 @@ -[package] -name = "mm2-libp2p" -version = "0.1.0" -authors = ["Artem Pikulin "] -edition = "2018" - -[lib] -doctest = false - -[dependencies] -async-trait = "0.1" -atomicdex-gossipsub = { path = "../gossipsub" } -common = { path = "../common" } -derive_more = "0.99" -libp2p-floodsub = { path = "../floodsub" } -futures = { version = "0.3.1", package = "futures", features = ["compat", "async-await"] } -hex = "0.4.2" -lazy_static = "1.4" -secp256k1 = { version = "0.20", features = ["rand"] } -log = "0.4.17" -rand = { package = "rand", version = "0.7", features = ["std", "wasm-bindgen"] } -regex = "1" -rmp-serde = "0.14.3" -serde = { version = "1.0", features = ["derive"] } -serde_bytes = "0.11.5" -sha2 = "0.10" -void = "1.0" -wasm-timer = "0.2.4" - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -futures-rustls = { version = "0.24" } -tokio = { version = "1.20", features = ["rt-multi-thread", "macros"] } -libp2p = { git = "https://github.com/libp2p/rust-libp2p.git", tag = "v0.45.1", default-features = false, features = ["dns-tokio", "floodsub", "mplex", "noise", "ping", "request-response", "secp256k1", "tcp-tokio", "websocket"] } - -[target.'cfg(target_arch = "wasm32")'.dependencies] -futures-rustls = { version = "0.22" } -libp2p = { git = "https://github.com/libp2p/rust-libp2p.git", tag = "v0.45.1", default-features = false, features = ["floodsub", "mplex", "noise", "ping", "request-response", "secp256k1", "wasm-ext", "wasm-ext-websocket"] } -wasm-bindgen-futures = "0.4.21" - -[dev-dependencies] -async-std = { version = "1.6.2", features = ["unstable"] } -env_logger = "0.9.3" -serde_json = { version = "1", features = ["preserve_order", "raw_value"] } diff --git a/mm2src/mm2_libp2p/src/adex_ping.rs b/mm2src/mm2_libp2p/src/adex_ping.rs deleted file mode 100644 index 73ffb94d83..0000000000 --- a/mm2src/mm2_libp2p/src/adex_ping.rs +++ /dev/null @@ -1,56 +0,0 @@ -use libp2p::swarm::NetworkBehaviour; -use libp2p::{ping::{Ping, PingConfig, PingEvent}, - swarm::{CloseConnection, NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters}, - NetworkBehaviour}; -use log::error; -use std::{collections::VecDeque, - num::NonZeroU32, - task::{Context, Poll}}; -use void::Void; - -/// Wrapper around libp2p Ping behaviour that forcefully disconnects a peer using NetworkBehaviourAction::DisconnectPeer -/// event. -/// Libp2p has unclear ConnectionHandlers keep alive logic so in some cases even if Ping handler emits Close event the -/// connection is kept active which is undesirable. -#[derive(NetworkBehaviour)] -#[behaviour(out_event = "Void", event_process = true)] -#[behaviour(poll_method = "poll_event")] -pub struct AdexPing { - ping: Ping, - #[behaviour(ignore)] - events: VecDeque::ConnectionHandler>>, -} - -impl NetworkBehaviourEventProcess for AdexPing { - fn inject_event(&mut self, event: PingEvent) { - if let Err(e) = event.result { - error!("Ping error {}. Disconnecting peer {}", e, event.peer); - self.events.push_back(NetworkBehaviourAction::CloseConnection { - peer_id: event.peer, - connection: CloseConnection::All, - }); - } - } -} - -#[allow(clippy::new_without_default)] -impl AdexPing { - pub fn new() -> Self { - AdexPing { - ping: Ping::new(PingConfig::new().with_max_failures(unsafe { NonZeroU32::new_unchecked(2) })), - events: VecDeque::new(), - } - } - - fn poll_event( - &mut self, - _cx: &mut Context, - _params: &mut impl PollParameters, - ) -> Poll::ConnectionHandler>> { - if let Some(event) = self.events.pop_front() { - return Poll::Ready(event); - } - - Poll::Pending - } -} diff --git a/mm2src/mm2_libp2p/src/atomicdex_behaviour.rs b/mm2src/mm2_libp2p/src/atomicdex_behaviour.rs deleted file mode 100644 index c0f2c47725..0000000000 --- a/mm2src/mm2_libp2p/src/atomicdex_behaviour.rs +++ /dev/null @@ -1,984 +0,0 @@ -use crate::{adex_ping::AdexPing, - network::{get_all_network_seednodes, NETID_8762}, - peers_exchange::{PeerAddresses, PeersExchange}, - request_response::{build_request_response_behaviour, PeerRequest, PeerResponse, RequestResponseBehaviour, - RequestResponseBehaviourEvent, RequestResponseSender}, - runtime::SwarmRuntime, - NetworkInfo, NetworkPorts, RelayAddress, RelayAddressError}; -use atomicdex_gossipsub::{Gossipsub, GossipsubConfigBuilder, GossipsubEvent, GossipsubMessage, MessageId, Topic, - TopicHash}; -use common::executor::SpawnFuture; -use derive_more::Display; -use futures::{channel::{mpsc::{channel, Receiver, Sender}, - oneshot}, - future::{join_all, poll_fn}, - Future, FutureExt, SinkExt, StreamExt}; -use futures_rustls::rustls; -use libp2p::core::transport::Boxed as BoxedTransport; -use libp2p::{core::{ConnectedPoint, Multiaddr, Transport}, - identity, - multiaddr::Protocol, - noise, - request_response::ResponseChannel, - swarm::{NetworkBehaviourEventProcess, Swarm}, - NetworkBehaviour, PeerId}; -use libp2p_floodsub::{Floodsub, FloodsubEvent, Topic as FloodsubTopic}; -use log::{debug, error, info}; -use rand::seq::SliceRandom; -use rand::Rng; -use std::{collections::{hash_map::DefaultHasher, BTreeMap}, - hash::{Hash, Hasher}, - iter, - net::IpAddr, - task::{Context, Poll}, - time::Duration}; -use void::Void; -use wasm_timer::{Instant, Interval}; - -pub type AdexCmdTx = Sender; -pub type AdexEventRx = Receiver; - -#[cfg(test)] mod tests; - -pub const PEERS_TOPIC: &str = "PEERS"; -const CONNECTED_RELAYS_CHECK_INTERVAL: Duration = Duration::from_secs(30); -const ANNOUNCE_INTERVAL: Duration = Duration::from_secs(600); -const ANNOUNCE_INITIAL_DELAY: Duration = Duration::from_secs(60); -const CHANNEL_BUF_SIZE: usize = 1024 * 8; - -/// Returns info about directly connected peers. -pub async fn get_directly_connected_peers(mut cmd_tx: AdexCmdTx) -> BTreeMap> { - let (result_tx, rx) = oneshot::channel(); - let cmd = AdexBehaviourCmd::GetPeersInfo { result_tx }; - cmd_tx.send(cmd).await.expect("Rx should be present"); - rx.await.expect("Tx should be present") -} - -/// Returns current gossipsub mesh state -pub async fn get_gossip_mesh(mut cmd_tx: AdexCmdTx) -> BTreeMap> { - let (result_tx, rx) = oneshot::channel(); - let cmd = AdexBehaviourCmd::GetGossipMesh { result_tx }; - cmd_tx.send(cmd).await.expect("Rx should be present"); - rx.await.expect("Tx should be present") -} - -pub async fn get_gossip_peer_topics(mut cmd_tx: AdexCmdTx) -> BTreeMap> { - let (result_tx, rx) = oneshot::channel(); - let cmd = AdexBehaviourCmd::GetGossipPeerTopics { result_tx }; - cmd_tx.send(cmd).await.expect("Rx should be present"); - rx.await.expect("Tx should be present") -} - -pub async fn get_gossip_topic_peers(mut cmd_tx: AdexCmdTx) -> BTreeMap> { - let (result_tx, rx) = oneshot::channel(); - let cmd = AdexBehaviourCmd::GetGossipTopicPeers { result_tx }; - cmd_tx.send(cmd).await.expect("Rx should be present"); - rx.await.expect("Tx should be present") -} - -pub async fn get_relay_mesh(mut cmd_tx: AdexCmdTx) -> Vec { - let (result_tx, rx) = oneshot::channel(); - let cmd = AdexBehaviourCmd::GetRelayMesh { result_tx }; - cmd_tx.send(cmd).await.expect("Rx should be present"); - rx.await.expect("Tx should be present") -} - -#[derive(Debug)] -pub struct AdexResponseChannel(ResponseChannel); - -impl From> for AdexResponseChannel { - fn from(res: ResponseChannel) -> Self { AdexResponseChannel(res) } -} - -impl From for ResponseChannel { - fn from(res: AdexResponseChannel) -> Self { res.0 } -} - -#[derive(Debug)] -pub enum AdexBehaviourCmd { - Subscribe { - /// Subscribe to this topic - topic: String, - }, - PublishMsg { - topics: Vec, - msg: Vec, - }, - PublishMsgFrom { - topics: Vec, - msg: Vec, - from: PeerId, - }, - /// Request relays sequential until a response is received. - RequestAnyRelay { - req: Vec, - response_tx: oneshot::Sender)>>, - }, - /// Request given peers and collect all their responses. - RequestPeers { - req: Vec, - peers: Vec, - response_tx: oneshot::Sender>, - }, - /// Request relays and collect all their responses. - RequestRelays { - req: Vec, - response_tx: oneshot::Sender>, - }, - /// Send a response using a `response_channel`. - SendResponse { - /// Response to a request. - res: AdexResponse, - /// Pass the same `response_channel` as that was obtained from [`AdexBehaviourEvent::PeerRequest`]. - response_channel: AdexResponseChannel, - }, - GetPeersInfo { - result_tx: oneshot::Sender>>, - }, - GetGossipMesh { - result_tx: oneshot::Sender>>, - }, - GetGossipPeerTopics { - result_tx: oneshot::Sender>>, - }, - GetGossipTopicPeers { - result_tx: oneshot::Sender>>, - }, - GetRelayMesh { - result_tx: oneshot::Sender>, - }, - /// Add a reserved peer to the peer exchange. - AddReservedPeer { - peer: PeerId, - addresses: PeerAddresses, - }, - PropagateMessage { - message_id: MessageId, - propagation_source: PeerId, - }, -} - -/// The structure is the same as `PeerResponse`, -/// but is used to prevent `PeerResponse` from being used outside the network implementation. -#[derive(Debug, Eq, PartialEq)] -pub enum AdexResponse { - Ok { response: Vec }, - None, - Err { error: String }, -} - -impl From for AdexResponse { - fn from(res: PeerResponse) -> Self { - match res { - PeerResponse::Ok { res } => AdexResponse::Ok { response: res }, - PeerResponse::None => AdexResponse::None, - PeerResponse::Err { err } => AdexResponse::Err { error: err }, - } - } -} - -impl From for PeerResponse { - fn from(res: AdexResponse) -> Self { - match res { - AdexResponse::Ok { response } => PeerResponse::Ok { res: response }, - AdexResponse::None => PeerResponse::None, - AdexResponse::Err { error } => PeerResponse::Err { err: error }, - } - } -} - -/// The structure consists of GossipsubEvent and RequestResponse events. -/// It is used to prevent the network events from being used outside the network implementation. -#[derive(Debug)] -pub enum AdexBehaviourEvent { - /// A message has been received. - /// Derived from GossipsubEvent. - Message(PeerId, MessageId, GossipsubMessage), - /// A remote subscribed to a topic. - Subscribed { - /// Remote that has subscribed. - peer_id: PeerId, - /// The topic it has subscribed to. - topic: TopicHash, - }, - /// A remote unsubscribed from a topic. - Unsubscribed { - /// Remote that has unsubscribed. - peer_id: PeerId, - /// The topic it has subscribed from. - topic: TopicHash, - }, - /// A remote peer sent a request and waits for a response. - PeerRequest { - /// Remote that sent this request. - peer_id: PeerId, - /// The serialized data. - request: Vec, - /// A channel for sending a response to this request. - /// The channel is used to identify the peer on the network that is waiting for an answer to this request. - /// See [`AdexBehaviourCmd::SendResponse`]. - response_channel: AdexResponseChannel, - }, -} - -impl From for AdexBehaviourEvent { - fn from(event: GossipsubEvent) -> Self { - match event { - GossipsubEvent::Message(peer_id, message_id, gossipsub_message) => { - AdexBehaviourEvent::Message(peer_id, message_id, gossipsub_message) - }, - GossipsubEvent::Subscribed { peer_id, topic } => AdexBehaviourEvent::Subscribed { peer_id, topic }, - GossipsubEvent::Unsubscribed { peer_id, topic } => AdexBehaviourEvent::Unsubscribed { peer_id, topic }, - } - } -} - -/// AtomicDEX libp2p Network behaviour implementation -#[derive(NetworkBehaviour)] -#[behaviour(event_process = true)] -pub struct AtomicDexBehaviour { - floodsub: Floodsub, - #[behaviour(ignore)] - event_tx: Sender, - #[behaviour(ignore)] - runtime: SwarmRuntime, - #[behaviour(ignore)] - cmd_rx: Receiver, - #[behaviour(ignore)] - netid: u16, - gossipsub: Gossipsub, - request_response: RequestResponseBehaviour, - peers_exchange: PeersExchange, - ping: AdexPing, -} - -impl AtomicDexBehaviour { - fn notify_on_adex_event(&mut self, event: AdexBehaviourEvent) { - if let Err(e) = self.event_tx.try_send(event) { - error!("notify_on_adex_event error {}", e); - } - } - - fn spawn(&self, fut: impl Future + Send + 'static) { self.runtime.spawn(fut) } - - fn process_cmd(&mut self, cmd: AdexBehaviourCmd) { - match cmd { - AdexBehaviourCmd::Subscribe { topic } => { - let topic = Topic::new(topic); - self.gossipsub.subscribe(topic); - }, - AdexBehaviourCmd::PublishMsg { topics, msg } => { - self.gossipsub.publish_many(topics.into_iter().map(Topic::new), msg); - }, - AdexBehaviourCmd::PublishMsgFrom { topics, msg, from } => { - self.gossipsub - .publish_many_from(topics.into_iter().map(Topic::new), msg, from); - }, - AdexBehaviourCmd::RequestAnyRelay { req, response_tx } => { - let relays = self.gossipsub.get_relay_mesh(); - // spawn the `request_any_peer` future - let future = request_any_peer(relays, req, self.request_response.sender(), response_tx); - self.spawn(future); - }, - AdexBehaviourCmd::RequestPeers { - req, - peers, - response_tx, - } => { - let peers = peers - .into_iter() - .filter_map(|peer| match peer.parse() { - Ok(p) => Some(p), - Err(e) => { - error!("Error on parse peer id {:?}: {:?}", peer, e); - None - }, - }) - .collect(); - let future = request_peers(peers, req, self.request_response.sender(), response_tx); - self.spawn(future); - }, - AdexBehaviourCmd::RequestRelays { req, response_tx } => { - let relays = self.gossipsub.get_relay_mesh(); - // spawn the `request_peers` future - let future = request_peers(relays, req, self.request_response.sender(), response_tx); - self.spawn(future); - }, - AdexBehaviourCmd::SendResponse { res, response_channel } => { - if let Err(response) = self.request_response.send_response(response_channel.into(), res.into()) { - error!("Error sending response: {:?}", response); - } - }, - AdexBehaviourCmd::GetPeersInfo { result_tx } => { - let result = self - .gossipsub - .get_peers_connections() - .into_iter() - .map(|(peer_id, connected_points)| { - let peer_id = peer_id.to_base58(); - let connected_points = connected_points - .into_iter() - .map(|(_conn_id, point)| match point { - ConnectedPoint::Dialer { address, .. } => address.to_string(), - ConnectedPoint::Listener { send_back_addr, .. } => send_back_addr.to_string(), - }) - .collect(); - (peer_id, connected_points) - }) - .collect(); - if result_tx.send(result).is_err() { - debug!("Result rx is dropped"); - } - }, - AdexBehaviourCmd::GetGossipMesh { result_tx } => { - let result = self - .gossipsub - .get_mesh() - .iter() - .map(|(topic, peers)| { - let topic = topic.to_string(); - let peers = peers.iter().map(|peer| peer.to_string()).collect(); - (topic, peers) - }) - .collect(); - if result_tx.send(result).is_err() { - debug!("Result rx is dropped"); - } - }, - AdexBehaviourCmd::GetGossipPeerTopics { result_tx } => { - let result = self - .gossipsub - .get_all_peer_topics() - .iter() - .map(|(peer, topics)| { - let peer = peer.to_string(); - let topics = topics.iter().map(|topic| topic.to_string()).collect(); - (peer, topics) - }) - .collect(); - if result_tx.send(result).is_err() { - error!("Result rx is dropped"); - } - }, - AdexBehaviourCmd::GetGossipTopicPeers { result_tx } => { - let result = self - .gossipsub - .get_all_topic_peers() - .iter() - .map(|(topic, peers)| { - let topic = topic.to_string(); - let peers = peers.iter().map(|peer| peer.to_string()).collect(); - (topic, peers) - }) - .collect(); - if result_tx.send(result).is_err() { - error!("Result rx is dropped"); - } - }, - AdexBehaviourCmd::GetRelayMesh { result_tx } => { - let result = self - .gossipsub - .get_relay_mesh() - .into_iter() - .map(|peer| peer.to_string()) - .collect(); - if result_tx.send(result).is_err() { - error!("Result rx is dropped"); - } - }, - AdexBehaviourCmd::AddReservedPeer { peer, addresses } => { - self.peers_exchange - .add_peer_addresses_to_reserved_peers(&peer, addresses); - }, - AdexBehaviourCmd::PropagateMessage { - message_id, - propagation_source, - } => { - self.gossipsub.propagate_message(&message_id, &propagation_source); - }, - } - } - - fn announce_listeners(&mut self, listeners: PeerAddresses) { - let serialized = rmp_serde::to_vec(&listeners).expect("PeerAddresses serialization should never fail"); - self.floodsub.publish(FloodsubTopic::new(PEERS_TOPIC), serialized); - } - - pub fn connected_relays_len(&self) -> usize { self.gossipsub.connected_relays_len() } - - pub fn relay_mesh_len(&self) -> usize { self.gossipsub.relay_mesh_len() } - - pub fn received_messages_in_period(&self) -> (Duration, usize) { self.gossipsub.get_received_messages_in_period() } - - pub fn connected_peers_len(&self) -> usize { self.gossipsub.get_num_peers() } -} - -impl NetworkBehaviourEventProcess for AtomicDexBehaviour { - fn inject_event(&mut self, event: GossipsubEvent) { self.notify_on_adex_event(event.into()); } -} - -impl NetworkBehaviourEventProcess for AtomicDexBehaviour { - fn inject_event(&mut self, event: FloodsubEvent) { - // do not process peer announce on 8762 temporary - if self.netid != NETID_8762 { - if let FloodsubEvent::Message(message) = &event { - for topic in &message.topics { - if topic == &FloodsubTopic::new(PEERS_TOPIC) { - let addresses: PeerAddresses = match rmp_serde::from_slice(&message.data) { - Ok(a) => a, - Err(_) => return, - }; - self.peers_exchange - .add_peer_addresses_to_known_peers(&message.source, addresses); - } - } - } - } - } -} - -impl NetworkBehaviourEventProcess for AtomicDexBehaviour { - fn inject_event(&mut self, _event: Void) {} -} - -impl NetworkBehaviourEventProcess<()> for AtomicDexBehaviour { - fn inject_event(&mut self, _event: ()) {} -} - -impl NetworkBehaviourEventProcess for AtomicDexBehaviour { - fn inject_event(&mut self, event: RequestResponseBehaviourEvent) { - match event { - RequestResponseBehaviourEvent::InboundRequest { - peer_id, - request, - response_channel, - } => { - let event = AdexBehaviourEvent::PeerRequest { - peer_id, - request: request.req, - response_channel: response_channel.into(), - }; - // forward the event to the AdexBehaviourCmd handler - self.notify_on_adex_event(event); - }, - } - } -} - -/// Custom types mapping the complex associated types of AtomicDexBehaviour to the ExpandedSwarm -type AtomicDexSwarm = Swarm; - -fn maintain_connection_to_relays(swarm: &mut AtomicDexSwarm, bootstrap_addresses: &[Multiaddr]) { - let behaviour = swarm.behaviour(); - let connected_relays = behaviour.gossipsub.connected_relays(); - let mesh_n_low = behaviour.gossipsub.get_config().mesh_n_low; - let mesh_n = behaviour.gossipsub.get_config().mesh_n; - // allow 2 * mesh_n_high connections to other nodes - let max_n = behaviour.gossipsub.get_config().mesh_n_high * 2; - - let mut rng = rand::thread_rng(); - if connected_relays.len() < mesh_n_low { - let to_connect_num = mesh_n - connected_relays.len(); - let to_connect = swarm - .behaviour_mut() - .peers_exchange - .get_random_peers(to_connect_num, |peer| !connected_relays.contains(peer)); - - // choose some random bootstrap addresses to connect if peers exchange returned not enough peers - if to_connect.len() < to_connect_num { - let connect_bootstrap_num = to_connect_num - to_connect.len(); - for addr in bootstrap_addresses - .iter() - .filter(|addr| !swarm.behaviour().gossipsub.is_connected_to_addr(addr)) - .collect::>() - .choose_multiple(&mut rng, connect_bootstrap_num) - { - if let Err(e) = libp2p::Swarm::dial(swarm, (*addr).clone()) { - error!("Bootstrap addr {} dial error {}", addr, e); - } - } - } - for (peer, addresses) in to_connect { - for addr in addresses { - if swarm.behaviour().gossipsub.is_connected_to_addr(&addr) { - continue; - } - if let Err(e) = libp2p::Swarm::dial(swarm, addr.clone()) { - error!("Peer {} address {} dial error {}", peer, addr, e); - } - } - } - } - - if connected_relays.len() > max_n { - let to_disconnect_num = connected_relays.len() - max_n; - let relays_mesh = swarm.behaviour().gossipsub.get_relay_mesh(); - let not_in_mesh: Vec<_> = connected_relays - .iter() - .filter(|peer| !relays_mesh.contains(peer)) - .collect(); - for peer in not_in_mesh.choose_multiple(&mut rng, to_disconnect_num) { - if !swarm.behaviour().peers_exchange.is_reserved_peer(peer) { - info!("Disconnecting peer {}", peer); - if Swarm::disconnect_peer_id(swarm, **peer).is_err() { - error!("Peer {} disconnect error", peer); - } - } - } - } - - for relay in connected_relays { - if !swarm.behaviour().peers_exchange.is_known_peer(&relay) { - swarm.behaviour_mut().peers_exchange.add_known_peer(relay); - } - } -} - -fn announce_my_addresses(swarm: &mut AtomicDexSwarm) { - let global_listeners: PeerAddresses = Swarm::listeners(swarm) - .filter(|listener| { - for protocol in listener.iter() { - if let Protocol::Ip4(ip) = protocol { - return ip.is_global(); - } - } - false - }) - .take(1) - .cloned() - .collect(); - if !global_listeners.is_empty() { - swarm.behaviour_mut().announce_listeners(global_listeners); - } -} - -#[derive(Debug, Display)] -pub enum AdexBehaviourError { - #[display(fmt = "{}", _0)] - ParsingRelayAddress(RelayAddressError), -} - -impl From for AdexBehaviourError { - fn from(e: RelayAddressError) -> Self { AdexBehaviourError::ParsingRelayAddress(e) } -} - -pub struct WssCerts { - pub server_priv_key: rustls::PrivateKey, - pub certs: Vec, -} - -pub enum NodeType { - Light { - network_ports: NetworkPorts, - }, - LightInMemory, - Relay { - ip: IpAddr, - network_ports: NetworkPorts, - wss_certs: Option, - }, - RelayInMemory { - port: u64, - }, -} - -impl NodeType { - pub fn to_network_info(&self) -> NetworkInfo { - match self { - NodeType::Light { network_ports } | NodeType::Relay { network_ports, .. } => NetworkInfo::Distributed { - network_ports: *network_ports, - }, - NodeType::LightInMemory | NodeType::RelayInMemory { .. } => NetworkInfo::InMemory, - } - } - - pub fn is_relay(&self) -> bool { matches!(self, NodeType::Relay { .. } | NodeType::RelayInMemory { .. }) } - - pub fn wss_certs(&self) -> Option<&WssCerts> { - match self { - NodeType::Relay { wss_certs, .. } => wss_certs.as_ref(), - _ => None, - } - } -} - -/// Creates and spawns new AdexBehaviour Swarm returning: -/// 1. tx to send control commands -/// 2. rx emitting gossip events to processing side -/// 3. our peer_id -/// 4. abort handle to stop the P2P processing fut. -pub async fn spawn_gossipsub( - netid: u16, - force_key: Option<[u8; 32]>, - runtime: SwarmRuntime, - to_dial: Vec, - node_type: NodeType, - on_poll: impl Fn(&AtomicDexSwarm) + Send + 'static, -) -> Result<(Sender, AdexEventRx, PeerId), AdexBehaviourError> { - let (result_tx, result_rx) = oneshot::channel(); - - let runtime_c = runtime.clone(); - let fut = async move { - let result = start_gossipsub(netid, force_key, runtime, to_dial, node_type, on_poll); - result_tx.send(result).unwrap(); - }; - - // `Libp2p` must be spawned on the tokio runtime - runtime_c.spawn(fut); - result_rx.await.expect("Fatal error on starting gossipsub") -} - -/// Creates and spawns new AdexBehaviour Swarm returning: -/// 1. tx to send control commands -/// 2. rx emitting gossip events to processing side -/// 3. our peer_id -/// 4. abort handle to stop the P2P processing fut -/// -/// Prefer using [`spawn_gossipsub`] to make sure the Swarm is initialized and spawned on the same runtime. -/// Otherwise, you can face the following error: -/// `panicked at 'there is no reactor running, must be called from the context of a Tokio 1.x runtime'`. -#[allow(clippy::too_many_arguments)] -fn start_gossipsub( - netid: u16, - force_key: Option<[u8; 32]>, - runtime: SwarmRuntime, - to_dial: Vec, - node_type: NodeType, - on_poll: impl Fn(&AtomicDexSwarm) + Send + 'static, -) -> Result<(Sender, AdexEventRx, PeerId), AdexBehaviourError> { - let i_am_relay = node_type.is_relay(); - let mut rng = rand::thread_rng(); - let local_key = generate_ed25519_keypair(&mut rng, force_key); - let local_peer_id = PeerId::from(local_key.public()); - info!("Local peer id: {:?}", local_peer_id); - - let noise_keys = noise::Keypair::::new() - .into_authentic(&local_key) - .expect("Signing libp2p-noise static DH keypair failed."); - - let network_info = node_type.to_network_info(); - let transport = match network_info { - NetworkInfo::InMemory => build_memory_transport(noise_keys), - NetworkInfo::Distributed { .. } => build_dns_ws_transport(noise_keys, node_type.wss_certs()), - }; - - let (cmd_tx, cmd_rx) = channel(CHANNEL_BUF_SIZE); - let (event_tx, event_rx) = channel(CHANNEL_BUF_SIZE); - - let bootstrap = to_dial - .into_iter() - .map(|addr| addr.try_to_multiaddr(network_info)) - .collect::, _>>()?; - - let (mesh_n_low, mesh_n, mesh_n_high) = if i_am_relay { (4, 6, 12) } else { (2, 3, 4) }; - - // Create a Swarm to manage peers and events - let mut swarm = { - // to set default parameters for gossipsub use: - // let gossipsub_config = gossipsub::GossipsubConfig::default(); - - // To content-address message, we can take the hash of message and use it as an ID. - let message_id_fn = |message: &GossipsubMessage| { - let mut s = DefaultHasher::new(); - message.data.hash(&mut s); - message.sequence_number.hash(&mut s); - MessageId(s.finish().to_string()) - }; - - // set custom gossipsub - let gossipsub_config = GossipsubConfigBuilder::new() - .message_id_fn(message_id_fn) - .i_am_relay(i_am_relay) - .mesh_n_low(mesh_n_low) - .mesh_n(mesh_n) - .mesh_n_high(mesh_n_high) - .manual_propagation() - .max_transmit_size(1024 * 1024 - 100) - .build(); - // build a gossipsub network behaviour - let mut gossipsub = Gossipsub::new(local_peer_id, gossipsub_config); - - let floodsub = Floodsub::new(local_peer_id, netid != NETID_8762); - - let mut peers_exchange = PeersExchange::new(network_info); - if !network_info.in_memory() { - // Please note WASM nodes don't support `PeersExchange` currently, - // so `get_all_network_seednodes` returns an empty list. - for (peer_id, addr) in get_all_network_seednodes(netid) { - let multiaddr = addr.try_to_multiaddr(network_info)?; - peers_exchange.add_peer_addresses_to_known_peers(&peer_id, iter::once(multiaddr).collect()); - gossipsub.add_explicit_relay(peer_id); - } - } - - // build a request-response network behaviour - let request_response = build_request_response_behaviour(); - - // use default ping config with 15s interval, 20s timeout and 1 max failure - let ping = AdexPing::new(); - - let adex_behavior = AtomicDexBehaviour { - floodsub, - event_tx, - runtime: runtime.clone(), - cmd_rx, - netid, - gossipsub, - request_response, - peers_exchange, - ping, - }; - libp2p::swarm::SwarmBuilder::new(transport, adex_behavior, local_peer_id) - .executor(Box::new(runtime.clone())) - .build() - }; - swarm - .behaviour_mut() - .floodsub - .subscribe(FloodsubTopic::new(PEERS_TOPIC.to_owned())); - - match node_type { - NodeType::Relay { - ip, - network_ports, - wss_certs, - } => { - let dns_addr: Multiaddr = format!("/ip4/{}/tcp/{}", ip, network_ports.tcp).parse().unwrap(); - libp2p::Swarm::listen_on(&mut swarm, dns_addr).unwrap(); - if wss_certs.is_some() { - let wss_addr: Multiaddr = format!("/ip4/{}/tcp/{}/wss", ip, network_ports.wss).parse().unwrap(); - libp2p::Swarm::listen_on(&mut swarm, wss_addr).unwrap(); - } - }, - NodeType::RelayInMemory { port } => { - let memory_addr: Multiaddr = format!("/memory/{}", port).parse().unwrap(); - libp2p::Swarm::listen_on(&mut swarm, memory_addr).unwrap(); - }, - _ => (), - } - - for relay in bootstrap.choose_multiple(&mut rng, mesh_n) { - match libp2p::Swarm::dial(&mut swarm, relay.clone()) { - Ok(_) => info!("Dialed {}", relay), - Err(e) => error!("Dial {:?} failed: {:?}", relay, e), - } - } - - let mut check_connected_relays_interval = Interval::new_at( - Instant::now() + CONNECTED_RELAYS_CHECK_INTERVAL, - CONNECTED_RELAYS_CHECK_INTERVAL, - ); - let mut announce_interval = Interval::new_at(Instant::now() + ANNOUNCE_INITIAL_DELAY, ANNOUNCE_INTERVAL); - let mut listening = false; - let polling_fut = poll_fn(move |cx: &mut Context| { - loop { - match swarm.behaviour_mut().cmd_rx.poll_next_unpin(cx) { - Poll::Ready(Some(cmd)) => swarm.behaviour_mut().process_cmd(cmd), - Poll::Ready(None) => return Poll::Ready(()), - Poll::Pending => break, - } - } - - loop { - match swarm.poll_next_unpin(cx) { - Poll::Ready(Some(event)) => debug!("Swarm event {:?}", event), - Poll::Ready(None) => return Poll::Ready(()), - Poll::Pending => break, - } - } - - if swarm.behaviour().gossipsub.is_relay() { - while let Poll::Ready(Some(())) = announce_interval.poll_next_unpin(cx) { - announce_my_addresses(&mut swarm); - } - } - - while let Poll::Ready(Some(())) = check_connected_relays_interval.poll_next_unpin(cx) { - maintain_connection_to_relays(&mut swarm, &bootstrap); - } - - if !listening && i_am_relay { - for listener in Swarm::listeners(&swarm) { - info!("Listening on {}", listener); - listening = true; - } - } - on_poll(&swarm); - Poll::Pending - }); - - runtime.spawn(polling_fut.then(|_| futures::future::ready(()))); - Ok((cmd_tx, event_rx, local_peer_id)) -} - -#[cfg(target_arch = "wasm32")] -fn build_dns_ws_transport( - noise_keys: libp2p::noise::AuthenticKeypair, - _wss_certs: Option<&WssCerts>, -) -> BoxedTransport<(PeerId, libp2p::core::muxing::StreamMuxerBox)> { - let websocket = libp2p::wasm_ext::ffi::websocket_transport(); - let transport = libp2p::wasm_ext::ExtTransport::new(websocket); - upgrade_transport(transport, noise_keys) -} - -#[cfg(not(target_arch = "wasm32"))] -fn build_dns_ws_transport( - noise_keys: libp2p::noise::AuthenticKeypair, - wss_certs: Option<&WssCerts>, -) -> BoxedTransport<(PeerId, libp2p::core::muxing::StreamMuxerBox)> { - use libp2p::websocket::tls as libp2p_tls; - - let ws_tcp = libp2p::dns::TokioDnsConfig::custom( - libp2p::tcp::TokioTcpConfig::new().nodelay(true), - libp2p::dns::ResolverConfig::google(), - Default::default(), - ) - .unwrap(); - let mut ws_dns_tcp = libp2p::websocket::WsConfig::new(ws_tcp); - - if let Some(certs) = wss_certs { - let server_priv_key = libp2p_tls::PrivateKey::new(certs.server_priv_key.0.clone()); - let certs = certs - .certs - .iter() - .map(|cert| libp2p_tls::Certificate::new(cert.0.clone())); - let wss_config = libp2p_tls::Config::new(server_priv_key, certs).unwrap(); - ws_dns_tcp.set_tls_config(wss_config); - } - - // This is for preventing port reuse of dns/tcp instead of - // websocket ports. - let dns_tcp = libp2p::dns::TokioDnsConfig::custom( - libp2p::tcp::TokioTcpConfig::new().nodelay(true), - libp2p::dns::ResolverConfig::google(), - Default::default(), - ) - .unwrap(); - - let transport = dns_tcp.or_transport(ws_dns_tcp); - upgrade_transport(transport, noise_keys) -} - -fn build_memory_transport( - noise_keys: libp2p::noise::AuthenticKeypair, -) -> BoxedTransport<(PeerId, libp2p::core::muxing::StreamMuxerBox)> { - let transport = libp2p::core::transport::MemoryTransport::default(); - upgrade_transport(transport, noise_keys) -} - -/// Set up an encrypted Transport over the Mplex protocol. -fn upgrade_transport( - transport: T, - noise_keys: libp2p::noise::AuthenticKeypair, -) -> BoxedTransport<(PeerId, libp2p::core::muxing::StreamMuxerBox)> -where - T: Transport + Send + Sync + 'static, - T::Output: futures::AsyncRead + futures::AsyncWrite + Unpin + Send + 'static, - T::ListenerUpgrade: Send, - T::Listener: Send, - T::Dial: Send, - T::Error: Send + Sync + 'static, -{ - transport - .upgrade(libp2p::core::upgrade::Version::V1) - .authenticate(noise::NoiseConfig::xx(noise_keys).into_authenticated()) - .multiplex(libp2p::mplex::MplexConfig::default()) - .timeout(std::time::Duration::from_secs(20)) - .map(|(peer, muxer), _| (peer, libp2p::core::muxing::StreamMuxerBox::new(muxer))) - .boxed() -} - -fn generate_ed25519_keypair(rng: &mut R, force_key: Option<[u8; 32]>) -> identity::Keypair { - let mut raw_key = match force_key { - Some(key) => key, - None => { - let mut key = [0; 32]; - rng.fill_bytes(&mut key); - key - }, - }; - let secret = identity::ed25519::SecretKey::from_bytes(&mut raw_key).expect("Secret length is 32 bytes"); - let keypair = identity::ed25519::Keypair::from(secret); - identity::Keypair::Ed25519(keypair) -} - -/// Request the peers sequential until a `PeerResponse::Ok()` will not be received. -async fn request_any_peer( - peers: Vec, - request_data: Vec, - request_response_tx: RequestResponseSender, - response_tx: oneshot::Sender)>>, -) { - debug!("start request_any_peer loop: peers {}", peers.len()); - for peer in peers { - match request_one_peer(peer, request_data.clone(), request_response_tx.clone()).await { - PeerResponse::Ok { res } => { - debug!("Received a response from peer {:?}, stop the request loop", peer); - if response_tx.send(Some((peer, res))).is_err() { - error!("Response oneshot channel was closed"); - } - return; - }, - PeerResponse::None => { - debug!("Received None from peer {:?}, request next peer", peer); - }, - PeerResponse::Err { err } => { - error!("Error on request {:?} peer: {:?}. Request next peer", peer, err); - }, - }; - } - - debug!("None of the peers responded to the request"); - if response_tx.send(None).is_err() { - error!("Response oneshot channel was closed"); - }; -} - -/// Request the peers and collect all their responses. -async fn request_peers( - peers: Vec, - request_data: Vec, - request_response_tx: RequestResponseSender, - response_tx: oneshot::Sender>, -) { - debug!("start request_any_peer loop: peers {}", peers.len()); - let mut futures = Vec::with_capacity(peers.len()); - for peer in peers { - let request_data = request_data.clone(); - let request_response_tx = request_response_tx.clone(); - futures.push(async move { - let response = request_one_peer(peer, request_data, request_response_tx).await; - (peer, response) - }) - } - - let responses = join_all(futures) - .await - .into_iter() - .map(|(peer_id, res)| { - let res: AdexResponse = res.into(); - (peer_id, res) - }) - .collect(); - - if response_tx.send(responses).is_err() { - error!("Response oneshot channel was closed"); - }; -} - -async fn request_one_peer(peer: PeerId, req: Vec, mut request_response_tx: RequestResponseSender) -> PeerResponse { - // Use the internal receiver to receive a response to this request. - let (internal_response_tx, internal_response_rx) = oneshot::channel(); - let request = PeerRequest { req }; - request_response_tx - .send((peer, request, internal_response_tx)) - .await - .unwrap(); - - match internal_response_rx.await { - Ok(response) => response, - Err(e) => PeerResponse::Err { - err: format!("Error on request the peer {:?}: \"{:?}\". Request next peer", peer, e), - }, - } -} diff --git a/mm2src/mm2_libp2p/src/atomicdex_behaviour/tests.rs b/mm2src/mm2_libp2p/src/atomicdex_behaviour/tests.rs deleted file mode 100644 index 4dfdfaa2cb..0000000000 --- a/mm2src/mm2_libp2p/src/atomicdex_behaviour/tests.rs +++ /dev/null @@ -1,368 +0,0 @@ -use super::{spawn_gossipsub, AdexBehaviourCmd, AdexBehaviourEvent, AdexResponse, NodeType, RelayAddress, SwarmRuntime}; -use async_std::task::spawn; -use common::executor::abortable_queue::AbortableQueue; -use futures::channel::{mpsc, oneshot}; -use futures::{SinkExt, StreamExt}; -use libp2p::PeerId; -use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; -use std::sync::Arc; -#[cfg(not(windows))] use std::sync::Mutex; -use std::time::Duration; - -static TEST_LISTEN_PORT: AtomicU64 = AtomicU64::new(1); - -lazy_static! { - static ref SYSTEM: AbortableQueue = AbortableQueue::default(); -} - -fn next_port() -> u64 { TEST_LISTEN_PORT.fetch_add(1, Ordering::Relaxed) } - -struct Node { - peer_id: PeerId, - cmd_tx: mpsc::Sender, -} - -impl Node { - async fn spawn(port: u64, seednodes: Vec, on_event: F) -> Node - where - F: Fn(mpsc::Sender, AdexBehaviourEvent) + Send + 'static, - { - let spawner = SwarmRuntime::new(SYSTEM.weak_spawner()); - let node_type = NodeType::RelayInMemory { port }; - let seednodes = seednodes.into_iter().map(RelayAddress::Memory).collect(); - let (cmd_tx, mut event_rx, peer_id) = spawn_gossipsub(333, None, spawner, seednodes, node_type, |_| {}) - .await - .expect("Error spawning AdexBehaviour"); - - // spawn a response future - let cmd_tx_fut = cmd_tx.clone(); - spawn(async move { - loop { - let cmd_tx_fut = cmd_tx_fut.clone(); - match event_rx.next().await { - Some(r) => on_event(cmd_tx_fut, r), - _ => { - log!("Finish response future"); - break; - }, - } - } - }); - - Node { peer_id, cmd_tx } - } - - async fn send_cmd(&mut self, cmd: AdexBehaviourCmd) { self.cmd_tx.send(cmd).await.unwrap(); } - - async fn wait_peers(&mut self, number: usize) { - let mut attempts = 0; - loop { - let (tx, rx) = oneshot::channel(); - self.cmd_tx - .send(AdexBehaviourCmd::GetPeersInfo { result_tx: tx }) - .await - .unwrap(); - match rx.await { - Ok(map) => { - if map.len() >= number { - return; - } - async_std::task::sleep(Duration::from_millis(500)).await; - }, - Err(e) => panic!("{}", e), - } - attempts += 1; - if attempts >= 10 { - panic!("wait_peers {} attempts exceeded", attempts); - } - } - } -} - -#[tokio::test] -async fn test_request_response_ok() { - let _ = env_logger::try_init(); - - let request_received = Arc::new(AtomicBool::new(false)); - let request_received_cpy = request_received.clone(); - - let node1_port = next_port(); - let node1 = Node::spawn(node1_port, vec![], move |mut cmd_tx, event| { - let (request, response_channel) = match event { - AdexBehaviourEvent::PeerRequest { - request, - response_channel, - .. - } => (request, response_channel), - _ => return, - }; - - request_received_cpy.store(true, Ordering::Relaxed); - assert_eq!(request, b"test request"); - - let res = AdexResponse::Ok { - response: b"test response".to_vec(), - }; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - }) - .await; - - let mut node2 = Node::spawn(next_port(), vec![node1_port], |_, _| ()).await; - - node2.wait_peers(1).await; - - let (response_tx, response_rx) = oneshot::channel(); - node2 - .send_cmd(AdexBehaviourCmd::RequestAnyRelay { - req: b"test request".to_vec(), - response_tx, - }) - .await; - - let response = response_rx.await.unwrap(); - assert_eq!(response, Some((node1.peer_id, b"test response".to_vec()))); - - assert!(request_received.load(Ordering::Relaxed)); -} - -#[tokio::test] -#[cfg(not(windows))] // https://github.com/KomodoPlatform/atomicDEX-API/issues/1712 -async fn test_request_response_ok_three_peers() { - let _ = env_logger::try_init(); - - #[derive(Default)] - struct RequestHandler { - requests: u8, - } - - impl RequestHandler { - fn handle(&mut self, mut cmd_tx: mpsc::Sender, event: AdexBehaviourEvent) { - let (request, response_channel) = match event { - AdexBehaviourEvent::PeerRequest { - request, - response_channel, - .. - } => (request, response_channel), - _ => return, - }; - - self.requests += 1; - - assert_eq!(request, b"test request"); - - // the first time we should respond the none - if self.requests == 1 { - let res = AdexResponse::None; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - return; - } - - // the second time we should respond an error - if self.requests == 2 { - let res = AdexResponse::Err { - error: "test error".into(), - }; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - return; - } - - // the third time we should respond an ok - if self.requests == 3 { - let res = AdexResponse::Ok { - response: format!("success {} request", self.requests).as_bytes().to_vec(), - }; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - return; - } - - panic!("Request received more than 3 times"); - } - } - - let request_handler = Arc::new(Mutex::new(RequestHandler::default())); - - let mut receivers = Vec::new(); - for _ in 0..3 { - let handler = request_handler.clone(); - let receiver_port = next_port(); - let receiver = Node::spawn(receiver_port, vec![], move |cmd_tx, event| { - let mut handler = handler.lock().unwrap(); - handler.handle(cmd_tx, event) - }) - .await; - receivers.push((receiver_port, receiver)); - } - - let mut sender = Node::spawn( - next_port(), - receivers.iter().map(|(port, _)| *port).collect(), - |_, _| (), - ) - .await; - - sender.wait_peers(3).await; - - let (response_tx, response_rx) = oneshot::channel(); - sender - .send_cmd(AdexBehaviourCmd::RequestAnyRelay { - req: b"test request".to_vec(), - response_tx, - }) - .await; - - let (_peer_id, res) = response_rx.await.unwrap().unwrap(); - assert_eq!(res, b"success 3 request".to_vec()); -} - -#[tokio::test] -async fn test_request_response_none() { - let _ = env_logger::try_init(); - - let request_received = Arc::new(AtomicBool::new(false)); - let request_received_cpy = request_received.clone(); - - let node1_port = next_port(); - let _node1 = Node::spawn(node1_port, vec![], move |mut cmd_tx, event| { - let (request, response_channel) = match event { - AdexBehaviourEvent::PeerRequest { - request, - response_channel, - .. - } => (request, response_channel), - _ => return, - }; - - request_received_cpy.store(true, Ordering::Relaxed); - assert_eq!(request, b"test request"); - - let res = AdexResponse::None; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - }) - .await; - - let mut node2 = Node::spawn(next_port(), vec![node1_port], |_, _| ()).await; - - node2.wait_peers(1).await; - - let (response_tx, response_rx) = oneshot::channel(); - node2 - .send_cmd(AdexBehaviourCmd::RequestAnyRelay { - req: b"test request".to_vec(), - response_tx, - }) - .await; - - assert_eq!(response_rx.await.unwrap(), None); - assert!(request_received.load(Ordering::Relaxed)); -} - -#[tokio::test] -#[cfg(target_os = "linux")] // https://github.com/KomodoPlatform/atomicDEX-API/issues/1712 -async fn test_request_peers_ok_three_peers() { - let _ = env_logger::try_init(); - - let receiver1_port = next_port(); - let receiver1 = Node::spawn(receiver1_port, vec![], move |mut cmd_tx, event| { - let (request, response_channel) = match event { - AdexBehaviourEvent::PeerRequest { - request, - response_channel, - .. - } => (request, response_channel), - _ => return, - }; - - assert_eq!(request, b"test request"); - - let res = AdexResponse::None; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - }) - .await; - - let receiver2_port = next_port(); - let receiver2 = Node::spawn(receiver2_port, vec![], move |mut cmd_tx, event| { - let (request, response_channel) = match event { - AdexBehaviourEvent::PeerRequest { - request, - response_channel, - .. - } => (request, response_channel), - _ => return, - }; - - assert_eq!(request, b"test request"); - - let res = AdexResponse::Err { - error: "test error".into(), - }; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - }) - .await; - - let receiver3_port = next_port(); - let receiver3 = Node::spawn(receiver3_port, vec![], move |mut cmd_tx, event| { - let (request, response_channel) = match event { - AdexBehaviourEvent::PeerRequest { - request, - response_channel, - .. - } => (request, response_channel), - _ => return, - }; - - assert_eq!(request, b"test request"); - - let res = AdexResponse::Ok { - response: b"test response".to_vec(), - }; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - }) - .await; - let mut sender = Node::spawn( - next_port(), - vec![receiver1_port, receiver2_port, receiver3_port], - |_, _| (), - ) - .await; - - sender.wait_peers(3).await; - - let (response_tx, response_rx) = oneshot::channel(); - sender - .send_cmd(AdexBehaviourCmd::RequestRelays { - req: b"test request".to_vec(), - response_tx, - }) - .await; - - let mut expected = vec![ - (receiver1.peer_id, AdexResponse::None), - (receiver2.peer_id, AdexResponse::Err { - error: "test error".into(), - }), - (receiver3.peer_id, AdexResponse::Ok { - response: b"test response".to_vec(), - }), - ]; - expected.sort_by(|x, y| x.0.cmp(&y.0)); - - let mut responses = response_rx.await.unwrap(); - responses.sort_by(|x, y| x.0.cmp(&y.0)); - assert_eq!(responses, expected); -} diff --git a/mm2src/mm2_libp2p/src/lib.rs b/mm2src/mm2_libp2p/src/lib.rs deleted file mode 100644 index ae554ca311..0000000000 --- a/mm2src/mm2_libp2p/src/lib.rs +++ /dev/null @@ -1,177 +0,0 @@ -#![feature(ip)] - -#[macro_use] extern crate lazy_static; - -mod adex_ping; -pub mod atomicdex_behaviour; -mod network; -pub mod peers_exchange; -pub mod relay_address; -pub mod request_response; -mod runtime; - -use lazy_static::lazy_static; -use secp256k1::{Message as SecpMessage, PublicKey as Secp256k1Pubkey, Secp256k1, SecretKey, SignOnly, Signature, - VerifyOnly}; -use sha2::{Digest, Sha256}; - -pub use atomicdex_behaviour::{spawn_gossipsub, AdexBehaviourError, NodeType, WssCerts}; -pub use atomicdex_gossipsub::{GossipsubEvent, GossipsubMessage, MessageId, TopicHash}; -pub use libp2p::identity::error::DecodingError; -pub use libp2p::identity::secp256k1::PublicKey as Libp2pSecpPublic; -pub use libp2p::identity::PublicKey as Libp2pPublic; -pub use libp2p::{Multiaddr, PeerId}; -pub use peers_exchange::PeerAddresses; -pub use relay_address::{RelayAddress, RelayAddressError}; -pub use runtime::SwarmRuntime; -use serde::{de, Deserialize, Serialize, Serializer}; - -lazy_static! { - static ref SECP_VERIFY: Secp256k1 = Secp256k1::verification_only(); - static ref SECP_SIGN: Secp256k1 = Secp256k1::signing_only(); -} - -#[derive(Clone, Copy)] -pub enum NetworkInfo { - /// The in-memory network. - InMemory, - /// The distributed network (out of the app memory). - Distributed { network_ports: NetworkPorts }, -} - -impl NetworkInfo { - pub fn in_memory(&self) -> bool { matches!(self, NetworkInfo::InMemory) } -} - -#[derive(Clone, Copy)] -pub struct NetworkPorts { - pub tcp: u16, - pub wss: u16, -} - -pub fn encode_message(message: &T) -> Result, rmp_serde::encode::Error> { - rmp_serde::to_vec(message) -} - -#[inline] -pub fn decode_message<'de, T: de::Deserialize<'de>>(bytes: &'de [u8]) -> Result { - rmp_serde::from_slice(bytes) -} - -#[derive(Deserialize, Serialize)] -struct SignedMessageSerdeHelper<'a> { - pubkey: PublicKey, - #[serde(with = "serde_bytes")] - signature: &'a [u8], - #[serde(with = "serde_bytes")] - payload: &'a [u8], -} - -pub fn encode_and_sign(message: &T, secret: &[u8; 32]) -> Result, rmp_serde::encode::Error> { - let secret = SecretKey::from_slice(secret).unwrap(); - let encoded = encode_message(message)?; - let sig_hash = SecpMessage::from_slice(&sha256(&encoded)).expect("Message::from_slice should never fail"); - let sig = SECP_SIGN.sign(&sig_hash, &secret); - let serialized_sig = sig.serialize_compact(); - let pubkey = PublicKey::from(Secp256k1Pubkey::from_secret_key(&*SECP_SIGN, &secret)); - let msg = SignedMessageSerdeHelper { - pubkey, - signature: &serialized_sig, - payload: &encoded, - }; - encode_message(&msg) -} - -pub fn decode_signed<'de, T: de::Deserialize<'de>>( - encoded: &'de [u8], -) -> Result<(T, Signature, PublicKey), rmp_serde::decode::Error> { - let helper: SignedMessageSerdeHelper = decode_message(encoded)?; - let signature = Signature::from_compact(helper.signature) - .map_err(|e| rmp_serde::decode::Error::Syntax(format!("Failed to parse signature {}", e)))?; - let sig_hash = SecpMessage::from_slice(&sha256(helper.payload)).expect("Message::from_slice should never fail"); - match &helper.pubkey { - PublicKey::Secp256k1(serialized_pub) => { - if SECP_VERIFY.verify(&sig_hash, &signature, &serialized_pub.0).is_err() { - return Err(rmp_serde::decode::Error::Syntax("Invalid message signature".into())); - } - }, - } - - let payload: T = decode_message(helper.payload)?; - Ok((payload, signature, helper.pubkey)) -} - -fn sha256(input: impl AsRef<[u8]>) -> [u8; 32] { Sha256::new().chain(input).finalize().into() } - -#[derive(Debug, Eq, PartialEq)] -pub struct Secp256k1PubkeySerialize(Secp256k1Pubkey); - -impl Serialize for Secp256k1PubkeySerialize { - fn serialize(&self, serializer: S) -> Result { - serializer.serialize_bytes(&self.0.serialize()) - } -} - -impl<'de> de::Deserialize<'de> for Secp256k1PubkeySerialize { - fn deserialize(deserializer: D) -> Result>::Error> - where - D: de::Deserializer<'de>, - { - let slice: &[u8] = de::Deserialize::deserialize(deserializer)?; - let pubkey = - Secp256k1Pubkey::from_slice(slice).map_err(|e| de::Error::custom(format!("Error {} parsing pubkey", e)))?; - - Ok(Secp256k1PubkeySerialize(pubkey)) - } -} - -#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] -pub enum PublicKey { - Secp256k1(Secp256k1PubkeySerialize), -} - -impl PublicKey { - pub fn to_bytes(&self) -> Vec { - match self { - PublicKey::Secp256k1(pubkey) => pubkey.0.serialize().to_vec(), - } - } - - pub fn to_hex(&self) -> String { - match self { - PublicKey::Secp256k1(pubkey) => hex::encode(pubkey.0.serialize().as_ref()), - } - } - - pub fn unprefixed(&self) -> [u8; 32] { - let mut res = [0; 32]; - match self { - PublicKey::Secp256k1(pubkey) => res.copy_from_slice(&pubkey.0.serialize()[1..33]), - } - res - } -} - -impl From for PublicKey { - fn from(pubkey: Secp256k1Pubkey) -> Self { PublicKey::Secp256k1(Secp256k1PubkeySerialize(pubkey)) } -} - -pub type TopicPrefix = &'static str; -pub const TOPIC_SEPARATOR: char = '/'; - -pub fn pub_sub_topic(prefix: TopicPrefix, topic: &str) -> String { - let mut res = prefix.to_owned(); - res.push(TOPIC_SEPARATOR); - res.push_str(topic); - res -} - -#[test] -fn signed_message_serde() { - let secret = [1u8; 32]; - let initial_msg = vec![0u8; 32]; - let signed_encoded = encode_and_sign(&initial_msg, &secret).unwrap(); - - let (decoded, ..) = decode_signed::>(&signed_encoded).unwrap(); - assert_eq!(decoded, initial_msg); -} diff --git a/mm2src/mm2_libp2p/src/network.rs b/mm2src/mm2_libp2p/src/network.rs deleted file mode 100644 index 0b09c444db..0000000000 --- a/mm2src/mm2_libp2p/src/network.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::RelayAddress; -use libp2p::PeerId; - -pub const NETID_8762: u16 = 8762; - -#[cfg_attr(target_arch = "wasm32", allow(dead_code))] -const ALL_NETID_8762_SEEDNODES: &[(&str, &str)] = &[ - ( - "12D3KooWHKkHiNhZtKceQehHhPqwU5W1jXpoVBgS1qst899GjvTm", - "168.119.236.251", - ), - ( - "12D3KooWAToxtunEBWCoAHjefSv74Nsmxranw8juy3eKEdrQyGRF", - "168.119.236.240", - ), - ( - "12D3KooWSmEi8ypaVzFA1AGde2RjxNW5Pvxw3qa2fVe48PjNs63R", - "168.119.236.239", - ), - ( - "12D3KooWJWBnkVsVNjiqUEPjLyHpiSmQVAJ5t6qt1Txv5ctJi9Xd", - "135.181.34.220", - ), - ( - "12D3KooWEsuiKcQaBaKEzuMtT6uFjs89P1E8MK3wGRZbeuCbCw6P", - "168.119.236.241", - ), - ( - "12D3KooWHBeCnJdzNk51G4mLnao9cDsjuqiMTEo5wMFXrd25bd1F", - "168.119.236.243", - ), - ( - "12D3KooWKxavLCJVrQ5Gk1kd9m6cohctGQBmiKPS9XQFoXEoyGmS", - "168.119.236.249", - ), - ( - "12D3KooW9soGyPfX6kcyh3uVXNHq1y2dPmQNt2veKgdLXkBiCVKq", - "168.119.236.246", - ), - ( - "12D3KooWL6yrrNACb7t7RPyTEPxKmq8jtrcbkcNd6H5G2hK7bXaL", - "168.119.236.233", - ), - ( - "12D3KooWMrjLmrv8hNgAoVf1RfumfjyPStzd4nv5XL47zN4ZKisb", - "168.119.237.8", - ), - ( - "12D3KooWPR2RoPi19vQtLugjCdvVmCcGLP2iXAzbDfP3tp81ZL4d", - "168.119.237.13", - ), - ( - "12D3KooWJDoV9vJdy6PnzwVETZ3fWGMhV41VhSbocR1h2geFqq9Y", - "65.108.90.210", - ), - ( - "12D3KooWEaZpH61H4yuQkaNG5AsyGdpBhKRppaLdAY52a774ab5u", - "46.4.78.11", - ), - ( - "12D3KooWAd5gPXwX7eDvKWwkr2FZGfoJceKDCA53SHmTFFVkrN7Q", - "46.4.87.18", - ), -]; - -#[cfg(target_arch = "wasm32")] -pub fn get_all_network_seednodes(_netid: u16) -> Vec<(PeerId, RelayAddress)> { Vec::new() } - -#[cfg(not(target_arch = "wasm32"))] -pub fn get_all_network_seednodes(netid: u16) -> Vec<(PeerId, RelayAddress)> { - use std::str::FromStr; - - if netid != NETID_8762 { - return Vec::new(); - } - ALL_NETID_8762_SEEDNODES - .iter() - .map(|(peer_id, ipv4)| { - let peer_id = PeerId::from_str(peer_id).expect("valid peer id"); - let address = RelayAddress::IPv4(ipv4.to_string()); - (peer_id, address) - }) - .collect() -} diff --git a/mm2src/mm2_libp2p/src/peers_exchange.rs b/mm2src/mm2_libp2p/src/peers_exchange.rs deleted file mode 100644 index 1721f73f8d..0000000000 --- a/mm2src/mm2_libp2p/src/peers_exchange.rs +++ /dev/null @@ -1,436 +0,0 @@ -use crate::request_response::Codec; -use crate::NetworkInfo; -use futures::StreamExt; -use libp2p::swarm::NetworkBehaviour; -use libp2p::{multiaddr::{Multiaddr, Protocol}, - request_response::{ProtocolName, ProtocolSupport, RequestResponse, RequestResponseConfig, - RequestResponseEvent, RequestResponseMessage}, - swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters}, - NetworkBehaviour, PeerId}; -use log::{error, info, warn}; -use rand::seq::SliceRandom; -use serde::{de::Deserializer, ser::Serializer, Deserialize, Serialize}; -use std::collections::HashSet; -use std::{collections::{HashMap, VecDeque}, - iter, - task::{Context, Poll}, - time::Duration}; -use wasm_timer::{Instant, Interval}; - -pub type PeerAddresses = HashSet; - -#[derive(Debug, Clone)] -pub enum PeersExchangeProtocol { - Version1, -} - -impl ProtocolName for PeersExchangeProtocol { - fn protocol_name(&self) -> &[u8] { - match self { - PeersExchangeProtocol::Version1 => b"/peers-exchange/1", - } - } -} - -type PeersExchangeCodec = Codec; - -const DEFAULT_PEERS_NUM: usize = 20; -const REQUEST_PEERS_INITIAL_DELAY: u64 = 20; -const REQUEST_PEERS_INTERVAL: u64 = 300; -const MAX_PEERS: usize = 100; - -#[derive(Debug, Clone, Eq, Hash, PartialEq)] -pub struct PeerIdSerde(PeerId); - -impl From for PeerIdSerde { - fn from(peer_id: PeerId) -> PeerIdSerde { PeerIdSerde(peer_id) } -} - -impl Serialize for PeerIdSerde { - fn serialize(&self, serializer: S) -> Result { - self.0.clone().to_bytes().serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for PeerIdSerde { - fn deserialize>(deserializer: D) -> Result { - let bytes: Vec = Deserialize::deserialize(deserializer)?; - let peer_id = PeerId::from_bytes(&bytes).map_err(|_| serde::de::Error::custom("PeerId::from_bytes error"))?; - Ok(PeerIdSerde(peer_id)) - } -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum PeersExchangeRequest { - GetKnownPeers { num: usize }, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum PeersExchangeResponse { - KnownPeers { peers: HashMap }, -} - -/// Behaviour that requests known peers list from other peers at random -#[derive(NetworkBehaviour)] -#[behaviour(poll_method = "poll", event_process = true)] -pub struct PeersExchange { - request_response: RequestResponse, - #[behaviour(ignore)] - known_peers: Vec, - #[behaviour(ignore)] - reserved_peers: Vec, - #[behaviour(ignore)] - events: VecDeque::ConnectionHandler>>, - #[behaviour(ignore)] - maintain_peers_interval: Interval, - #[behaviour(ignore)] - network_info: NetworkInfo, -} - -#[allow(clippy::new_without_default)] -impl PeersExchange { - pub fn new(network_info: NetworkInfo) -> Self { - let codec = Codec::default(); - let protocol = iter::once((PeersExchangeProtocol::Version1, ProtocolSupport::Full)); - let config = RequestResponseConfig::default(); - let request_response = RequestResponse::new(codec, protocol, config); - PeersExchange { - request_response, - known_peers: Vec::new(), - reserved_peers: Vec::new(), - events: VecDeque::new(), - maintain_peers_interval: Interval::new_at( - Instant::now() + Duration::from_secs(REQUEST_PEERS_INITIAL_DELAY), - Duration::from_secs(REQUEST_PEERS_INTERVAL), - ), - network_info, - } - } - - fn get_random_known_peers(&mut self, num: usize) -> HashMap { - let mut result = HashMap::with_capacity(num); - let mut rng = rand::thread_rng(); - let peer_ids = self - .known_peers - .clone() - .into_iter() - .filter(|peer| !self.request_response.addresses_of_peer(peer).is_empty()) - .collect::>(); - - let peer_ids = peer_ids.choose_multiple(&mut rng, num); - for peer_id in peer_ids { - let addresses = self.request_response.addresses_of_peer(peer_id).into_iter().collect(); - result.insert((*peer_id).into(), addresses); - } - result - } - - fn forget_peer(&mut self, peer: &PeerId) { - self.known_peers.retain(|known_peer| known_peer != peer); - self.forget_peer_addresses(peer); - } - - fn forget_peer_addresses(&mut self, peer: &PeerId) { - for address in self.request_response.addresses_of_peer(peer) { - if !self.is_reserved_peer(peer) { - self.request_response.remove_address(peer, &address); - } - } - } - - pub fn add_peer_addresses_to_known_peers(&mut self, peer: &PeerId, addresses: PeerAddresses) { - for address in addresses.iter() { - if !self.validate_global_multiaddr(address) { - warn!("Attempt adding a not valid address of the peer '{}': {}", peer, address); - return; - } - } - if !self.known_peers.contains(peer) && !addresses.is_empty() { - self.known_peers.push(*peer); - } - let already_known = self.request_response.addresses_of_peer(peer); - for address in addresses { - if !already_known.contains(&address) { - self.request_response.add_address(peer, address); - } - } - } - - pub fn add_peer_addresses_to_reserved_peers(&mut self, peer: &PeerId, addresses: PeerAddresses) { - for address in addresses.iter() { - if !self.validate_global_multiaddr(address) { - return; - } - } - - if !self.reserved_peers.contains(peer) && !addresses.is_empty() { - self.reserved_peers.push(*peer); - } - - let already_reserved = self.request_response.addresses_of_peer(peer); - for address in addresses { - if !already_reserved.contains(&address) { - self.request_response.add_address(peer, address); - } - } - } - - fn maintain_known_peers(&mut self) { - if self.known_peers.len() > MAX_PEERS { - let mut rng = rand::thread_rng(); - let to_remove_num = self.known_peers.len() - MAX_PEERS; - self.known_peers.shuffle(&mut rng); - let removed_peers: Vec<_> = self.known_peers.drain(..to_remove_num).collect(); - for peer in removed_peers { - self.forget_peer_addresses(&peer); - } - } - self.request_known_peers_from_random_peer(); - } - - fn request_known_peers_from_random_peer(&mut self) { - let mut rng = rand::thread_rng(); - if let Some(from_peer) = self.known_peers.choose(&mut rng) { - info!("Try to request {} peers from peer {}", DEFAULT_PEERS_NUM, from_peer); - let request = PeersExchangeRequest::GetKnownPeers { num: DEFAULT_PEERS_NUM }; - self.request_response.send_request(from_peer, request); - } - } - - pub fn get_random_peers( - &mut self, - num: usize, - mut filter: impl FnMut(&PeerId) -> bool, - ) -> HashMap { - let mut result = HashMap::with_capacity(num); - let mut rng = rand::thread_rng(); - let peer_ids = self.known_peers.iter().filter(|peer| filter(peer)).collect::>(); - for peer_id in peer_ids.choose_multiple(&mut rng, num) { - let addresses = self.request_response.addresses_of_peer(peer_id).into_iter().collect(); - result.insert(**peer_id, addresses); - } - result - } - - pub fn is_known_peer(&self, peer: &PeerId) -> bool { self.known_peers.contains(peer) } - - pub fn is_reserved_peer(&self, peer: &PeerId) -> bool { self.reserved_peers.contains(peer) } - - pub fn add_known_peer(&mut self, peer: PeerId) { - if !self.is_known_peer(&peer) { - self.known_peers.push(peer) - } - } - - fn validate_global_multiaddr(&self, address: &Multiaddr) -> bool { - let network_ports = match self.network_info { - NetworkInfo::Distributed { network_ports } => network_ports, - NetworkInfo::InMemory => panic!("PeersExchange must not be used with in-memory network"), - }; - - let mut components = address.iter(); - match components.next() { - Some(Protocol::Ip4(addr)) => { - if !addr.is_global() { - return false; - } - }, - _ => return false, - } - - match components.next() { - Some(Protocol::Tcp(port)) => { - // currently, `NetworkPorts::ws` is not supported by `PeersExchange` - if port != network_ports.tcp { - return false; - } - }, - _ => return false, - } - - true - } - - fn validate_get_known_peers_response(&self, response: &HashMap) -> bool { - if response.is_empty() { - return false; - } - - if response.len() > DEFAULT_PEERS_NUM { - return false; - } - - for addresses in response.values() { - if addresses.is_empty() { - return false; - } - - for address in addresses { - if !self.validate_global_multiaddr(address) { - warn!("Received a not valid address: {}", address); - return false; - } - } - } - true - } - - fn poll( - &mut self, - cx: &mut Context, - _params: &mut impl PollParameters, - ) -> Poll::ConnectionHandler>> { - while let Poll::Ready(Some(())) = self.maintain_peers_interval.poll_next_unpin(cx) { - self.maintain_known_peers(); - } - - if let Some(event) = self.events.pop_front() { - return Poll::Ready(event); - } - - Poll::Pending - } -} - -impl NetworkBehaviourEventProcess> for PeersExchange { - fn inject_event(&mut self, event: RequestResponseEvent) { - match event { - RequestResponseEvent::Message { message, peer } => match message { - RequestResponseMessage::Request { request, channel, .. } => match request { - PeersExchangeRequest::GetKnownPeers { num } => { - // Should not send a response in such case - if num > DEFAULT_PEERS_NUM { - return; - } - let response = PeersExchangeResponse::KnownPeers { - peers: self.get_random_known_peers(num), - }; - if let Err(_response) = self.request_response.send_response(channel, response) { - warn!("Response channel has been closed already"); - } - }, - }, - RequestResponseMessage::Response { response, .. } => match response { - PeersExchangeResponse::KnownPeers { peers } => { - info!("Got peers {:?}", peers); - - if !self.validate_get_known_peers_response(&peers) { - // if peer provides invalid response forget it and try to request from other peer - self.forget_peer(&peer); - self.request_known_peers_from_random_peer(); - return; - } - - peers.into_iter().for_each(|(peer, addresses)| { - self.add_peer_addresses_to_known_peers(&peer.0, addresses); - }); - }, - }, - }, - RequestResponseEvent::OutboundFailure { - peer, - request_id, - error, - } => { - error!( - "Outbound failure {:?} while requesting {:?} to peer {}", - error, request_id, peer - ); - self.forget_peer(&peer); - self.request_known_peers_from_random_peer(); - }, - RequestResponseEvent::InboundFailure { peer, error, .. } => { - error!( - "Inbound failure {:?} while processing request from peer {}", - error, peer - ); - }, - RequestResponseEvent::ResponseSent { .. } => (), - } - } -} - -#[cfg(test)] -mod tests { - use super::{NetworkInfo, PeerIdSerde, PeersExchange}; - use crate::{NetworkPorts, PeerId}; - use libp2p::core::Multiaddr; - use std::collections::{HashMap, HashSet}; - use std::iter::FromIterator; - - #[test] - fn test_peer_id_serde() { - let peer_id = PeerIdSerde(PeerId::random()); - let serialized = rmp_serde::to_vec(&peer_id).unwrap(); - let deserialized: PeerIdSerde = rmp_serde::from_slice(&serialized).unwrap(); - assert_eq!(peer_id.0, deserialized.0); - } - - #[test] - fn test_validate_get_known_peers_response() { - let network_info = NetworkInfo::Distributed { - network_ports: NetworkPorts { tcp: 3000, wss: 3010 }, - }; - let behaviour = PeersExchange::new(network_info); - let response = HashMap::default(); - assert!(!behaviour.validate_get_known_peers_response(&response)); - - let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::new())]); - assert!(!behaviour.validate_get_known_peers_response(&response)); - - let address: Multiaddr = "/ip4/127.0.0.1/tcp/3000".parse().unwrap(); - let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address]))]); - assert!(!behaviour.validate_get_known_peers_response(&response)); - - let address: Multiaddr = "/ip4/216.58.210.142/tcp/3000".parse().unwrap(); - let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address]))]); - assert!(behaviour.validate_get_known_peers_response(&response)); - - let address: Multiaddr = "/ip4/216.58.210.142/tcp/3001".parse().unwrap(); - let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address]))]); - assert!(!behaviour.validate_get_known_peers_response(&response)); - - let address: Multiaddr = "/ip4/216.58.210.142".parse().unwrap(); - let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address]))]); - assert!(!behaviour.validate_get_known_peers_response(&response)); - - let address: Multiaddr = - "/ip4/168.119.236.241/tcp/3000/p2p/12D3KooWEsuiKcQaBaKEzuMtT6uFjs89P1E8MK3wGRZbeuCbCw6P" - .parse() - .unwrap(); - let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address]))]); - assert!(behaviour.validate_get_known_peers_response(&response)); - - let address1: Multiaddr = - "/ip4/168.119.236.241/tcp/3000/p2p/12D3KooWEsuiKcQaBaKEzuMtT6uFjs89P1E8MK3wGRZbeuCbCw6P" - .parse() - .unwrap(); - - let address2: Multiaddr = "/ip4/168.119.236.241/tcp/3000".parse().unwrap(); - let response = HashMap::from_iter(vec![( - PeerIdSerde(PeerId::random()), - HashSet::from_iter(vec![address1, address2]), - )]); - assert!(behaviour.validate_get_known_peers_response(&response)); - } - - #[test] - fn test_get_random_known_peers() { - let mut behaviour = PeersExchange::new(NetworkInfo::InMemory); - let peer_id = PeerId::random(); - behaviour.add_known_peer(peer_id); - - let result = behaviour.get_random_known_peers(1); - assert!(result.is_empty()); - - let address: Multiaddr = "/ip4/168.119.236.241/tcp/3000".parse().unwrap(); - behaviour.request_response.add_address(&peer_id, address.clone()); - - let result = behaviour.get_random_known_peers(1); - assert_eq!(result.len(), 1); - - let addresses = result.get(&peer_id.into()).unwrap(); - assert_eq!(addresses.len(), 1); - assert!(addresses.contains(&address)); - } -} diff --git a/mm2src/mm2_libp2p/src/relay_address.rs b/mm2src/mm2_libp2p/src/relay_address.rs deleted file mode 100644 index d23c419632..0000000000 --- a/mm2src/mm2_libp2p/src/relay_address.rs +++ /dev/null @@ -1,186 +0,0 @@ -use crate::{NetworkInfo, NetworkPorts}; -use derive_more::Display; -use libp2p::Multiaddr; -use serde::{de, Deserialize, Deserializer, Serialize}; -use std::str::FromStr; - -#[derive(Clone, Debug, Display, Serialize)] -pub enum RelayAddressError { - #[display( - fmt = "Error parsing 'RelayAddress' from {}: address has unknown protocol, expected either IPv4 or DNS or Memory address", - found - )] - FromStrError { found: String }, - #[display( - fmt = "Error converting '{:?}' to Multiaddr: unexpected IPv4/DNS address on a memory network", - self_str - )] - DistributedAddrOnMemoryNetwork { self_str: String }, - #[display( - fmt = "Error converting '{:?}' to Multiaddr: unexpected memory address on a distributed network", - self_str - )] - MemoryAddrOnDistributedNetwork { self_str: String }, -} - -impl std::error::Error for RelayAddressError {} - -impl RelayAddressError { - fn distributed_addr_on_memory_network(addr: &RelayAddress) -> RelayAddressError { - RelayAddressError::DistributedAddrOnMemoryNetwork { - self_str: format!("{:?}", addr), - } - } - - fn memory_addr_on_distributed_network(addr: &RelayAddress) -> RelayAddressError { - RelayAddressError::MemoryAddrOnDistributedNetwork { - self_str: format!("{:?}", addr), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum RelayAddress { - IPv4(String), - Dns(String), - Memory(u64), -} - -impl FromStr for RelayAddress { - type Err = RelayAddressError; - - fn from_str(s: &str) -> Result { - // check if the string is IPv4 - if std::net::Ipv4Addr::from_str(s).is_ok() { - return Ok(RelayAddress::IPv4(s.to_string())); - } - // check if the string is a domain name - if validate_domain_name(s) { - return Ok(RelayAddress::Dns(s.to_owned())); - } - // check if the string is a `/memory/` address - if let Some(port_str) = s.strip_prefix("/memory/") { - if let Ok(port) = port_str.parse() { - return Ok(RelayAddress::Memory(port)); - } - } - Err(RelayAddressError::FromStrError { found: s.to_owned() }) - } -} - -impl<'de> Deserialize<'de> for RelayAddress { - fn deserialize(deserializer: D) -> Result>::Error> - where - D: Deserializer<'de>, - { - let addr_str = String::deserialize(deserializer)?; - RelayAddress::from_str(&addr_str).map_err(de::Error::custom) - } -} - -impl RelayAddress { - /// Try to convert `RelayAddress` to `Multiaddr` using the given `network_info`. - pub fn try_to_multiaddr(&self, network_info: NetworkInfo) -> Result { - let network_ports = match network_info { - NetworkInfo::InMemory => match self { - RelayAddress::Memory(port) => return Ok(memory_multiaddr(*port)), - _ => return Err(RelayAddressError::distributed_addr_on_memory_network(self)), - }, - NetworkInfo::Distributed { network_ports } => network_ports, - }; - - match self { - RelayAddress::IPv4(ipv4) => Ok(ipv4_multiaddr(ipv4, network_ports)), - RelayAddress::Dns(dns) => Ok(dns_multiaddr(dns, network_ports)), - RelayAddress::Memory(_) => Err(RelayAddressError::memory_addr_on_distributed_network(self)), - } - } -} - -/// Use [this](https://regex101.com/r/94nCB5/1) regular expression to validate the domain name. -/// See examples at the linked resource above. -fn validate_domain_name(s: &str) -> bool { - use regex::Regex; - - lazy_static! { - static ref DNS_REGEX: Regex = Regex::new(r#"^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$"#).unwrap(); - } - - DNS_REGEX.is_match(s) -} - -fn memory_multiaddr(port: u64) -> Multiaddr { format!("/memory/{}", port).parse().unwrap() } - -#[cfg(target_arch = "wasm32")] -fn ipv4_multiaddr(ipv4_addr: &str, ports: NetworkPorts) -> Multiaddr { - format!("/ip4/{}/tcp/{}/wss", ipv4_addr, ports.wss).parse().unwrap() -} - -#[cfg(not(target_arch = "wasm32"))] -fn ipv4_multiaddr(ipv4_addr: &str, ports: NetworkPorts) -> Multiaddr { - format!("/ip4/{}/tcp/{}", ipv4_addr, ports.tcp).parse().unwrap() -} - -#[cfg(target_arch = "wasm32")] -fn dns_multiaddr(dns_addr: &str, ports: NetworkPorts) -> Multiaddr { - format!("/dns/{}/tcp/{}/wss", dns_addr, ports.wss).parse().unwrap() -} - -#[cfg(not(target_arch = "wasm32"))] -fn dns_multiaddr(dns_addr: &str, ports: NetworkPorts) -> Multiaddr { - format!("/dns/{}/tcp/{}", dns_addr, ports.tcp).parse().unwrap() -} - -#[test] -fn test_relay_address_from_str() { - let valid_addresses = vec![ - ("127.0.0.1", RelayAddress::IPv4("127.0.0.1".to_owned())), - ("255.255.255.255", RelayAddress::IPv4("255.255.255.255".to_owned())), - ("google.com", RelayAddress::Dns("google.com".to_owned())), - ("www.google.com", RelayAddress::Dns("www.google.com".to_owned())), - ("g.co", RelayAddress::Dns("g.co".to_owned())), - ( - "stackoverflow.co.uk", - RelayAddress::Dns("stackoverflow.co.uk".to_owned()), - ), - ("1.2.3.4.com", RelayAddress::Dns("1.2.3.4.com".to_owned())), - ("/memory/123", RelayAddress::Memory(123)), - ("/memory/71428421981", RelayAddress::Memory(71428421981)), - ]; - for (s, expected) in valid_addresses { - let actual = RelayAddress::from_str(s).unwrap_or_else(|_| panic!("Error parsing '{}'", s)); - assert_eq!(actual, expected); - } - - let invalid_addresses = vec![ - "127.0.0", - "127.0.0.0.2", - "google.c", - "http://google.com", - "https://google.com/", - "google.com/", - "/memory/", - "/memory/9999999999999999999999999999999", - ]; - for s in invalid_addresses { - let _ = RelayAddress::from_str(s).expect_err("Expected an error"); - } -} - -#[test] -fn test_deserialize_relay_address() { - #[derive(Deserialize, PartialEq)] - struct Config { - addresses: Vec, - } - - let Config { addresses: actual } = - serde_json::from_str(r#"{"addresses": ["foo.bar.com", "127.0.0.2", "/memory/12345"]}"#) - .expect("Error deserializing a list of RelayAddress"); - let expected = vec![ - RelayAddress::Dns("foo.bar.com".to_owned()), - RelayAddress::IPv4("127.0.0.2".to_owned()), - RelayAddress::Memory(12345), - ]; - assert_eq!(actual, expected); -} diff --git a/mm2src/mm2_libp2p/src/request_response.rs b/mm2src/mm2_libp2p/src/request_response.rs deleted file mode 100644 index 25b87e46de..0000000000 --- a/mm2src/mm2_libp2p/src/request_response.rs +++ /dev/null @@ -1,320 +0,0 @@ -use crate::{decode_message, encode_message}; -use async_trait::async_trait; -use core::iter; -use futures::channel::{mpsc, oneshot}; -use futures::io::{AsyncRead, AsyncWrite}; -use futures::task::{Context, Poll}; -use futures::StreamExt; -use libp2p::core::upgrade::{read_length_prefixed, write_length_prefixed}; -use libp2p::request_response::{ProtocolName, ProtocolSupport, RequestId, RequestResponse, RequestResponseCodec, - RequestResponseConfig, RequestResponseEvent, RequestResponseMessage, ResponseChannel}; -use libp2p::swarm::{NetworkBehaviour, NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters}; -use libp2p::NetworkBehaviour; -use libp2p::PeerId; -use log::{debug, error, warn}; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; -use std::collections::{HashMap, VecDeque}; -use std::io; -use std::time::Duration; -use wasm_timer::{Instant, Interval}; - -const MAX_BUFFER_SIZE: usize = 1024 * 1024 - 100; - -pub type RequestResponseReceiver = mpsc::UnboundedReceiver<(PeerId, PeerRequest, oneshot::Sender)>; -pub type RequestResponseSender = mpsc::UnboundedSender<(PeerId, PeerRequest, oneshot::Sender)>; - -/// Build a request-response network behaviour. -pub fn build_request_response_behaviour() -> RequestResponseBehaviour { - let config = RequestResponseConfig::default(); - let protocol = iter::once((Protocol::Version1, ProtocolSupport::Full)); - let inner = RequestResponse::new(Codec::default(), protocol, config); - - let (tx, rx) = mpsc::unbounded(); - let pending_requests = HashMap::new(); - let events = VecDeque::new(); - let timeout = Duration::from_secs(10); - let timeout_interval = Interval::new(Duration::from_secs(1)); - - RequestResponseBehaviour { - inner, - rx, - tx, - pending_requests, - events, - timeout, - timeout_interval, - } -} - -pub enum RequestResponseBehaviourEvent { - InboundRequest { - peer_id: PeerId, - request: PeerRequest, - response_channel: ResponseChannel, - }, -} - -struct PendingRequest { - tx: oneshot::Sender, - initiated_at: Instant, -} - -#[derive(NetworkBehaviour)] -#[behaviour(out_event = "RequestResponseBehaviourEvent", event_process = true)] -#[behaviour(poll_method = "poll_event")] -pub struct RequestResponseBehaviour { - /// The inner RequestResponse network behaviour. - inner: RequestResponse>, - #[behaviour(ignore)] - rx: RequestResponseReceiver, - #[behaviour(ignore)] - tx: RequestResponseSender, - #[behaviour(ignore)] - pending_requests: HashMap, - /// Events that need to be yielded to the outside when polling. - #[behaviour(ignore)] - events: VecDeque, - /// Timeout for pending requests - #[behaviour(ignore)] - timeout: Duration, - /// Interval for request timeout check - #[behaviour(ignore)] - timeout_interval: Interval, -} - -impl RequestResponseBehaviour { - pub fn sender(&self) -> RequestResponseSender { self.tx.clone() } - - pub fn send_response(&mut self, ch: ResponseChannel, rs: PeerResponse) -> Result<(), PeerResponse> { - self.inner.send_response(ch, rs) - } - - pub fn send_request( - &mut self, - peer_id: &PeerId, - request: PeerRequest, - response_tx: oneshot::Sender, - ) -> RequestId { - let request_id = self.inner.send_request(peer_id, request); - let pending_request = PendingRequest { - tx: response_tx, - initiated_at: Instant::now(), - }; - assert!(self.pending_requests.insert(request_id, pending_request).is_none()); - request_id - } - - fn poll_event( - &mut self, - cx: &mut Context, - _params: &mut impl PollParameters, - ) -> Poll::ConnectionHandler>> - { - // poll the `rx` - match self.rx.poll_next_unpin(cx) { - // received a request, forward it through the network and put to the `pending_requests` - Poll::Ready(Some((peer_id, request, response_tx))) => { - let _request_id = self.send_request(&peer_id, request, response_tx); - }, - // the channel was closed - Poll::Ready(None) => panic!("request-response channel has been closed"), - Poll::Pending => (), - } - - if let Some(event) = self.events.pop_front() { - // forward a pending event to the top - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); - } - - while let Poll::Ready(Some(())) = self.timeout_interval.poll_next_unpin(cx) { - let now = Instant::now(); - let timeout = self.timeout; - self.pending_requests.retain(|request_id, pending_request| { - let retain = now.duration_since(pending_request.initiated_at) < timeout; - if !retain { - warn!("Request {} timed out", request_id); - } - retain - }); - } - - Poll::Pending - } - - fn process_request( - &mut self, - peer_id: PeerId, - request: PeerRequest, - response_channel: ResponseChannel, - ) { - self.events.push_back(RequestResponseBehaviourEvent::InboundRequest { - peer_id, - request, - response_channel, - }) - } - - fn process_response(&mut self, request_id: RequestId, response: PeerResponse) { - match self.pending_requests.remove(&request_id) { - Some(pending) => { - if let Err(e) = pending.tx.send(response) { - error!("{:?}. Request {:?} is not processed", e, request_id); - } - }, - _ => error!("Received unknown request {:?}", request_id), - } - } -} - -impl NetworkBehaviourEventProcess> for RequestResponseBehaviour { - fn inject_event(&mut self, event: RequestResponseEvent) { - let (peer_id, message) = match event { - RequestResponseEvent::Message { peer, message } => (peer, message), - RequestResponseEvent::InboundFailure { error, .. } => { - error!("Error on receive a request: {:?}", error); - return; - }, - RequestResponseEvent::OutboundFailure { - peer, - request_id, - error, - } => { - error!("Error on send request {:?} to peer {:?}: {:?}", request_id, peer, error); - let err_response = PeerResponse::Err { - err: format!("{:?}", error), - }; - self.process_response(request_id, err_response); - return; - }, - RequestResponseEvent::ResponseSent { .. } => return, - }; - - match message { - RequestResponseMessage::Request { request, channel, .. } => { - debug!("Received a request from {:?} peer", peer_id); - self.process_request(peer_id, request, channel) - }, - RequestResponseMessage::Response { request_id, response } => { - debug!( - "Received a response to the {:?} request from peer {:?}", - request_id, peer_id - ); - self.process_response(request_id, response) - }, - } - } -} - -#[derive(Clone)] -pub struct Codec { - phantom: std::marker::PhantomData<(Proto, Req, Res)>, -} - -impl Default for Codec { - fn default() -> Self { - Codec { - phantom: Default::default(), - } - } -} - -#[derive(Debug, Clone)] -pub enum Protocol { - Version1, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct PeerRequest { - pub req: Vec, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum PeerResponse { - Ok { res: Vec }, - None, - Err { err: String }, -} - -macro_rules! try_io { - ($e: expr) => { - match $e { - Ok(ok) => ok, - Err(err) => return Err(io::Error::new(io::ErrorKind::InvalidData, err)), - } - }; -} - -impl ProtocolName for Protocol { - fn protocol_name(&self) -> &[u8] { - match self { - Protocol::Version1 => b"/request-response/1", - } - } -} - -#[async_trait] -impl< - Proto: Clone + ProtocolName + Send + Sync, - Req: DeserializeOwned + Serialize + Send + Sync, - Res: DeserializeOwned + Serialize + Send + Sync, - > RequestResponseCodec for Codec -{ - type Protocol = Proto; - type Request = Req; - type Response = Res; - - async fn read_request(&mut self, _protocol: &Self::Protocol, io: &mut T) -> io::Result - where - T: AsyncRead + Unpin + Send, - { - read_to_end(io).await - } - - async fn read_response(&mut self, _protocol: &Self::Protocol, io: &mut T) -> io::Result - where - T: AsyncRead + Unpin + Send, - { - read_to_end(io).await - } - - async fn write_request(&mut self, _protocol: &Self::Protocol, io: &mut T, req: Self::Request) -> io::Result<()> - where - T: AsyncWrite + Unpin + Send, - { - write_all(io, &req).await - } - - async fn write_response(&mut self, _protocol: &Self::Protocol, io: &mut T, res: Self::Response) -> io::Result<()> - where - T: AsyncWrite + Unpin + Send, - { - write_all(io, &res).await - } -} - -async fn read_to_end(io: &mut T) -> io::Result -where - T: AsyncRead + Unpin + Send, - M: DeserializeOwned, -{ - match read_length_prefixed(io, MAX_BUFFER_SIZE).await { - Ok(data) => Ok(try_io!(decode_message(&data))), - Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)), - } -} - -async fn write_all(io: &mut T, msg: &M) -> io::Result<()> -where - T: AsyncWrite + Unpin + Send, - M: Serialize, -{ - let data = try_io!(encode_message(msg)); - if data.len() > MAX_BUFFER_SIZE { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "Try to send data size over maximum", - )); - } - write_length_prefixed(io, data).await -} diff --git a/mm2src/mm2_libp2p/src/runtime.rs b/mm2src/mm2_libp2p/src/runtime.rs deleted file mode 100644 index 016cf2b455..0000000000 --- a/mm2src/mm2_libp2p/src/runtime.rs +++ /dev/null @@ -1,33 +0,0 @@ -use common::executor::{BoxFutureSpawner, SpawnFuture}; -use futures::Future; -use std::pin::Pin; -use std::sync::Arc; - -#[derive(Clone)] -pub struct SwarmRuntime { - inner: Arc, -} - -impl SwarmRuntime { - pub fn new(spawner: S) -> SwarmRuntime - where - S: BoxFutureSpawner + Send + Sync + 'static, - { - SwarmRuntime { - inner: Arc::new(spawner), - } - } -} - -impl SpawnFuture for SwarmRuntime { - fn spawn(&self, f: F) - where - F: Future + Send + 'static, - { - self.inner.spawn_boxed(Box::new(Box::pin(f))) - } -} - -impl libp2p::core::Executor for SwarmRuntime { - fn exec(&self, future: Pin + Send>>) { self.inner.spawn_boxed(Box::new(future)) } -}