2020-01-25 02:16:02 +11:00
|
|
|
// 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::{
|
|
|
|
io::Error,
|
|
|
|
pin::Pin,
|
|
|
|
task::{Context, Poll},
|
|
|
|
time::Duration,
|
|
|
|
};
|
|
|
|
|
|
|
|
use libp2p_core::{
|
Multiple connections per peer (#1440)
* Allow multiple connections per peer in libp2p-core.
Instead of trying to enforce a single connection per peer,
which involves quite a bit of additional complexity e.g.
to prioritise simultaneously opened connections and can
have other undesirable consequences [1], we now
make multiple connections per peer a feature.
The gist of these changes is as follows:
The concept of a "node" with an implicit 1-1 correspondence
to a connection has been replaced with the "first-class"
concept of a "connection". The code from `src/nodes` has moved
(with varying degrees of modification) to `src/connection`.
A `HandledNode` has become a `Connection`, a `NodeHandler` a
`ConnectionHandler`, the `CollectionStream` was the basis for
the new `connection::Pool`, and so forth.
Conceptually, a `Network` contains a `connection::Pool` which
in turn internally employs the `connection::Manager` for
handling the background `connection::manager::Task`s, one
per connection, as before. These are all considered implementation
details. On the public API, `Peer`s are managed as before through
the `Network`, except now the API has changed with the shift of focus
to (potentially multiple) connections per peer. The `NetworkEvent`s have
accordingly also undergone changes.
The Swarm APIs remain largely unchanged, except for the fact that
`inject_replaced` is no longer called. It may now practically happen
that multiple `ProtocolsHandler`s are associated with a single
`NetworkBehaviour`, one per connection. If implementations of
`NetworkBehaviour` rely somehow on communicating with exactly
one `ProtocolsHandler`, this may cause issues, but it is unlikely.
[1]: https://github.com/paritytech/substrate/issues/4272
* Fix intra-rustdoc links.
* Update core/src/connection/pool.rs
Co-Authored-By: Max Inden <mail@max-inden.de>
* Address some review feedback and fix doc links.
* Allow responses to be sent on the same connection.
* Remove unnecessary remainders of inject_replaced.
* Update swarm/src/behaviour.rs
Co-Authored-By: Pierre Krieger <pierre.krieger1708@gmail.com>
* Update swarm/src/lib.rs
Co-Authored-By: Pierre Krieger <pierre.krieger1708@gmail.com>
* Update core/src/connection/manager.rs
Co-Authored-By: Pierre Krieger <pierre.krieger1708@gmail.com>
* Update core/src/connection/manager.rs
Co-Authored-By: Pierre Krieger <pierre.krieger1708@gmail.com>
* Update core/src/connection/pool.rs
Co-Authored-By: Pierre Krieger <pierre.krieger1708@gmail.com>
* Incorporate more review feedback.
* Move module declaration below imports.
* Update core/src/connection/manager.rs
Co-Authored-By: Toralf Wittner <tw@dtex.org>
* Update core/src/connection/manager.rs
Co-Authored-By: Toralf Wittner <tw@dtex.org>
* Simplify as per review.
* Fix rustoc link.
* Add try_notify_handler and simplify.
* Relocate DialingConnection and DialingAttempt.
For better visibility constraints.
* Small cleanup.
* Small cleanup. More robust EstablishedConnectionIter.
* Clarify semantics of `DialingPeer::connect`.
* Don't call inject_disconnected on InvalidPeerId.
To preserve the previous behavior and ensure calls to
`inject_disconnected` are always paired with calls to
`inject_connected`.
* Provide public ConnectionId constructor.
Mainly needed for testing purposes, e.g. in substrate.
* Move the established connection limit check to the right place.
* Clean up connection error handling.
Separate connection errors into those occuring during
connection setup or upon rejecting a newly established
connection (the `PendingConnectionError`) and those
errors occurring on previously established connections,
i.e. for which a `ConnectionEstablished` event has
been emitted by the connection pool earlier.
* Revert change in log level and clarify an invariant.
* Remove inject_replaced entirely.
* Allow notifying all connection handlers.
Thereby simplify by introducing a new enum `NotifyHandler`,
used with a single constructor `NetworkBehaviourAction::NotifyHandler`.
* Finishing touches.
Small API simplifications and code deduplication.
Some more useful debug logging.
Co-authored-by: Max Inden <mail@max-inden.de>
Co-authored-by: Pierre Krieger <pierre.krieger1708@gmail.com>
Co-authored-by: Toralf Wittner <tw@dtex.org>
2020-03-04 13:49:25 +01:00
|
|
|
Multiaddr,
|
|
|
|
Transport,
|
2020-01-25 02:16:02 +11:00
|
|
|
identity,
|
|
|
|
multiaddr::Protocol,
|
|
|
|
muxing::StreamMuxerBox,
|
2020-02-07 16:29:30 +01:00
|
|
|
transport::MemoryTransport,
|
Multiple connections per peer (#1440)
* Allow multiple connections per peer in libp2p-core.
Instead of trying to enforce a single connection per peer,
which involves quite a bit of additional complexity e.g.
to prioritise simultaneously opened connections and can
have other undesirable consequences [1], we now
make multiple connections per peer a feature.
The gist of these changes is as follows:
The concept of a "node" with an implicit 1-1 correspondence
to a connection has been replaced with the "first-class"
concept of a "connection". The code from `src/nodes` has moved
(with varying degrees of modification) to `src/connection`.
A `HandledNode` has become a `Connection`, a `NodeHandler` a
`ConnectionHandler`, the `CollectionStream` was the basis for
the new `connection::Pool`, and so forth.
Conceptually, a `Network` contains a `connection::Pool` which
in turn internally employs the `connection::Manager` for
handling the background `connection::manager::Task`s, one
per connection, as before. These are all considered implementation
details. On the public API, `Peer`s are managed as before through
the `Network`, except now the API has changed with the shift of focus
to (potentially multiple) connections per peer. The `NetworkEvent`s have
accordingly also undergone changes.
The Swarm APIs remain largely unchanged, except for the fact that
`inject_replaced` is no longer called. It may now practically happen
that multiple `ProtocolsHandler`s are associated with a single
`NetworkBehaviour`, one per connection. If implementations of
`NetworkBehaviour` rely somehow on communicating with exactly
one `ProtocolsHandler`, this may cause issues, but it is unlikely.
[1]: https://github.com/paritytech/substrate/issues/4272
* Fix intra-rustdoc links.
* Update core/src/connection/pool.rs
Co-Authored-By: Max Inden <mail@max-inden.de>
* Address some review feedback and fix doc links.
* Allow responses to be sent on the same connection.
* Remove unnecessary remainders of inject_replaced.
* Update swarm/src/behaviour.rs
Co-Authored-By: Pierre Krieger <pierre.krieger1708@gmail.com>
* Update swarm/src/lib.rs
Co-Authored-By: Pierre Krieger <pierre.krieger1708@gmail.com>
* Update core/src/connection/manager.rs
Co-Authored-By: Pierre Krieger <pierre.krieger1708@gmail.com>
* Update core/src/connection/manager.rs
Co-Authored-By: Pierre Krieger <pierre.krieger1708@gmail.com>
* Update core/src/connection/pool.rs
Co-Authored-By: Pierre Krieger <pierre.krieger1708@gmail.com>
* Incorporate more review feedback.
* Move module declaration below imports.
* Update core/src/connection/manager.rs
Co-Authored-By: Toralf Wittner <tw@dtex.org>
* Update core/src/connection/manager.rs
Co-Authored-By: Toralf Wittner <tw@dtex.org>
* Simplify as per review.
* Fix rustoc link.
* Add try_notify_handler and simplify.
* Relocate DialingConnection and DialingAttempt.
For better visibility constraints.
* Small cleanup.
* Small cleanup. More robust EstablishedConnectionIter.
* Clarify semantics of `DialingPeer::connect`.
* Don't call inject_disconnected on InvalidPeerId.
To preserve the previous behavior and ensure calls to
`inject_disconnected` are always paired with calls to
`inject_connected`.
* Provide public ConnectionId constructor.
Mainly needed for testing purposes, e.g. in substrate.
* Move the established connection limit check to the right place.
* Clean up connection error handling.
Separate connection errors into those occuring during
connection setup or upon rejecting a newly established
connection (the `PendingConnectionError`) and those
errors occurring on previously established connections,
i.e. for which a `ConnectionEstablished` event has
been emitted by the connection pool earlier.
* Revert change in log level and clarify an invariant.
* Remove inject_replaced entirely.
* Allow notifying all connection handlers.
Thereby simplify by introducing a new enum `NotifyHandler`,
used with a single constructor `NetworkBehaviourAction::NotifyHandler`.
* Finishing touches.
Small API simplifications and code deduplication.
Some more useful debug logging.
Co-authored-by: Max Inden <mail@max-inden.de>
Co-authored-by: Pierre Krieger <pierre.krieger1708@gmail.com>
Co-authored-by: Toralf Wittner <tw@dtex.org>
2020-03-04 13:49:25 +01:00
|
|
|
upgrade,
|
2020-01-25 02:16:02 +11:00
|
|
|
};
|
|
|
|
use libp2p_gossipsub::{Gossipsub, GossipsubConfig, GossipsubEvent, Topic};
|
|
|
|
use libp2p_plaintext::PlainText2Config;
|
|
|
|
use libp2p_swarm::Swarm;
|
|
|
|
use libp2p_yamux as yamux;
|
|
|
|
|
|
|
|
struct Graph {
|
2020-02-07 16:29:30 +01:00
|
|
|
pub nodes: Vec<(Multiaddr, Swarm<Gossipsub>)>,
|
2020-01-25 02:16:02 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Future for Graph {
|
|
|
|
type Output = (Multiaddr, GossipsubEvent);
|
|
|
|
|
|
|
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
|
|
|
for (addr, node) in &mut self.nodes {
|
|
|
|
match node.poll_next_unpin(cx) {
|
|
|
|
Poll::Ready(Some(event)) => return Poll::Ready((addr.clone(), event)),
|
|
|
|
Poll::Ready(None) => panic!("unexpected None when polling nodes"),
|
|
|
|
Poll::Pending => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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())
|
2020-02-07 16:29:30 +01:00
|
|
|
.collect::<Vec<(Multiaddr, Swarm<Gossipsub>)>>();
|
2020-01-25 02:16:02 +11:00
|
|
|
|
|
|
|
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_addr(&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 it returns `true`.
|
|
|
|
fn wait_for<F>(self, mut f: F) -> Self
|
|
|
|
where
|
|
|
|
F: FnMut(GossipsubEvent) -> bool,
|
|
|
|
{
|
|
|
|
// 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((_addr, ev)) => {
|
|
|
|
if f(ev) {
|
|
|
|
return Poll::Ready(this.take().unwrap());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Poll::Pending => return Poll::Pending,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
None => panic!("future called after final return"),
|
|
|
|
});
|
|
|
|
|
|
|
|
let fut = async_std::future::timeout(Duration::from_secs(10), fut);
|
|
|
|
|
|
|
|
futures::executor::block_on(fut).unwrap()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-07 16:29:30 +01:00
|
|
|
fn build_node() -> (Multiaddr, Swarm<Gossipsub>) {
|
2020-01-25 02:16:02 +11:00
|
|
|
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::Config::default())
|
|
|
|
.map(|(p, m), _| (p, StreamMuxerBox::new(m)))
|
2020-02-07 16:29:30 +01:00
|
|
|
.map_err(|e| -> Error { panic!("Failed to create transport: {:?}", e) })
|
2020-01-25 02:16:02 +11:00
|
|
|
.boxed();
|
|
|
|
|
|
|
|
let peer_id = public_key.clone().into_peer_id();
|
|
|
|
let behaviour = Gossipsub::new(peer_id.clone(), GossipsubConfig::default());
|
|
|
|
let mut swarm = Swarm::new(transport, behaviour, peer_id);
|
|
|
|
|
|
|
|
let port = 1 + random::<u64>();
|
|
|
|
let mut addr: Multiaddr = Protocol::Memory(port).into();
|
|
|
|
Swarm::listen_on(&mut swarm, addr.clone()).unwrap();
|
|
|
|
|
|
|
|
addr = addr.with(libp2p_core::multiaddr::Protocol::P2p(
|
|
|
|
public_key.into_peer_id().into(),
|
|
|
|
));
|
|
|
|
|
|
|
|
(addr, swarm)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn multi_hop_propagation() {
|
|
|
|
let _ = env_logger::try_init();
|
|
|
|
|
|
|
|
fn prop(num_nodes: usize, seed: u64) -> TestResult {
|
|
|
|
if num_nodes < 2 || num_nodes > 100 {
|
|
|
|
return TestResult::discard();
|
|
|
|
}
|
|
|
|
|
|
|
|
debug!("number nodes: {:?}, seed: {:?}", num_nodes, seed);
|
|
|
|
|
|
|
|
let mut graph = Graph::new_connected(num_nodes, 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.subscribe(topic.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for all nodes to be subscribed.
|
|
|
|
let mut subscribed = 0;
|
|
|
|
graph = graph.wait_for(move |ev| {
|
|
|
|
if let GossipsubEvent::Subscribed { .. } = ev {
|
|
|
|
subscribed += 1;
|
|
|
|
if subscribed == (number_nodes - 1) * 2 {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
|
|
|
});
|
|
|
|
|
|
|
|
// Publish a single message.
|
|
|
|
graph.nodes[0].1.publish(&topic, vec![1, 2, 3]);
|
|
|
|
|
|
|
|
// Wait for all nodes to receive the published message.
|
|
|
|
let mut received_msgs = 0;
|
|
|
|
graph.wait_for(move |ev| {
|
|
|
|
if let GossipsubEvent::Message(..) = ev {
|
|
|
|
received_msgs += 1;
|
|
|
|
if received_msgs == number_nodes - 1 {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
|
|
|
});
|
|
|
|
|
|
|
|
TestResult::passed()
|
|
|
|
}
|
|
|
|
|
|
|
|
QuickCheck::new()
|
|
|
|
.max_tests(10)
|
|
|
|
.quickcheck(prop as fn(usize, u64) -> TestResult)
|
|
|
|
}
|