diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d4148477108..b7b26694ac5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,13 @@ jobs: test-desktop: name: Build and test runs-on: ubuntu-latest + strategy: + matrix: + args: [ + "--no-default-features", + "--all-features", + "--benches --all-features", + ] steps: - name: Cancel Previous Runs @@ -17,22 +24,23 @@ jobs: with: access_token: ${{ github.token }} - - uses: actions/checkout@v2.3.4 + - uses: actions/checkout@v2.3.5 - uses: Swatinem/rust-cache@v1.3.0 + with: + key: ${{ matrix.args }} - - name: Run tests, with no feature - run: cargo test --workspace --no-default-features - - - name: Run tests, with all features - run: cargo test --workspace --all-features - - - name: Run benches, with all features - run: cargo test --workspace --benches --all-features + - run: cargo test --workspace ${{ matrix.args }} test-wasm: name: Build on WASM runs-on: ubuntu-latest + strategy: + matrix: + toolchain: [ + wasm32-unknown-emscripten, + wasm32-wasi + ] container: image: rust env: @@ -44,20 +52,13 @@ jobs: with: access_token: ${{ github.token }} - - uses: actions/checkout@v2.3.4 - - - name: Install Rust wasm32-unknown-emscripten - uses: actions-rs/toolchain@v1.0.7 - with: - toolchain: stable - target: wasm32-unknown-emscripten - override: true + - uses: actions/checkout@v2.3.5 - - name: Install Rust wasm32-wasi + - name: Install Rust ${{ matrix.toolchain }} uses: actions-rs/toolchain@v1.0.7 with: toolchain: stable - target: wasm32-wasi + target: ${{ matrix.toolchain }} override: true - name: Install a recent version of clang @@ -69,16 +70,13 @@ jobs: run: apt-get install -y cmake - uses: Swatinem/rust-cache@v1.3.0 + with: + key: ${{ matrix.toolchain }} - - name: Build on wasm32-unknown-emscripten - # TODO: also run `cargo test` - # TODO: ideally we would build `--workspace`, but not all crates compile for WASM - run: cargo build --target=wasm32-unknown-emscripten - - - name: Build on wasm32-wasi + - name: Build on ${{ matrix.toolchain }} # TODO: also run `cargo test` # TODO: ideally we would build `--workspace`, but not all crates compile for WASM - run: cargo build --target=wasm32-wasi + run: cargo build --target=${{ matrix.toolchain }} check-rustdoc-links: name: Check rustdoc intra-doc links @@ -92,7 +90,9 @@ jobs: with: access_token: ${{ github.token }} - - uses: actions/checkout@v2.3.4 + - uses: actions/checkout@v2.3.5 + + - uses: Swatinem/rust-cache@v1.3.0 - name: Check rustdoc links run: RUSTDOCFLAGS="--deny broken_intra_doc_links" cargo doc --verbose --workspace --no-deps --document-private-items @@ -106,7 +106,7 @@ jobs: with: access_token: ${{ github.token }} - - uses: actions/checkout@v2.3.4 + - uses: actions/checkout@v2.3.5 - uses: actions-rs/toolchain@v1.0.7 with: @@ -134,7 +134,7 @@ jobs: with: access_token: ${{ github.token }} - - uses: actions/checkout@v2.3.4 + - uses: actions/checkout@v2.3.5 - uses: Swatinem/rust-cache@v1.3.0 @@ -150,7 +150,7 @@ jobs: with: access_token: ${{ github.token }} - - uses: actions/checkout@v2.3.4 + - uses: actions/checkout@v2.3.5 - uses: actions-rs/toolchain@v1.0.7 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index c28e4afe1e8..db0ca108d3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,7 +42,13 @@ # `libp2p` facade crate -## Version 0.40.0 [unreleased] + +## Version 0.40.0-rc.2 [2021-10-15] + +- Update individual crates. + - `libp2p-kad` + +## Version 0.40.0-rc.1 [2021-10-15] - Update individual crates. - `libp2p-core` diff --git a/Cargo.toml b/Cargo.toml index bb665d0d573..64522036420 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p" edition = "2018" description = "Peer-to-peer networking library" -version = "0.40.0" +version = "0.40.0-rc.2" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -71,37 +71,37 @@ bytes = "1" futures = "0.3.1" lazy_static = "1.2" libp2p-autonat = { version = "0.20.0", path = "protocols/autonat", optional = true } -libp2p-core = { version = "0.30.0", path = "core", default-features = false } -libp2p-floodsub = { version = "0.31.0", path = "protocols/floodsub", optional = true } -libp2p-gossipsub = { version = "0.33.0", path = "./protocols/gossipsub", optional = true } -libp2p-identify = { version = "0.31.0", path = "protocols/identify", optional = true } -libp2p-kad = { version = "0.32.0", path = "protocols/kad", optional = true } -libp2p-metrics = { version = "0.1.0", path = "misc/metrics", optional = true } -libp2p-mplex = { version = "0.30.0", path = "muxers/mplex", optional = true } -libp2p-noise = { version = "0.33.0", path = "transports/noise", optional = true } -libp2p-ping = { version = "0.31.0", path = "protocols/ping", optional = true } -libp2p-plaintext = { version = "0.30.0", path = "transports/plaintext", optional = true } -libp2p-pnet = { version = "0.22.0", path = "transports/pnet", optional = true } -libp2p-relay = { version = "0.4.0", path = "protocols/relay", optional = true } -libp2p-rendezvous = { version = "0.1.0", path = "protocols/rendezvous", optional = true } -libp2p-request-response = { version = "0.13.0", path = "protocols/request-response", optional = true } -libp2p-swarm = { version = "0.31.0", path = "swarm" } -libp2p-swarm-derive = { version = "0.25.0", path = "swarm-derive" } -libp2p-uds = { version = "0.30.0", path = "transports/uds", optional = true } -libp2p-wasm-ext = { version = "0.30.0", path = "transports/wasm-ext", default-features = false, optional = true } -libp2p-yamux = { version = "0.34.0", path = "muxers/yamux", optional = true } -multiaddr = { version = "0.13.0" } +libp2p-core = { version = "0.30.0-rc.1", path = "core", default-features = false } +libp2p-floodsub = { version = "0.31.0-rc.1", path = "protocols/floodsub", optional = true } +libp2p-gossipsub = { version = "0.33.0-rc.1", path = "./protocols/gossipsub", optional = true } +libp2p-identify = { version = "0.31.0-rc.1", path = "protocols/identify", optional = true } +libp2p-kad = { version = "0.32.0-rc.2", path = "protocols/kad", optional = true } +libp2p-metrics = { version = "0.1.0-rc.1", path = "misc/metrics", optional = true } +libp2p-mplex = { version = "0.30.0-rc.1", path = "muxers/mplex", optional = true } +libp2p-noise = { version = "0.33.0-rc.1", path = "transports/noise", optional = true } +libp2p-ping = { version = "0.31.0-rc.1", path = "protocols/ping", optional = true } +libp2p-plaintext = { version = "0.30.0-rc.1", path = "transports/plaintext", optional = true } +libp2p-pnet = { version = "0.22.0-rc.1", path = "transports/pnet", optional = true } +libp2p-relay = { version = "0.4.0-rc.1", path = "protocols/relay", optional = true } +libp2p-rendezvous = { version = "0.1.0-rc.1", path = "protocols/rendezvous", optional = true } +libp2p-request-response = { version = "0.13.0-rc.1", path = "protocols/request-response", optional = true } +libp2p-swarm = { version = "0.31.0-rc.1", path = "swarm" } +libp2p-swarm-derive = { version = "0.25.0-rc.1", path = "swarm-derive" } +libp2p-uds = { version = "0.30.0-rc.1", path = "transports/uds", optional = true } +libp2p-wasm-ext = { version = "0.30.0-rc.1", path = "transports/wasm-ext", default-features = false, optional = true } +libp2p-yamux = { version = "0.34.0-rc.1", path = "muxers/yamux", optional = true } +multiaddr = { version = "0.13.0-rc.1" } parking_lot = "0.11.0" pin-project = "1.0.0" smallvec = "1.6.1" wasm-timer = "0.2.4" [target.'cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown")))'.dependencies] -libp2p-deflate = { version = "0.30.0", path = "transports/deflate", optional = true } -libp2p-dns = { version = "0.30.0", path = "transports/dns", optional = true, default-features = false } -libp2p-mdns = { version = "0.32.0", path = "protocols/mdns", optional = true } -libp2p-tcp = { version = "0.30.0", path = "transports/tcp", default-features = false, optional = true } -libp2p-websocket = { version = "0.31.0", path = "transports/websocket", optional = true } +libp2p-deflate = { version = "0.30.0-rc.1", path = "transports/deflate", optional = true } +libp2p-dns = { version = "0.30.0-rc.1", path = "transports/dns", optional = true, default-features = false } +libp2p-mdns = { version = "0.32.0-rc.1", path = "protocols/mdns", optional = true } +libp2p-tcp = { version = "0.30.0-rc.1", path = "transports/tcp", default-features = false, optional = true } +libp2p-websocket = { version = "0.31.0-rc.1", path = "transports/websocket", optional = true } [dev-dependencies] async-std = { version = "1.6.2", features = ["attributes"] } diff --git a/README.md b/README.md index 7536687dcb6..1d5f52be414 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,11 @@ Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). > - "Too long, didn't read" is not a valid excuse for not knowing what is in > this document. +## Maintainers + +- Max Inden ([@mxinden](https://github.com/mxinden/)) +- Thomas Eizinger ([@thomaseizinger](https://github.com/thomaseizinger)) + ## Notable users (open a pull request if you want your project to be added here) diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 1c4ace6835f..cbe0d188e97 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.30.0 [unreleased] +# 0.30.0-rc.1 [2021-10-15] - Add `ConnectionLimit::with_max_established` (see [PR 2137]). @@ -36,6 +36,17 @@ - Add `SignedEnvelope` and `PeerRecord` according to [RFC0002] and [RFC0003] (see [PR 2107]). +- Report `ListenersEvent::Closed` when dropping a listener in `ListenersStream::remove_listener`, + return `bool` instead of `Result<(), ()>` (see [PR 2261]). + +- Concurrently dial address candidates within a single dial attempt (see [PR 2248]) configured + via `Network::with_dial_concurrency_factor`. + + - On success of a single address, provide errors of the thus far failed dials via + `NetworkEvent::ConnectionEstablished::outgoing`. + + - On failure of all addresses, provide the errors via `NetworkEvent::DialError`. + [PR 2145]: https://github.com/libp2p/rust-libp2p/pull/2145 [PR 2213]: https://github.com/libp2p/rust-libp2p/pull/2213 [PR 2142]: https://github.com/libp2p/rust-libp2p/pull/2142 @@ -44,6 +55,8 @@ [PR 2191]: https://github.com/libp2p/rust-libp2p/pull/2191 [PR 2195]: https://github.com/libp2p/rust-libp2p/pull/2195 [PR 2107]: https://github.com/libp2p/rust-libp2p/pull/2107 +[PR 2248]: https://github.com/libp2p/rust-libp2p/pull/2248 +[PR 2261]: https://github.com/libp2p/rust-libp2p/pull/2261 [RFC0002]: https://github.com/libp2p/specs/blob/master/RFC/0002-signed-envelopes.md [RFC0003]: https://github.com/libp2p/specs/blob/master/RFC/0003-routing-records.md diff --git a/core/Cargo.toml b/core/Cargo.toml index e2ca3996738..18a0a90b17e 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-core" edition = "2018" description = "Core traits and structs of libp2p" -version = "0.30.0" +version = "0.30.0-rc.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -18,15 +18,15 @@ fnv = "1.0" futures = { version = "0.3.1", features = ["executor", "thread-pool"] } futures-timer = "3" lazy_static = "1.2" -libsecp256k1 = { version = "0.6.0", optional = true } +libsecp256k1 = { version = "0.7.0", optional = true } log = "0.4" multiaddr = { version = "0.13.0" } multihash = { version = "0.14", default-features = false, features = ["std", "multihash-impl", "identity", "sha2"] } multistream-select = { version = "0.10", path = "../misc/multistream-select" } parking_lot = "0.11.0" pin-project = "1.0.0" -prost = "0.8" -rand = "0.7" +prost = "0.9" +rand = "0.8" rw-stream-sink = "0.2.0" sha2 = "0.9.1" smallvec = "1.6.1" @@ -47,10 +47,11 @@ libp2p-noise = { path = "../transports/noise" } libp2p-tcp = { path = "../transports/tcp" } multihash = { version = "0.14", default-features = false, features = ["arb"] } quickcheck = "0.9.0" +rand07 = { package = "rand", version = "0.7" } wasm-timer = "0.2" [build-dependencies] -prost-build = "0.8" +prost-build = "0.9" [features] default = ["secp256k1"] diff --git a/core/src/connection.rs b/core/src/connection.rs index 9e39ae21807..961eb4abc0e 100644 --- a/core/src/connection.rs +++ b/core/src/connection.rs @@ -23,13 +23,14 @@ pub(crate) mod handler; mod listeners; mod substream; -pub(crate) mod manager; pub(crate) mod pool; -pub use error::{ConnectionError, PendingConnectionError}; +pub use error::{ + ConnectionError, PendingConnectionError, PendingInboundConnectionError, + PendingOutboundConnectionError, +}; pub use handler::{ConnectionHandler, ConnectionHandlerEvent, IntoConnectionHandler}; pub use listeners::{ListenerId, ListenersEvent, ListenersStream}; -pub use manager::ConnectionId; pub use pool::{ConnectionCounters, ConnectionLimits}; pub use pool::{EstablishedConnection, EstablishedConnectionIter, PendingConnection}; pub use substream::{Close, Substream, SubstreamEndpoint}; @@ -40,6 +41,21 @@ use std::hash::Hash; use std::{error::Error, fmt, pin::Pin, task::Context, task::Poll}; use substream::{Muxing, SubstreamEvent}; +/// Connection identifier. +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct ConnectionId(usize); + +impl ConnectionId { + /// Creates a `ConnectionId` from a non-negative integer. + /// + /// This is primarily useful for creating connection IDs + /// in test environments. There is in general no guarantee + /// that all connection IDs are based on non-negative integers. + pub fn new(id: usize) -> Self { + ConnectionId(id) + } +} + /// The endpoint roles associated with a peer-to-peer communication channel. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum Endpoint { @@ -72,7 +88,40 @@ impl Endpoint { } } -/// The endpoint roles associated with a peer-to-peer connection. +/// The endpoint roles associated with a pending peer-to-peer connection. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum PendingPoint { + /// The socket comes from a dialer. + /// + /// There is no single address associated with the Dialer of a pending + /// connection. Addresses are dialed in parallel. Only once the first dial + /// is successful is the address of the connection known. + Dialer, + /// The socket comes from a listener. + Listener { + /// Local connection address. + local_addr: Multiaddr, + /// Address used to send back data to the remote. + send_back_addr: Multiaddr, + }, +} + +impl From for PendingPoint { + fn from(endpoint: ConnectedPoint) -> Self { + match endpoint { + ConnectedPoint::Dialer { .. } => PendingPoint::Dialer, + ConnectedPoint::Listener { + local_addr, + send_back_addr, + } => PendingPoint::Listener { + local_addr, + send_back_addr, + }, + } + } +} + +/// The endpoint roles associated with an established peer-to-peer connection. #[derive(PartialEq, Eq, Debug, Clone, Hash)] pub enum ConnectedPoint { /// We dialed the node. @@ -84,7 +133,7 @@ pub enum ConnectedPoint { Listener { /// Local connection address. local_addr: Multiaddr, - /// Stack of protocols used to send back data to the remote. + /// Address used to send back data to the remote. send_back_addr: Multiaddr, }, } @@ -289,32 +338,23 @@ where pub struct IncomingInfo<'a> { /// Local connection address. pub local_addr: &'a Multiaddr, - /// Stack of protocols used to send back data to the remote. + /// Address used to send back data to the remote. pub send_back_addr: &'a Multiaddr, } impl<'a> IncomingInfo<'a> { - /// Builds the `ConnectedPoint` corresponding to the incoming connection. - pub fn to_connected_point(&self) -> ConnectedPoint { - ConnectedPoint::Listener { + /// Builds the [`PendingPoint`] corresponding to the incoming connection. + pub fn to_pending_point(&self) -> PendingPoint { + PendingPoint::Listener { local_addr: self.local_addr.clone(), send_back_addr: self.send_back_addr.clone(), } } -} - -/// Borrowed information about an outgoing connection currently being negotiated. -#[derive(Debug, Copy, Clone)] -pub struct OutgoingInfo<'a> { - pub address: &'a Multiaddr, - pub peer_id: Option<&'a PeerId>, -} - -impl<'a> OutgoingInfo<'a> { - /// Builds a `ConnectedPoint` corresponding to the outgoing connection. + /// Builds the [`ConnectedPoint`] corresponding to the incoming connection. pub fn to_connected_point(&self) -> ConnectedPoint { - ConnectedPoint::Dialer { - address: self.address.clone(), + ConnectedPoint::Listener { + local_addr: self.local_addr.clone(), + send_back_addr: self.send_back_addr.clone(), } } } diff --git a/core/src/connection/error.rs b/core/src/connection/error.rs index ec4f7ff6e61..e127db7574e 100644 --- a/core/src/connection/error.rs +++ b/core/src/connection/error.rs @@ -20,6 +20,7 @@ use crate::connection::ConnectionLimit; use crate::transport::TransportError; +use crate::Multiaddr; use std::{fmt, io}; /// Errors that can occur in the context of an established `Connection`. @@ -29,10 +30,6 @@ pub enum ConnectionError { // TODO: Eventually this should also be a custom error? IO(io::Error), - /// The connection was dropped because the connection limit - /// for a peer has been reached. - ConnectionLimit(ConnectionLimit), - /// The connection handler produced an error. Handler(THandlerErr), } @@ -45,9 +42,6 @@ where match self { ConnectionError::IO(err) => write!(f, "Connection error: I/O error: {}", err), ConnectionError::Handler(err) => write!(f, "Connection error: Handler error: {}", err), - ConnectionError::ConnectionLimit(l) => { - write!(f, "Connection error: Connection limit: {}.", l) - } } } } @@ -60,16 +54,31 @@ where match self { ConnectionError::IO(err) => Some(err), ConnectionError::Handler(err) => Some(err), - ConnectionError::ConnectionLimit(..) => None, } } } +/// Errors that can occur in the context of a pending outgoing `Connection`. +/// +/// Note: Addresses for an outbound connection are dialed in parallel. Thus, compared to +/// [`PendingInboundConnectionError`], one or more [`TransportError`]s can occur for a single +/// connection. +pub type PendingOutboundConnectionError = + PendingConnectionError)>>; + +/// Errors that can occur in the context of a pending incoming `Connection`. +pub type PendingInboundConnectionError = + PendingConnectionError>; + /// Errors that can occur in the context of a pending `Connection`. #[derive(Debug)] pub enum PendingConnectionError { - /// An error occurred while negotiating the transport protocol(s). - Transport(TransportError), + /// An error occurred while negotiating the transport protocol(s) on a connection. + Transport(TTransErr), + + /// The connection was dropped because the connection limit + /// for a peer has been reached. + ConnectionLimit(ConnectionLimit), /// Pending connection attempt has been aborted. Aborted, @@ -85,14 +94,21 @@ pub enum PendingConnectionError { impl fmt::Display for PendingConnectionError where - TTransErr: fmt::Display, + TTransErr: fmt::Display + fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { PendingConnectionError::IO(err) => write!(f, "Pending connection: I/O error: {}", err), PendingConnectionError::Aborted => write!(f, "Pending connection: Aborted."), PendingConnectionError::Transport(err) => { - write!(f, "Pending connection: Transport error: {}", err) + write!( + f, + "Pending connection: Transport error on connection: {}", + err + ) + } + PendingConnectionError::ConnectionLimit(l) => { + write!(f, "Connection error: Connection limit: {}.", l) } PendingConnectionError::InvalidPeerId => { write!(f, "Pending connection: Invalid peer ID.") @@ -108,9 +124,10 @@ where fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { PendingConnectionError::IO(err) => Some(err), - PendingConnectionError::Transport(err) => Some(err), + PendingConnectionError::Transport(_) => None, PendingConnectionError::InvalidPeerId => None, PendingConnectionError::Aborted => None, + PendingConnectionError::ConnectionLimit(..) => None, } } } diff --git a/core/src/connection/listeners.rs b/core/src/connection/listeners.rs index cf6daa17f5f..4c394aeb75d 100644 --- a/core/src/connection/listeners.rs +++ b/core/src/connection/listeners.rs @@ -27,7 +27,7 @@ use crate::{ use futures::{prelude::*, task::Context, task::Poll}; use log::debug; use smallvec::SmallVec; -use std::{collections::VecDeque, fmt, pin::Pin}; +use std::{collections::VecDeque, fmt, mem, pin::Pin}; /// Implementation of `futures::Stream` that allows listening on multiaddresses. /// @@ -90,6 +90,8 @@ where listeners: VecDeque>>>, /// The next listener ID to assign. next_id: ListenerId, + /// Pending listeners events to return from [`ListenersStream::poll`]. + pending_events: VecDeque>, } /// The ID of a single listener. @@ -177,6 +179,7 @@ where transport, listeners: VecDeque::new(), next_id: ListenerId(1), + pending_events: VecDeque::new(), } } @@ -187,6 +190,7 @@ where transport, listeners: VecDeque::with_capacity(capacity), next_id: ListenerId(1), + pending_events: VecDeque::new(), } } @@ -213,13 +217,24 @@ where /// Remove the listener matching the given `ListenerId`. /// - /// Return `Ok(())` if a listener with this ID was in the list. - pub fn remove_listener(&mut self, id: ListenerId) -> Result<(), ()> { + /// Returns `true` if there was a listener with this ID, `false` + /// otherwise. + pub fn remove_listener(&mut self, id: ListenerId) -> bool { if let Some(i) = self.listeners.iter().position(|l| l.id == id) { - self.listeners.remove(i); - Ok(()) + let mut listener = self + .listeners + .remove(i) + .expect("Index can not be out of bounds."); + let listener_project = listener.as_mut().project(); + let addresses = mem::take(listener_project.addresses).into_vec(); + self.pending_events.push_back(ListenersEvent::Closed { + listener_id: *listener_project.id, + addresses, + reason: Ok(()), + }); + true } else { - Err(()) + false } } @@ -235,6 +250,10 @@ where /// Provides an API similar to `Stream`, except that it cannot end. pub fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + // Return pending events from closed listeners. + if let Some(event) = self.pending_events.pop_front() { + return Poll::Ready(event); + } // We remove each element from `listeners` one by one and add them back. let mut remaining = self.listeners.len(); while let Some(mut listener) = self.listeners.pop_back() { @@ -264,8 +283,7 @@ where Poll::Ready(Some(Ok(ListenerEvent::NewAddress(a)))) => { if listener_project.addresses.contains(&a) { debug!("Transport has reported address {} multiple times", a) - } - if !listener_project.addresses.contains(&a) { + } else { listener_project.addresses.push(a.clone()); } let id = *listener_project.id; @@ -293,18 +311,20 @@ where }); } Poll::Ready(None) => { + let addresses = mem::take(listener_project.addresses).into_vec(); return Poll::Ready(ListenersEvent::Closed { listener_id: *listener_project.id, - addresses: listener_project.addresses.drain(..).collect(), + addresses, reason: Ok(()), - }) + }); } Poll::Ready(Some(Err(err))) => { + let addresses = mem::take(listener_project.addresses).into_vec(); return Poll::Ready(ListenersEvent::Closed { listener_id: *listener_project.id, - addresses: listener_project.addresses.drain(..).collect(), + addresses, reason: Err(err), - }) + }); } } } @@ -538,4 +558,36 @@ mod tests { } }); } + + #[test] + fn listener_closed() { + async_std::task::block_on(async move { + let mem_transport = transport::MemoryTransport::default(); + + let mut listeners = ListenersStream::new(mem_transport); + let id = listeners.listen_on("/memory/0".parse().unwrap()).unwrap(); + + let event = listeners.next().await.unwrap(); + let addr; + if let ListenersEvent::NewAddress { listen_addr, .. } = event { + addr = listen_addr + } else { + panic!("Was expecting the listen address to be reported") + } + + assert!(listeners.remove_listener(id)); + + match listeners.next().await.unwrap() { + ListenersEvent::Closed { + listener_id, + addresses, + reason: Ok(()), + } => { + assert_eq!(listener_id, id); + assert!(addresses.contains(&addr)); + } + other => panic!("Unexpected listeners event: {:?}", other), + } + }); + } } diff --git a/core/src/connection/manager.rs b/core/src/connection/manager.rs deleted file mode 100644 index 51c08024202..00000000000 --- a/core/src/connection/manager.rs +++ /dev/null @@ -1,488 +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. - -use super::{ - handler::{THandlerError, THandlerInEvent, THandlerOutEvent}, - Connected, ConnectedPoint, ConnectionError, ConnectionHandler, ConnectionLimit, - IntoConnectionHandler, PendingConnectionError, Substream, -}; -use crate::{muxing::StreamMuxer, Executor}; -use fnv::FnvHashMap; -use futures::{channel::mpsc, prelude::*, stream::FuturesUnordered}; -use std::{ - collections::hash_map, - error, fmt, mem, - pin::Pin, - task::{Context, Poll}, -}; -use task::{Task, TaskId}; - -mod task; - -// Implementation Notes -// ==================== -// -// A `Manager` is decoupled from the background tasks through channels. -// The state of a `Manager` therefore "lags behind" the progress of -// the tasks -- it is only made aware of progress in the background tasks -// when it is `poll()`ed. -// -// A `Manager` is ignorant of substreams and does not emit any events -// related to specific substreams. -// -// A `Manager` is unaware of any association between connections and peers -// / peer identities (i.e. the type parameter `C` is completely opaque). -// -// There is a 1-1 correspondence between (internal) task IDs and (public) -// connection IDs, i.e. the task IDs are "re-exported" as connection IDs -// by the manager. The notion of a (background) task is internal to the -// manager. - -/// The result of a pending connection attempt. -type ConnectResult = Result<(Connected, M), PendingConnectionError>; - -/// Connection identifier. -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct ConnectionId(TaskId); - -impl ConnectionId { - /// Creates a `ConnectionId` from a non-negative integer. - /// - /// This is primarily useful for creating connection IDs - /// in test environments. There is in general no guarantee - /// that all connection IDs are based on non-negative integers. - pub fn new(id: usize) -> Self { - ConnectionId(TaskId(id)) - } -} - -/// A connection `Manager` orchestrates the I/O of a set of connections. -pub struct Manager { - /// The tasks of the managed connections. - /// - /// Each managed connection is associated with a (background) task - /// spawned onto an executor. Each `TaskInfo` in `tasks` is linked to such a - /// background task via a channel. Closing that channel (i.e. dropping - /// the sender in the associated `TaskInfo`) stops the background task, - /// which will attempt to gracefully close the connection. - tasks: FnvHashMap>>, - - /// Next available identifier for a new connection / task. - next_task_id: TaskId, - - /// Size of the task command buffer (per task). - task_command_buffer_size: usize, - - /// The executor to use for running the background tasks. If `None`, - /// the tasks are kept in `local_spawns` instead and polled on the - /// current thread when the manager is polled for new events. - executor: Option>, - - /// If no `executor` is configured, tasks are kept in this set and - /// polled on the current thread when the manager is polled for new events. - local_spawns: FuturesUnordered + Send>>>, - - /// Sender distributed to managed tasks for reporting events back - /// to the manager. - events_tx: mpsc::Sender>, - - /// Receiver for events reported from managed tasks. - events_rx: mpsc::Receiver>, -} - -impl fmt::Debug for Manager { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_map() - .entries(self.tasks.iter().map(|(id, task)| (id, &task.state))) - .finish() - } -} - -/// Configuration options when creating a [`Manager`]. -/// -/// The default configuration specifies no dedicated task executor, a -/// task event buffer size of 32, and a task command buffer size of 7. -#[non_exhaustive] -pub struct ManagerConfig { - /// Executor to use to spawn tasks. - pub executor: Option>, - - /// Size of the task command buffer (per task). - pub task_command_buffer_size: usize, - - /// Size of the task event buffer (for all tasks). - pub task_event_buffer_size: usize, -} - -impl Default for ManagerConfig { - fn default() -> Self { - ManagerConfig { - executor: None, - task_event_buffer_size: 32, - task_command_buffer_size: 7, - } - } -} - -/// Internal information about a running task. -/// -/// Contains the sender to deliver event messages to the task, and -/// the associated user data. -#[derive(Debug)] -struct TaskInfo { - /// Channel endpoint to send messages to the task. - sender: mpsc::Sender>, - /// The state of the task as seen by the `Manager`. - state: TaskState, -} - -/// Internal state of a running task as seen by the `Manager`. -#[derive(Debug, Clone, PartialEq, Eq)] -enum TaskState { - /// The connection is being established. - Pending, - /// The connection is established. - Established(Connected), -} - -/// Events produced by the [`Manager`]. -#[derive(Debug)] -pub enum Event<'a, H: IntoConnectionHandler, TE> { - /// A connection attempt has failed. - PendingConnectionError { - /// The connection ID. - /// - /// As a result of the error, the pending connection has been removed - /// from the `Manager` and is being closed. Hence this ID will - /// no longer resolve to a valid entry in the manager. - id: ConnectionId, - /// What happened. - error: PendingConnectionError, - /// The handler that was supposed to handle the failed connection. - handler: H, - }, - - /// An established connection has been closed. - ConnectionClosed { - /// The connection ID. - /// - /// > **Note**: Closed connections are removed from the `Manager`. - /// > Hence this ID will no longer resolve to a valid entry in - /// > the manager. - id: ConnectionId, - /// Information about the closed connection. - connected: Connected, - /// The error that occurred, if any. If `None`, the connection - /// has been actively closed. - error: Option>>, - handler: H::Handler, - }, - - /// A connection has been established. - ConnectionEstablished { - /// The entry associated with the new connection. - entry: EstablishedEntry<'a, THandlerInEvent>, - }, - - /// A connection handler has produced an event. - ConnectionEvent { - /// The entry associated with the connection that produced the event. - entry: EstablishedEntry<'a, THandlerInEvent>, - /// The produced event. - event: THandlerOutEvent, - }, - - /// A connection to a node has changed its address. - AddressChange { - /// The entry associated with the connection that changed address. - entry: EstablishedEntry<'a, THandlerInEvent>, - /// The former [`ConnectedPoint`]. - old_endpoint: ConnectedPoint, - /// The new [`ConnectedPoint`]. - new_endpoint: ConnectedPoint, - }, -} - -impl Manager { - /// Creates a new connection manager. - pub fn new(config: ManagerConfig) -> Self { - let (tx, rx) = mpsc::channel(config.task_event_buffer_size); - Self { - tasks: FnvHashMap::default(), - next_task_id: TaskId(0), - task_command_buffer_size: config.task_command_buffer_size, - executor: config.executor, - local_spawns: FuturesUnordered::new(), - events_tx: tx, - events_rx: rx, - } - } - - /// Adds to the manager a future that tries to reach a node. - /// - /// This method spawns a task dedicated to resolving this future and - /// processing the node's events. - pub fn add_pending(&mut self, future: F, handler: H) -> ConnectionId - where - TE: error::Error + Send + 'static, - M: StreamMuxer + Send + Sync + 'static, - M::OutboundSubstream: Send + 'static, - F: Future> + Send + 'static, - H: IntoConnectionHandler + Send + 'static, - H::Handler: ConnectionHandler> + Send + 'static, - ::OutboundOpenInfo: Send + 'static, - { - let task_id = self.next_task_id; - self.next_task_id.0 += 1; - - let (tx, rx) = mpsc::channel(self.task_command_buffer_size); - self.tasks.insert( - task_id, - TaskInfo { - sender: tx, - state: TaskState::Pending, - }, - ); - - let task = Box::pin(Task::pending( - task_id, - self.events_tx.clone(), - rx, - future, - handler, - )); - if let Some(executor) = &mut self.executor { - executor.exec(task); - } else { - self.local_spawns.push(task); - } - - ConnectionId(task_id) - } - - /// Gets an entry for a managed connection, if it exists. - pub fn entry(&mut self, id: ConnectionId) -> Option>> { - if let hash_map::Entry::Occupied(task) = self.tasks.entry(id.0) { - Some(Entry::new(task)) - } else { - None - } - } - - /// Checks whether an established connection with the given ID is currently managed. - pub fn is_established(&self, id: &ConnectionId) -> bool { - matches!( - self.tasks.get(&id.0), - Some(TaskInfo { - state: TaskState::Established(..), - .. - }) - ) - } - - /// Polls the manager for events relating to the managed connections. - pub fn poll<'a>(&'a mut self, cx: &mut Context<'_>) -> Poll> { - // Advance the content of `local_spawns`. - while let Poll::Ready(Some(_)) = self.local_spawns.poll_next_unpin(cx) {} - - // Poll for the first event for which the manager still has a registered task, if any. - let event = loop { - match self.events_rx.poll_next_unpin(cx) { - Poll::Ready(Some(event)) => { - if self.tasks.contains_key(event.id()) { - // (1) - break event; - } - } - Poll::Pending => return Poll::Pending, - Poll::Ready(None) => unreachable!("Manager holds both sender and receiver."), - } - }; - - if let hash_map::Entry::Occupied(mut task) = self.tasks.entry(*event.id()) { - Poll::Ready(match event { - task::Event::Notify { id: _, event } => Event::ConnectionEvent { - entry: EstablishedEntry { task }, - event, - }, - task::Event::Established { id: _, info } => { - // (2) - task.get_mut().state = TaskState::Established(info); // (3) - Event::ConnectionEstablished { - entry: EstablishedEntry { task }, - } - } - task::Event::Failed { id, error, handler } => { - let id = ConnectionId(id); - let _ = task.remove(); - Event::PendingConnectionError { id, error, handler } - } - task::Event::AddressChange { id: _, new_address } => { - let (new, old) = if let TaskState::Established(c) = &mut task.get_mut().state { - let mut new_endpoint = c.endpoint.clone(); - new_endpoint.set_remote_address(new_address); - let old_endpoint = mem::replace(&mut c.endpoint, new_endpoint.clone()); - (new_endpoint, old_endpoint) - } else { - unreachable!( - "`Event::AddressChange` implies (2) occurred on that task and thus (3)." - ) - }; - Event::AddressChange { - entry: EstablishedEntry { task }, - old_endpoint: old, - new_endpoint: new, - } - } - task::Event::Closed { id, error, handler } => { - let id = ConnectionId(id); - let task = task.remove(); - match task.state { - TaskState::Established(connected) => Event::ConnectionClosed { - id, - connected, - error, - handler, - }, - TaskState::Pending => unreachable!( - "`Event::Closed` implies (2) occurred on that task and thus (3)." - ), - } - } - }) - } else { - unreachable!("By (1)") - } - } -} - -/// An entry for a connection in the manager. -#[derive(Debug)] -pub enum Entry<'a, I> { - Pending(PendingEntry<'a, I>), - Established(EstablishedEntry<'a, I>), -} - -impl<'a, I> Entry<'a, I> { - fn new(task: hash_map::OccupiedEntry<'a, TaskId, TaskInfo>) -> Self { - match &task.get().state { - TaskState::Pending => Entry::Pending(PendingEntry { task }), - TaskState::Established(_) => Entry::Established(EstablishedEntry { task }), - } - } -} - -/// An entry for a managed connection that is considered established. -#[derive(Debug)] -pub struct EstablishedEntry<'a, I> { - task: hash_map::OccupiedEntry<'a, TaskId, TaskInfo>, -} - -impl<'a, I> EstablishedEntry<'a, I> { - /// (Asynchronously) sends an event to the connection handler. - /// - /// If the handler is not ready to receive the event, either because - /// it is busy or the connection is about to close, the given event - /// is returned with an `Err`. - /// - /// If execution of this method is preceded by successful execution of - /// `poll_ready_notify_handler` without another intervening execution - /// of `notify_handler`, it only fails if the connection is now about - /// to close. - /// - /// > **Note**: As this method does not take a `Context`, the current - /// > task _may not be notified_ if sending the event fails due to - /// > the connection handler not being ready at this time. - pub fn notify_handler(&mut self, event: I) -> Result<(), I> { - let cmd = task::Command::NotifyHandler(event); // (*) - self.task - .get_mut() - .sender - .try_send(cmd) - .map_err(|e| match e.into_inner() { - task::Command::NotifyHandler(event) => event, - _ => panic!("Unexpected command. Expected `NotifyHandler`"), // see (*) - }) - } - - /// Checks if `notify_handler` is ready to accept an event. - /// - /// Returns `Ok(())` if the handler is ready to receive an event via `notify_handler`. - /// - /// Returns `Err(())` if the background task associated with the connection - /// is terminating and the connection is about to close. - pub fn poll_ready_notify_handler(&mut self, cx: &mut Context<'_>) -> Poll> { - self.task.get_mut().sender.poll_ready(cx).map_err(|_| ()) - } - - /// Sends a close command to the associated background task, - /// thus initiating a graceful active close of the connection. - /// - /// Has no effect if the connection is already closing. - /// - /// When the connection is ultimately closed, [`Event::ConnectionClosed`] - /// is emitted by [`Manager::poll`]. - pub fn start_close(mut self, error: Option) { - // Clone the sender so that we are guaranteed to have - // capacity for the close command (every sender gets a slot). - match self - .task - .get_mut() - .sender - .clone() - .try_send(task::Command::Close(error)) - { - Ok(()) => {} - Err(e) => assert!(e.is_disconnected(), "No capacity for close command."), - } - } - - /// Obtains information about the established connection. - pub fn connected(&self) -> &Connected { - match &self.task.get().state { - TaskState::Established(c) => c, - TaskState::Pending => unreachable!("By Entry::new()"), - } - } - - /// Returns the connection ID. - pub fn id(&self) -> ConnectionId { - ConnectionId(*self.task.key()) - } -} - -/// An entry for a managed connection that is currently being established -/// (i.e. pending). -#[derive(Debug)] -pub struct PendingEntry<'a, I> { - task: hash_map::OccupiedEntry<'a, TaskId, TaskInfo>, -} - -impl<'a, I> PendingEntry<'a, I> { - /// Returns the connection id. - pub fn id(&self) -> ConnectionId { - ConnectionId(*self.task.key()) - } - - /// Aborts the pending connection attempt. - pub fn abort(self) { - self.task.remove(); - } -} diff --git a/core/src/connection/manager/task.rs b/core/src/connection/manager/task.rs deleted file mode 100644 index 5860bcc6c9d..00000000000 --- a/core/src/connection/manager/task.rs +++ /dev/null @@ -1,395 +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 super::ConnectResult; -use crate::{ - connection::{ - self, - handler::{THandlerError, THandlerInEvent, THandlerOutEvent}, - Close, Connected, Connection, ConnectionError, ConnectionHandler, ConnectionLimit, - IntoConnectionHandler, PendingConnectionError, Substream, - }, - muxing::StreamMuxer, - Multiaddr, -}; -use futures::{channel::mpsc, prelude::*, stream}; -use std::{pin::Pin, task::Context, task::Poll}; - -/// Identifier of a [`Task`] in a [`Manager`](super::Manager). -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct TaskId(pub(super) usize); - -/// Commands that can be sent to a [`Task`]. -#[derive(Debug)] -pub enum Command { - /// Notify the connection handler of an event. - NotifyHandler(T), - /// Gracefully close the connection (active close) before - /// terminating the task. - Close(Option), -} - -/// Events that a task can emit to its manager. -#[derive(Debug)] -pub enum Event { - /// A connection to a node has succeeded. - Established { id: TaskId, info: Connected }, - /// A pending connection failed. - Failed { - id: TaskId, - error: PendingConnectionError, - handler: H, - }, - /// A node we are connected to has changed its address. - AddressChange { id: TaskId, new_address: Multiaddr }, - /// Notify the manager of an event from the connection. - Notify { - id: TaskId, - event: THandlerOutEvent, - }, - /// A connection closed, possibly due to an error. - /// - /// If `error` is `None`, the connection has completed - /// an active orderly close. - Closed { - id: TaskId, - error: Option>>, - handler: H::Handler, - }, -} - -impl Event { - pub fn id(&self) -> &TaskId { - match self { - Event::Established { id, .. } => id, - Event::Failed { id, .. } => id, - Event::AddressChange { id, .. } => id, - Event::Notify { id, .. } => id, - Event::Closed { id, .. } => id, - } - } -} - -/// A `Task` is a [`Future`] that handles a single connection. -pub struct Task -where - M: StreamMuxer, - H: IntoConnectionHandler, - H::Handler: ConnectionHandler>, -{ - /// The ID of this task. - id: TaskId, - - /// Sender to emit events to the manager of this task. - events: mpsc::Sender>, - - /// Receiver for commands sent by the manager of this task. - commands: stream::Fuse>>>, - - /// Inner state of this `Task`. - state: State, -} - -impl Task -where - M: StreamMuxer, - H: IntoConnectionHandler, - H::Handler: ConnectionHandler>, -{ - /// Create a new task to connect and handle some node. - pub fn pending( - id: TaskId, - events: mpsc::Sender>, - commands: mpsc::Receiver>>, - future: F, - handler: H, - ) -> Self { - Task { - id, - events, - commands: commands.fuse(), - state: State::Pending { - future: Box::pin(future), - handler, - }, - } - } -} - -/// The state associated with the `Task` of a connection. -enum State -where - M: StreamMuxer, - H: IntoConnectionHandler, - H::Handler: ConnectionHandler>, -{ - /// The connection is being negotiated. - Pending { - /// The future that will attempt to reach the node. - // TODO: don't pin this Future; this requires deeper changes though - future: Pin>, - /// The intended handler for the established connection. - handler: H, - }, - - /// The connection is established. - Established { - connection: Connection, - /// An event to send to the `Manager`. If `None`, the `connection` - /// is polled for new events in this state, otherwise the event - /// must be sent to the `Manager` before the connection can be - /// polled again. - event: Option>, - }, - - /// The connection is closing (active close). - Closing { - closing_muxer: Close, - handler: H::Handler, - error: Option, - }, - - /// The task is terminating with a final event for the `Manager`. - Terminating(Event), - - /// The task has finished. - Done, -} - -impl Unpin for Task -where - M: StreamMuxer, - H: IntoConnectionHandler, - H::Handler: ConnectionHandler>, -{ -} - -impl Future for Task -where - M: StreamMuxer, - F: Future>, - H: IntoConnectionHandler, - H::Handler: ConnectionHandler> + Send + 'static, -{ - type Output = (); - - // NOTE: It is imperative to always consume all incoming commands from - // the manager first, in order to not prevent it from making progress because - // it is blocked on the channel capacity. - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { - let this = &mut *self; - let id = this.id; - - 'poll: loop { - match std::mem::replace(&mut this.state, State::Done) { - State::Pending { - mut future, - handler, - } => { - // Check whether the task is still registered with a `Manager` - // by polling the commands channel. - match this.commands.poll_next_unpin(cx) { - Poll::Pending => {} - Poll::Ready(None) => { - // The manager has dropped the task; abort. - // Don't accept any further commands and terminate the - // task with a final event. - this.commands.get_mut().close(); - let event = Event::Failed { - id, - handler, - error: PendingConnectionError::Aborted, - }; - this.state = State::Terminating(event); - continue 'poll; - } - Poll::Ready(Some(_)) => { - panic!("Task received command while the connection is pending.") - } - } - // Check if the connection succeeded. - match future.poll_unpin(cx) { - Poll::Ready(Ok((info, muxer))) => { - this.state = State::Established { - connection: Connection::new(muxer, handler.into_handler(&info)), - event: Some(Event::Established { id, info }), - } - } - Poll::Pending => { - this.state = State::Pending { future, handler }; - return Poll::Pending; - } - Poll::Ready(Err(error)) => { - // Don't accept any further commands and terminate the - // task with a final event. - this.commands.get_mut().close(); - let event = Event::Failed { id, handler, error }; - this.state = State::Terminating(event) - } - } - } - - State::Established { - mut connection, - event, - } => { - // Check for commands from the `Manager`. - loop { - match this.commands.poll_next_unpin(cx) { - Poll::Pending => break, - Poll::Ready(Some(Command::NotifyHandler(event))) => { - connection.inject_event(event) - } - Poll::Ready(Some(Command::Close(error))) => { - // Don't accept any further commands. - this.commands.get_mut().close(); - // Discard the event, if any, and start a graceful close. - let (handler, closing_muxer) = connection.close(); - this.state = State::Closing { - handler, - closing_muxer, - error, - }; - continue 'poll; - } - Poll::Ready(None) => { - // The manager has disappeared; abort. - return Poll::Ready(()); - } - } - } - - if let Some(event) = event { - // Send the event to the manager. - match this.events.poll_ready(cx) { - Poll::Pending => { - this.state = State::Established { - connection, - event: Some(event), - }; - return Poll::Pending; - } - Poll::Ready(result) => { - if result.is_ok() { - if let Ok(()) = this.events.start_send(event) { - this.state = State::Established { - connection, - event: None, - }; - continue 'poll; - } - } - // The manager is no longer reachable; abort. - return Poll::Ready(()); - } - } - } else { - // Poll the connection for new events. - match Connection::poll(Pin::new(&mut connection), cx) { - Poll::Pending => { - this.state = State::Established { - connection, - event: None, - }; - return Poll::Pending; - } - Poll::Ready(Ok(connection::Event::Handler(event))) => { - this.state = State::Established { - connection, - event: Some(Event::Notify { id, event }), - }; - } - Poll::Ready(Ok(connection::Event::AddressChange(new_address))) => { - this.state = State::Established { - connection, - event: Some(Event::AddressChange { id, new_address }), - }; - } - Poll::Ready(Err(error)) => { - // Don't accept any further commands. - this.commands.get_mut().close(); - let (handler, _closing_muxer) = connection.close(); - // Terminate the task with the error, dropping the connection. - let event = Event::Closed { - id, - error: Some(error), - handler, - }; - this.state = State::Terminating(event); - } - } - } - } - - State::Closing { - handler, - error, - mut closing_muxer, - } => { - // Try to gracefully close the connection. - match closing_muxer.poll_unpin(cx) { - Poll::Ready(Ok(())) => { - let event = Event::Closed { - id: this.id, - error: error.map(ConnectionError::ConnectionLimit), - handler, - }; - this.state = State::Terminating(event); - } - Poll::Ready(Err(e)) => { - let event = Event::Closed { - id: this.id, - error: Some(ConnectionError::IO(e)), - handler, - }; - this.state = State::Terminating(event); - } - Poll::Pending => { - this.state = State::Closing { - handler, - error, - closing_muxer, - }; - return Poll::Pending; - } - } - } - - State::Terminating(event) => { - // Try to deliver the final event. - match this.events.poll_ready(cx) { - Poll::Pending => { - self.state = State::Terminating(event); - return Poll::Pending; - } - Poll::Ready(result) => { - if result.is_ok() { - let _ = this.events.start_send(event); - } - return Poll::Ready(()); - } - } - } - - State::Done => panic!("`Task::poll()` called after completion."), - } - } - } -} diff --git a/core/src/connection/pool.rs b/core/src/connection/pool.rs index f147f766d4d..163da2178f6 100644 --- a/core/src/connection/pool.rs +++ b/core/src/connection/pool.rs @@ -1,3 +1,4 @@ +// Copyright 2021 Protocol Labs. // Copyright 2018 Parity Technologies (UK) Ltd. // // Permission is hereby granted, free of charge, to any person obtaining a @@ -21,42 +22,125 @@ use crate::{ connection::{ handler::{THandlerError, THandlerInEvent, THandlerOutEvent}, - manager::{self, Manager, ManagerConfig}, Connected, ConnectionError, ConnectionHandler, ConnectionId, ConnectionLimit, IncomingInfo, - IntoConnectionHandler, OutgoingInfo, PendingConnectionError, Substream, + IntoConnectionHandler, PendingConnectionError, PendingInboundConnectionError, + PendingOutboundConnectionError, PendingPoint, Substream, }, muxing::StreamMuxer, network::DialError, - ConnectedPoint, PeerId, + transport::{Transport, TransportError}, + ConnectedPoint, Executor, Multiaddr, PeerId, }; -use either::Either; +use concurrent_dial::ConcurrentDial; use fnv::FnvHashMap; use futures::prelude::*; +use futures::{ + channel::{mpsc, oneshot}, + future::{poll_fn, BoxFuture, Either}, + ready, + stream::FuturesUnordered, +}; use smallvec::SmallVec; -use std::{convert::TryFrom as _, error, fmt, num::NonZeroU32, task::Context, task::Poll}; +use std::{ + collections::{hash_map, HashMap}, + convert::TryFrom as _, + fmt, + num::{NonZeroU32, NonZeroU8}, + pin::Pin, + task::Context, + task::Poll, +}; +use void::Void; + +mod concurrent_dial; +mod task; /// A connection `Pool` manages a set of connections for each peer. -pub struct Pool { +pub struct Pool +where + TTrans: Transport, +{ local_id: PeerId, /// The connection counter(s). counters: ConnectionCounters, - /// The connection manager that handles the connection I/O for both - /// established and pending connections. - /// - /// For every established connection there is a corresponding entry in `established`. - manager: Manager, - - /// The managed connections of each peer that are currently considered - /// established, as witnessed by the associated `ConnectedPoint`. - established: FnvHashMap>, + /// The managed connections of each peer that are currently considered established. + established: FnvHashMap< + PeerId, + FnvHashMap>>, + >, /// The pending connections that are currently being negotiated. - pending: FnvHashMap)>, + pending: HashMap>, + + /// Next available identifier for a new connection / task. + next_connection_id: ConnectionId, + + /// Size of the task command buffer (per task). + task_command_buffer_size: usize, + + /// Number of addresses concurrently dialed for a single outbound connection attempt. + dial_concurrency_factor: NonZeroU8, + + /// The executor to use for running the background tasks. If `None`, + /// the tasks are kept in `local_spawns` instead and polled on the + /// current thread when the [`Pool`] is polled for new events. + executor: Option>, + + /// If no `executor` is configured, tasks are kept in this set and + /// polled on the current thread when the [`Pool`] is polled for new events. + local_spawns: FuturesUnordered + Send>>>, + + /// Sender distributed to pending tasks for reporting events back + /// to the pool. + pending_connection_events_tx: mpsc::Sender>, + + /// Receiver for events reported from pending tasks. + pending_connection_events_rx: mpsc::Receiver>, + + /// Sender distributed to established tasks for reporting events back + /// to the pool. + established_connection_events_tx: mpsc::Sender>, + + /// Receiver for events reported from established tasks. + established_connection_events_rx: mpsc::Receiver>, +} + +#[derive(Debug)] +struct EstablishedConnectionInfo { + /// [`PeerId`] of the remote peer. + peer_id: PeerId, + endpoint: ConnectedPoint, + /// Channel endpoint to send commands to the task. + sender: mpsc::Sender>, } -impl fmt::Debug for Pool { +impl EstablishedConnectionInfo { + /// Initiates a graceful close of the connection. + /// + /// Has no effect if the connection is already closing. + pub fn start_close(&mut self) { + // Clone the sender so that we are guaranteed to have + // capacity for the close command (every sender gets a slot). + match self.sender.clone().try_send(task::Command::Close) { + Ok(()) => {} + Err(e) => assert!(e.is_disconnected(), "No capacity for close command."), + }; + } +} + +struct PendingConnectionInfo { + /// [`PeerId`] of the remote peer. + peer_id: Option, + /// Handler to handle connection once no longer pending but established. + handler: THandler, + endpoint: PendingPoint, + /// When dropped, notifies the task which then knows to terminate. + _drop_notifier: oneshot::Sender, +} + +impl fmt::Debug for Pool { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { f.debug_struct("Pool") .field("counters", &self.counters) @@ -64,14 +148,19 @@ impl fmt::Debug for Pool Unpin for Pool {} - /// Event that can happen on the `Pool`. -pub enum PoolEvent<'a, THandler: IntoConnectionHandler, TTransErr> { +pub enum PoolEvent<'a, THandler: IntoConnectionHandler, TTrans> +where + TTrans: Transport, +{ /// A new connection has been established. ConnectionEstablished { connection: EstablishedConnection<'a, THandlerInEvent>, num_established: NonZeroU32, + /// [`Some`] when the new connection is an outgoing connection. + /// Addresses are dialed in parallel. Contains the addresses and errors + /// of dial attempts that failed before the one successful dial. + concurrent_dial_errors: Option)>>, }, /// An established connection was closed. @@ -93,27 +182,36 @@ pub enum PoolEvent<'a, THandler: IntoConnectionHandler, TTransErr> { /// was closed by the local peer. error: Option>>, /// A reference to the pool that used to manage the connection. - pool: &'a mut Pool, + pool: &'a mut Pool, /// The remaining number of established connections to the same peer. num_established: u32, handler: THandler::Handler, }, - /// A connection attempt failed. - PendingConnectionError { + /// An outbound connection attempt failed. + PendingOutboundConnectionError { /// The ID of the failed connection. id: ConnectionId, - /// The local endpoint of the failed connection. - endpoint: ConnectedPoint, /// The error that occurred. - error: PendingConnectionError, - /// The handler that was supposed to handle the connection, - /// if the connection failed before the handler was consumed. + error: PendingOutboundConnectionError, + /// The handler that was supposed to handle the connection. handler: THandler, /// The (expected) peer of the failed connection. peer: Option, - /// A reference to the pool that managed the connection. - pool: &'a mut Pool, + }, + + /// An inbound connection attempt failed. + PendingInboundConnectionError { + /// The ID of the failed connection. + id: ConnectionId, + /// Address used to send back data to the remote. + send_back_addr: Multiaddr, + /// Local connection address. + local_addr: Multiaddr, + /// The error that occurred. + error: PendingInboundConnectionError, + /// The handler that was supposed to handle the connection. + handler: THandler, }, /// A node has produced an event. @@ -135,21 +233,26 @@ pub enum PoolEvent<'a, THandler: IntoConnectionHandler, TTransErr> { }, } -impl<'a, THandler: IntoConnectionHandler, TTransErr> fmt::Debug - for PoolEvent<'a, THandler, TTransErr> +impl<'a, THandler: IntoConnectionHandler, TTrans> fmt::Debug for PoolEvent<'a, THandler, TTrans> where - TTransErr: fmt::Debug, + TTrans: Transport, + TTrans::Error: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - match *self { - PoolEvent::ConnectionEstablished { ref connection, .. } => f + match self { + PoolEvent::ConnectionEstablished { + connection, + concurrent_dial_errors, + .. + } => f .debug_tuple("PoolEvent::ConnectionEstablished") .field(connection) + .field(concurrent_dial_errors) .finish(), PoolEvent::ConnectionClosed { - ref id, - ref connected, - ref error, + id, + connected, + error, .. } => f .debug_struct("PoolEvent::ConnectionClosed") @@ -157,25 +260,36 @@ where .field("connected", connected) .field("error", error) .finish(), - PoolEvent::PendingConnectionError { - ref id, ref error, .. + PoolEvent::PendingOutboundConnectionError { + id, error, peer, .. } => f - .debug_struct("PoolEvent::PendingConnectionError") + .debug_struct("PoolEvent::PendingOutboundConnectionError") .field("id", id) .field("error", error) + .field("peer", peer) .finish(), - PoolEvent::ConnectionEvent { - ref connection, - ref event, + PoolEvent::PendingInboundConnectionError { + id, + error, + send_back_addr, + local_addr, + .. } => f + .debug_struct("PoolEvent::PendingInboundConnectionError") + .field("id", id) + .field("error", error) + .field("send_back_addr", send_back_addr) + .field("local_addr", local_addr) + .finish(), + PoolEvent::ConnectionEvent { connection, event } => f .debug_struct("PoolEvent::ConnectionEvent") .field("peer", &connection.peer_id()) .field("event", event) .finish(), PoolEvent::AddressChange { - ref connection, - ref new_endpoint, - ref old_endpoint, + connection, + new_endpoint, + old_endpoint, } => f .debug_struct("PoolEvent::AddressChange") .field("peer", &connection.peer_id()) @@ -186,15 +300,31 @@ where } } -impl Pool { +impl Pool +where + THandler: IntoConnectionHandler, + TTrans: Transport, +{ /// Creates a new empty `Pool`. - pub fn new(local_id: PeerId, manager_config: ManagerConfig, limits: ConnectionLimits) -> Self { + pub fn new(local_id: PeerId, config: PoolConfig, limits: ConnectionLimits) -> Self { + let (pending_connection_events_tx, pending_connection_events_rx) = + mpsc::channel(config.task_event_buffer_size); + let (established_connection_events_tx, established_connection_events_rx) = + mpsc::channel(config.task_event_buffer_size); Pool { local_id, counters: ConnectionCounters::new(limits), - manager: Manager::new(manager_config), established: Default::default(), pending: Default::default(), + next_connection_id: ConnectionId(0), + task_command_buffer_size: config.task_command_buffer_size, + dial_concurrency_factor: config.dial_concurrency_factor, + executor: config.executor, + local_spawns: FuturesUnordered::new(), + pending_connection_events_tx, + pending_connection_events_rx, + established_connection_events_tx, + established_connection_events_rx, } } @@ -203,131 +333,24 @@ impl Pool { &self.counters } - /// Adds a pending incoming connection to the pool in the form of a - /// `Future` that establishes and negotiates the connection. - /// - /// Returns an error if the limit of pending incoming connections - /// has been reached. - pub fn add_incoming( - &mut self, - future: TFut, - handler: THandler, - info: IncomingInfo<'_>, - ) -> Result - where - TFut: Future>> - + Send - + 'static, - THandler: IntoConnectionHandler + Send + 'static, - THandler::Handler: ConnectionHandler> + Send + 'static, - ::OutboundOpenInfo: Send + 'static, - TTransErr: error::Error + Send + 'static, - TMuxer: StreamMuxer + Send + Sync + 'static, - TMuxer::OutboundSubstream: Send + 'static, - { - self.counters.check_max_pending_incoming()?; - let endpoint = info.to_connected_point(); - Ok(self.add_pending(future, handler, endpoint, None)) - } - - /// Adds a pending outgoing connection to the pool in the form of a `Future` - /// that establishes and negotiates the connection. - /// - /// Returns an error if the limit of pending outgoing connections - /// has been reached. - pub fn add_outgoing( - &mut self, - future: TFut, - handler: THandler, - info: OutgoingInfo<'_>, - ) -> Result> - where - TFut: Future>> - + Send - + 'static, - THandler: IntoConnectionHandler + Send + 'static, - THandler::Handler: ConnectionHandler> + Send + 'static, - ::OutboundOpenInfo: Send + 'static, - TTransErr: error::Error + Send + 'static, - TMuxer: StreamMuxer + Send + Sync + 'static, - TMuxer::OutboundSubstream: Send + 'static, - { - if let Err(limit) = self.counters.check_max_pending_outgoing() { - return Err(DialError::ConnectionLimit { limit, handler }); - }; - let endpoint = info.to_connected_point(); - Ok(self.add_pending(future, handler, endpoint, info.peer_id.cloned())) - } - - /// Adds a pending connection to the pool in the form of a - /// `Future` that establishes and negotiates the connection. - fn add_pending( - &mut self, - future: TFut, - handler: THandler, - endpoint: ConnectedPoint, - peer: Option, - ) -> ConnectionId - where - TFut: Future>> - + Send - + 'static, - THandler: IntoConnectionHandler + Send + 'static, - THandler::Handler: ConnectionHandler> + Send + 'static, - ::OutboundOpenInfo: Send + 'static, - TTransErr: error::Error + Send + 'static, - TMuxer: StreamMuxer + Send + Sync + 'static, - TMuxer::OutboundSubstream: Send + 'static, - { - // Validate the received peer ID as the last step of the pending connection - // future, so that these errors can be raised before the `handler` is consumed - // by the background task, which happens when this future resolves to an - // "established" connection. - let future = future.and_then({ - let endpoint = endpoint.clone(); - let expected_peer = peer; - let local_id = self.local_id; - move |(peer_id, muxer)| { - if let Some(peer) = expected_peer { - if peer != peer_id { - return future::err(PendingConnectionError::InvalidPeerId); - } - } - - if local_id == peer_id { - return future::err(PendingConnectionError::InvalidPeerId); - } - - let connected = Connected { peer_id, endpoint }; - future::ready(Ok((connected, muxer))) - } - }); - - let id = self.manager.add_pending(future, handler); - self.counters.inc_pending(&endpoint); - self.pending.insert(id, (endpoint, peer)); - id - } - /// Gets an entry representing a connection in the pool. /// /// Returns `None` if the pool has no connection with the given ID. - pub fn get( - &mut self, - id: ConnectionId, - ) -> Option>> { - match self.manager.entry(id) { - Some(manager::Entry::Established(entry)) => { - Some(PoolConnection::Established(EstablishedConnection { entry })) - } - Some(manager::Entry::Pending(entry)) => { - Some(PoolConnection::Pending(PendingConnection { - entry, - pending: &mut self.pending, - counters: &mut self.counters, - })) - } - None => None, + pub fn get(&mut self, id: ConnectionId) -> Option> { + if let hash_map::Entry::Occupied(entry) = self.pending.entry(id) { + Some(PoolConnection::Pending(PendingConnection { + entry, + counters: &mut self.counters, + })) + } else { + self.established + .iter_mut() + .find_map(|(_, cs)| match cs.entry(id) { + hash_map::Entry::Occupied(entry) => { + Some(PoolConnection::Established(EstablishedConnection { entry })) + } + hash_map::Entry::Vacant(_) => None, + }) } } @@ -343,20 +366,13 @@ impl Pool { } /// Gets a pending outgoing connection by ID. - pub fn get_outgoing( - &mut self, - id: ConnectionId, - ) -> Option>> { - match self.pending.get(&id) { - Some((ConnectedPoint::Dialer { .. }, _peer)) => match self.manager.entry(id) { - Some(manager::Entry::Pending(entry)) => Some(PendingConnection { - entry, - pending: &mut self.pending, - counters: &mut self.counters, - }), - _ => unreachable!("by consistency of `self.pending` with `self.manager`"), - }, - _ => None, + pub fn get_outgoing(&mut self, id: ConnectionId) -> Option> { + match self.pending.entry(id) { + hash_map::Entry::Occupied(entry) => Some(PendingConnection { + entry, + counters: &mut self.counters, + }), + hash_map::Entry::Vacant(_) => None, } } @@ -379,25 +395,36 @@ impl Pool { /// closed asap and no more events from these connections are emitted /// by the pool effective immediately. pub fn disconnect(&mut self, peer: &PeerId) { - if let Some(conns) = self.established.get(peer) { - for (&id, _endpoint) in conns.iter() { - if let Some(manager::Entry::Established(e)) = self.manager.entry(id) { - e.start_close(None); - } + if let Some(conns) = self.established.get_mut(peer) { + for (_, conn) in conns.iter_mut() { + conn.start_close(); } } - for (&id, (_endpoint, peer2)) in &self.pending { - if Some(peer) == peer2.as_ref() { - if let Some(manager::Entry::Pending(e)) = self.manager.entry(id) { - e.abort(); - } + #[allow(clippy::needless_collect)] + let pending_connections = self + .pending + .iter() + .filter(|(_, PendingConnectionInfo { peer_id, .. })| peer_id.as_ref() == Some(peer)) + .map(|(id, _)| *id) + .collect::>(); + + for pending_connection in pending_connections { + let entry = self + .pending + .entry(pending_connection) + .expect_occupied("Iterating pending connections"); + + PendingConnection { + entry, + counters: &mut self.counters, } + .abort(); } } /// Counts the number of established connections to the given peer. - pub fn num_peer_established(&self, peer: &PeerId) -> u32 { + pub fn num_peer_established(&self, peer: PeerId) -> u32 { num_peer_established(&self.established, peer) } @@ -405,7 +432,7 @@ impl Pool { pub fn iter_peer_established<'a>( &'a mut self, peer: &PeerId, - ) -> EstablishedConnectionIter<'a, impl Iterator, THandler, TTransErr> + ) -> EstablishedConnectionIter<'a, impl Iterator, THandlerInEvent> { let ids = self .iter_peer_established_info(peer) @@ -413,33 +440,24 @@ impl Pool { .collect::>() .into_iter(); - EstablishedConnectionIter { pool: self, ids } + EstablishedConnectionIter { + connections: self.established.get_mut(peer), + ids, + } } /// Returns an iterator for information on all pending incoming connections. pub fn iter_pending_incoming(&self) -> impl Iterator> { self.iter_pending_info() .filter_map(|(_, ref endpoint, _)| match endpoint { - ConnectedPoint::Listener { + PendingPoint::Listener { local_addr, send_back_addr, } => Some(IncomingInfo { local_addr, send_back_addr, }), - ConnectedPoint::Dialer { .. } => None, - }) - } - - /// Returns an iterator for information on all pending outgoing connections. - pub fn iter_pending_outgoing(&self) -> impl Iterator> { - self.iter_pending_info() - .filter_map(|(_, ref endpoint, peer_id)| match endpoint { - ConnectedPoint::Listener { .. } => None, - ConnectedPoint::Dialer { address } => Some(OutgoingInfo { - address, - peer_id: peer_id.as_ref(), - }), + PendingPoint::Dialer => None, }) } @@ -448,10 +466,14 @@ impl Pool { pub fn iter_peer_established_info( &self, peer: &PeerId, - ) -> impl Iterator + fmt::Debug + '_ { + ) -> impl Iterator { match self.established.get(peer) { - Some(conns) => Either::Left(conns.iter()), - None => Either::Right(std::iter::empty()), + Some(conns) => either::Either::Left( + conns + .iter() + .map(|(id, EstablishedConnectionInfo { endpoint, .. })| (id, endpoint)), + ), + None => either::Either::Right(std::iter::empty()), } } @@ -459,10 +481,15 @@ impl Pool { /// with associated endpoints and expected peer IDs in the pool. pub fn iter_pending_info( &self, - ) -> impl Iterator)> + '_ { - self.pending - .iter() - .map(|(id, (endpoint, info))| (id, endpoint, info)) + ) -> impl Iterator)> + '_ { + self.pending.iter().map( + |( + id, + PendingConnectionInfo { + peer_id, endpoint, .. + }, + )| (id, endpoint, peer_id), + ) } /// Returns an iterator over all connected peers, i.e. those that have @@ -471,206 +498,489 @@ impl Pool { self.established.keys() } + fn next_connection_id(&mut self) -> ConnectionId { + let connection_id = self.next_connection_id; + self.next_connection_id.0 += 1; + + connection_id + } + + fn spawn(&mut self, task: BoxFuture<'static, ()>) { + if let Some(executor) = &mut self.executor { + executor.exec(task); + } else { + self.local_spawns.push(task); + } + } +} + +impl Pool +where + THandler: IntoConnectionHandler, + TTrans: Transport + 'static, + TTrans::Output: Send + 'static, + TTrans::Error: Send + 'static, +{ + /// Adds a pending outgoing connection to the pool in the form of a `Future` + /// that establishes and negotiates the connection. + /// + /// Returns an error if the limit of pending outgoing connections + /// has been reached. + pub fn add_outgoing( + &mut self, + transport: TTrans, + addresses: impl Iterator + Send + 'static, + peer: Option, + handler: THandler, + ) -> Result> + where + TTrans: Clone + Send, + TTrans::Dial: Send + 'static, + { + if let Err(limit) = self.counters.check_max_pending_outgoing() { + return Err(DialError::ConnectionLimit { limit, handler }); + }; + + let dial = ConcurrentDial::new(transport, peer, addresses, self.dial_concurrency_factor); + + let connection_id = self.next_connection_id(); + + let (drop_notifier, drop_receiver) = oneshot::channel(); + + self.spawn( + task::new_for_pending_outgoing_connection( + connection_id, + dial, + drop_receiver, + self.pending_connection_events_tx.clone(), + ) + .boxed(), + ); + + self.counters.inc_pending(&PendingPoint::Dialer); + self.pending.insert( + connection_id, + PendingConnectionInfo { + peer_id: peer, + handler, + endpoint: PendingPoint::Dialer, + _drop_notifier: drop_notifier, + }, + ); + Ok(connection_id) + } + + /// Adds a pending incoming connection to the pool in the form of a + /// `Future` that establishes and negotiates the connection. + /// + /// Returns an error if the limit of pending incoming connections + /// has been reached. + pub fn add_incoming( + &mut self, + future: TFut, + handler: THandler, + info: IncomingInfo<'_>, + ) -> Result + where + TFut: Future> + Send + 'static, + { + let endpoint = info.to_connected_point(); + + if let Err(limit) = self.counters.check_max_pending_incoming() { + return Err((limit, handler)); + } + + let connection_id = self.next_connection_id(); + + let (drop_notifier, drop_receiver) = oneshot::channel(); + + self.spawn( + task::new_for_pending_incoming_connection( + connection_id, + future, + drop_receiver, + self.pending_connection_events_tx.clone(), + ) + .boxed(), + ); + + self.counters.inc_pending_incoming(); + self.pending.insert( + connection_id, + PendingConnectionInfo { + peer_id: None, + handler, + endpoint: endpoint.into(), + _drop_notifier: drop_notifier, + }, + ); + Ok(connection_id) + } /// Polls the connection pool for events. /// /// > **Note**: We use a regular `poll` method instead of implementing `Stream`, /// > because we want the `Pool` to stay borrowed if necessary. - pub fn poll<'a>( + pub fn poll<'a, TMuxer>( &'a mut self, cx: &mut Context<'_>, - ) -> Poll> { - // Poll the connection `Manager`. - loop { - let item = match self.manager.poll(cx) { - Poll::Ready(item) => item, - Poll::Pending => return Poll::Pending, - }; - - match item { - manager::Event::PendingConnectionError { id, error, handler } => { - if let Some((endpoint, peer)) = self.pending.remove(&id) { - self.counters.dec_pending(&endpoint); - return Poll::Ready(PoolEvent::PendingConnectionError { - id, - endpoint, - error, - handler, - peer, - pool: self, - }); + ) -> Poll> + where + TTrans: Transport, + TMuxer: StreamMuxer + Send + Sync + 'static, + TMuxer::Error: std::fmt::Debug, + TMuxer::OutboundSubstream: Send, + THandler: IntoConnectionHandler + 'static, + THandler::Handler: ConnectionHandler> + Send, + ::OutboundOpenInfo: Send, + { + // Poll for events of established connections. + // + // Note that established connections are polled before pending connections, thus + // prioritizing established connections over pending connections. + match self.established_connection_events_rx.poll_next_unpin(cx) { + Poll::Pending => {} + Poll::Ready(None) => unreachable!("Pool holds both sender and receiver."), + + Poll::Ready(Some(task::EstablishedConnectionEvent::Notify { id, peer_id, event })) => { + let entry = self + .established + .get_mut(&peer_id) + .expect("Receive `Notify` event for established peer.") + .entry(id) + .expect_occupied("Receive `Notify` event from established connection"); + return Poll::Ready(PoolEvent::ConnectionEvent { + connection: EstablishedConnection { entry }, + event, + }); + } + Poll::Ready(Some(task::EstablishedConnectionEvent::AddressChange { + id, + peer_id, + new_address, + })) => { + let connection = self + .established + .get_mut(&peer_id) + .expect("Receive `AddressChange` event for established peer.") + .get_mut(&id) + .expect("Receive `AddressChange` event from established connection"); + let mut new_endpoint = connection.endpoint.clone(); + new_endpoint.set_remote_address(new_address); + let old_endpoint = + std::mem::replace(&mut connection.endpoint, new_endpoint.clone()); + + match self.get(id) { + Some(PoolConnection::Established(connection)) => { + return Poll::Ready(PoolEvent::AddressChange { + connection, + new_endpoint, + old_endpoint, + }) } + _ => unreachable!("since `entry` is an `EstablishedEntry`."), } - manager::Event::ConnectionClosed { + } + Poll::Ready(Some(task::EstablishedConnectionEvent::Closed { + id, + peer_id, + error, + handler, + })) => { + let connections = self + .established + .get_mut(&peer_id) + .expect("`Closed` event for established connection"); + let EstablishedConnectionInfo { endpoint, .. } = + connections.remove(&id).expect("Connection to be present"); + self.counters.dec_established(&endpoint); + let num_established = u32::try_from(connections.len()).unwrap(); + if num_established == 0 { + self.established.remove(&peer_id); + } + return Poll::Ready(PoolEvent::ConnectionClosed { id, - connected, + connected: Connected { endpoint, peer_id }, error, + num_established, + pool: self, handler, + }); + } + } + + // Poll for events of pending connections. + loop { + let event = match self.pending_connection_events_rx.poll_next_unpin(cx) { + Poll::Ready(Some(event)) => event, + Poll::Pending => break, + Poll::Ready(None) => unreachable!("Pool holds both sender and receiver."), + }; + + match event { + task::PendingConnectionEvent::ConnectionEstablished { + id, + output: (peer_id, muxer), + outgoing, } => { - let num_established = - if let Some(conns) = self.established.get_mut(&connected.peer_id) { - if let Some(endpoint) = conns.remove(&id) { - self.counters.dec_established(&endpoint); - } - u32::try_from(conns.len()).unwrap() - } else { - 0 - }; - if num_established == 0 { - self.established.remove(&connected.peer_id); - } - return Poll::Ready(PoolEvent::ConnectionClosed { - id, - connected, - error, - num_established, - pool: self, + let PendingConnectionInfo { + peer_id: expected_peer_id, handler, - }); - } - manager::Event::ConnectionEstablished { entry } => { - let id = entry.id(); - if let Some((endpoint, peer)) = self.pending.remove(&id) { - self.counters.dec_pending(&endpoint); - - // Check general established connection limit. - if let Err(e) = self.counters.check_max_established(&endpoint) { - entry.start_close(Some(e)); - continue; + endpoint, + _drop_notifier, + } = self + .pending + .remove(&id) + .expect("Entry in `self.pending` for previously pending connection."); + + self.counters.dec_pending(&endpoint); + + let (endpoint, concurrent_dial_errors) = match (endpoint, outgoing) { + (PendingPoint::Dialer, Some((address, errors))) => { + (ConnectedPoint::Dialer { address }, Some(errors)) } + ( + PendingPoint::Listener { + local_addr, + send_back_addr, + }, + None, + ) => ( + ConnectedPoint::Listener { + local_addr, + send_back_addr, + }, + None, + ), + (PendingPoint::Dialer, None) => unreachable!( + "Established incoming connection via pending outgoing connection." + ), + (PendingPoint::Listener { .. }, Some(_)) => unreachable!( + "Established outgoing connection via pending incoming connection." + ), + }; - // Check per-peer established connection limit. - let current = - num_peer_established(&self.established, &entry.connected().peer_id); - if let Err(e) = self.counters.check_max_established_per_peer(current) { - entry.start_close(Some(e)); - continue; - } + enum Error { + ConnectionLimit(ConnectionLimit), + InvalidPeerId, + } - // Peer ID checks must already have happened. See `add_pending`. - if cfg!(debug_assertions) { - if self.local_id == entry.connected().peer_id { - panic!("Unexpected local peer ID for remote."); - } - if let Some(peer) = peer { - if peer != entry.connected().peer_id { - panic!("Unexpected peer ID mismatch."); + impl From for PendingConnectionError { + fn from(error: Error) -> Self { + match error { + Error::ConnectionLimit(limit) => { + PendingConnectionError::ConnectionLimit(limit) } + Error::InvalidPeerId => PendingConnectionError::InvalidPeerId, } } + } - // Add the connection to the pool. - let peer = entry.connected().peer_id; - let conns = self.established.entry(peer).or_default(); - let num_established = - NonZeroU32::new(u32::try_from(conns.len() + 1).unwrap()) - .expect("n + 1 is always non-zero; qed"); - self.counters.inc_established(&endpoint); - conns.insert(id, endpoint); - match self.get(id) { - Some(PoolConnection::Established(connection)) => { - return Poll::Ready(PoolEvent::ConnectionEstablished { - connection, - num_established, + let error = self + .counters + // Check general established connection limit. + .check_max_established(&endpoint) + .map_err(Error::ConnectionLimit) + // Check per-peer established connection limit. + .and_then(|()| { + self.counters + .check_max_established_per_peer(num_peer_established( + &self.established, + peer_id, + )) + .map_err(Error::ConnectionLimit) + }) + // Check expected peer id matches. + .and_then(|()| { + if let Some(peer) = expected_peer_id { + if peer != peer_id { + return Err(Error::InvalidPeerId); + } + } + Ok(()) + }) + // Check peer is not local peer. + .and_then(|()| { + if self.local_id == peer_id { + Err(Error::InvalidPeerId) + } else { + Ok(()) + } + }); + + if let Err(error) = error { + self.spawn( + poll_fn(move |cx| { + if let Err(e) = ready!(muxer.close(cx)) { + log::debug!( + "Failed to close connection {:?} to peer {}: {:?}", + id, + peer_id, + e + ); + } + Poll::Ready(()) + }) + .boxed(), + ); + + match endpoint { + ConnectedPoint::Dialer { .. } => { + return Poll::Ready(PoolEvent::PendingOutboundConnectionError { + id, + error: error.into(), + handler, + peer: Some(peer_id), }) } - _ => unreachable!("since `entry` is an `EstablishedEntry`."), - } + ConnectedPoint::Listener { + send_back_addr, + local_addr, + } => { + return Poll::Ready(PoolEvent::PendingInboundConnectionError { + id, + error: error.into(), + handler, + send_back_addr, + local_addr, + }) + } + }; } - } - manager::Event::ConnectionEvent { entry, event } => { - let id = entry.id(); + + // Add the connection to the pool. + let conns = self.established.entry(peer_id).or_default(); + let num_established = NonZeroU32::new(u32::try_from(conns.len() + 1).unwrap()) + .expect("n + 1 is always non-zero; qed"); + self.counters.inc_established(&endpoint); + + let (command_sender, command_receiver) = + mpsc::channel(self.task_command_buffer_size); + conns.insert( + id, + EstablishedConnectionInfo { + peer_id, + endpoint: endpoint.clone(), + sender: command_sender, + }, + ); + + let connected = Connected { peer_id, endpoint }; + + let connection = + super::Connection::new(muxer, handler.into_handler(&connected)); + self.spawn( + task::new_for_established_connection( + id, + peer_id, + connection, + command_receiver, + self.established_connection_events_tx.clone(), + ) + .boxed(), + ); + match self.get(id) { Some(PoolConnection::Established(connection)) => { - return Poll::Ready(PoolEvent::ConnectionEvent { connection, event }) + return Poll::Ready(PoolEvent::ConnectionEstablished { + connection, + num_established, + concurrent_dial_errors, + }) } _ => unreachable!("since `entry` is an `EstablishedEntry`."), } } - manager::Event::AddressChange { - entry, - new_endpoint, - old_endpoint, - } => { - let id = entry.id(); - - match self.established.get_mut(&entry.connected().peer_id) { - Some(list) => { - *list.get_mut(&id).expect( - "state inconsistency: entry is `EstablishedEntry` but absent \ - from `established`", - ) = new_endpoint.clone() - } - None => unreachable!("since `entry` is an `EstablishedEntry`."), - }; + task::PendingConnectionEvent::PendingFailed { id, error } => { + if let Some(PendingConnectionInfo { + peer_id, + handler, + endpoint, + _drop_notifier, + }) = self.pending.remove(&id) + { + self.counters.dec_pending(&endpoint); - match self.get(id) { - Some(PoolConnection::Established(connection)) => { - return Poll::Ready(PoolEvent::AddressChange { - connection, - new_endpoint, - old_endpoint, - }) + match (endpoint, error) { + (PendingPoint::Dialer, Either::Left(error)) => { + return Poll::Ready(PoolEvent::PendingOutboundConnectionError { + id, + error, + handler, + peer: peer_id, + }); + } + ( + PendingPoint::Listener { + send_back_addr, + local_addr, + }, + Either::Right(error), + ) => { + return Poll::Ready(PoolEvent::PendingInboundConnectionError { + id, + error, + handler, + send_back_addr, + local_addr, + }); + } + (PendingPoint::Dialer, Either::Right(_)) => { + unreachable!("Inbound error for outbound connection.") + } + (PendingPoint::Listener { .. }, Either::Left(_)) => { + unreachable!("Outbound error for inbound connection.") + } } - _ => unreachable!("since `entry` is an `EstablishedEntry`."), } } } } + + // Advance the tasks in `local_spawns`. + while let Poll::Ready(Some(())) = self.local_spawns.poll_next_unpin(cx) {} + + Poll::Pending } } /// A connection in a [`Pool`]. -pub enum PoolConnection<'a, TInEvent> { - Pending(PendingConnection<'a, TInEvent>), - Established(EstablishedConnection<'a, TInEvent>), +pub enum PoolConnection<'a, THandler: IntoConnectionHandler> { + Pending(PendingConnection<'a, THandler>), + Established(EstablishedConnection<'a, THandlerInEvent>), } /// A pending connection in a pool. -pub struct PendingConnection<'a, TInEvent> { - entry: manager::PendingEntry<'a, TInEvent>, - pending: &'a mut FnvHashMap)>, +pub struct PendingConnection<'a, THandler: IntoConnectionHandler> { + entry: hash_map::OccupiedEntry<'a, ConnectionId, PendingConnectionInfo>, counters: &'a mut ConnectionCounters, } -impl PendingConnection<'_, TInEvent> { +impl PendingConnection<'_, THandler> { /// Returns the local connection ID. pub fn id(&self) -> ConnectionId { - self.entry.id() + *self.entry.key() } /// Returns the (expected) identity of the remote peer, if known. pub fn peer_id(&self) -> &Option { - &self - .pending - .get(&self.entry.id()) - .expect("`entry` is a pending entry") - .1 + &self.entry.get().peer_id } /// Returns information about this endpoint of the connection. - pub fn endpoint(&self) -> &ConnectedPoint { - &self - .pending - .get(&self.entry.id()) - .expect("`entry` is a pending entry") - .0 + pub fn endpoint(&self) -> &PendingPoint { + &self.entry.get().endpoint } /// Aborts the connection attempt, closing the connection. pub fn abort(self) { - let endpoint = self - .pending - .remove(&self.entry.id()) - .expect("`entry` is a pending entry") - .0; - self.counters.dec_pending(&endpoint); - self.entry.abort(); + self.counters.dec_pending(&self.entry.get().endpoint); + self.entry.remove(); } } /// An established connection in a pool. pub struct EstablishedConnection<'a, TInEvent> { - entry: manager::EstablishedEntry<'a, TInEvent>, + entry: hash_map::OccupiedEntry<'a, ConnectionId, EstablishedConnectionInfo>, } impl fmt::Debug for EstablishedConnection<'_, TInEvent> @@ -685,23 +995,19 @@ where } impl EstablishedConnection<'_, TInEvent> { - pub fn connected(&self) -> &Connected { - self.entry.connected() - } - /// Returns information about the connected endpoint. pub fn endpoint(&self) -> &ConnectedPoint { - &self.entry.connected().endpoint + &self.entry.get().endpoint } /// Returns the identity of the connected peer. pub fn peer_id(&self) -> PeerId { - self.entry.connected().peer_id + self.entry.get().peer_id } /// Returns the local connection ID. pub fn id(&self) -> ConnectionId { - self.entry.id() + *self.entry.key() } /// (Asynchronously) sends an event to the connection handler. @@ -715,7 +1021,15 @@ impl EstablishedConnection<'_, TInEvent> { /// of `notify_handler`, it only fails if the connection is now about /// to close. pub fn notify_handler(&mut self, event: TInEvent) -> Result<(), TInEvent> { - self.entry.notify_handler(event) + let cmd = task::Command::NotifyHandler(event); + self.entry + .get_mut() + .sender + .try_send(cmd) + .map_err(|e| match e.into_inner() { + task::Command::NotifyHandler(event) => event, + _ => unreachable!("Expect failed send to return initial event."), + }) } /// Checks if `notify_handler` is ready to accept an event. @@ -725,46 +1039,42 @@ impl EstablishedConnection<'_, TInEvent> { /// Returns `Err(())` if the background task associated with the connection /// is terminating and the connection is about to close. pub fn poll_ready_notify_handler(&mut self, cx: &mut Context<'_>) -> Poll> { - self.entry.poll_ready_notify_handler(cx) + self.entry.get_mut().sender.poll_ready(cx).map_err(|_| ()) } /// Initiates a graceful close of the connection. /// /// Has no effect if the connection is already closing. - pub fn start_close(self) { - self.entry.start_close(None) + pub fn start_close(mut self) { + self.entry.get_mut().start_close() } } /// An iterator over established connections in a pool. -pub struct EstablishedConnectionIter<'a, I, THandler: IntoConnectionHandler, TTransErr> { - pool: &'a mut Pool, +pub struct EstablishedConnectionIter<'a, I, TInEvent> { + connections: Option<&'a mut FnvHashMap>>, ids: I, } // Note: Ideally this would be an implementation of `Iterator`, but that // requires GATs (cf. https://github.com/rust-lang/rust/issues/44265) and // a different definition of `Iterator`. -impl<'a, I, THandler: IntoConnectionHandler, TTransErr> - EstablishedConnectionIter<'a, I, THandler, TTransErr> +impl<'a, I, TInEvent> EstablishedConnectionIter<'a, I, TInEvent> where I: Iterator, { /// Obtains the next connection, if any. #[allow(clippy::should_implement_trait)] - pub fn next(&mut self) -> Option>> { - while let Some(id) = self.ids.next() { - if self.pool.manager.is_established(&id) { - // (*) - match self.pool.manager.entry(id) { - Some(manager::Entry::Established(entry)) => { - return Some(EstablishedConnection { entry }) - } - _ => panic!("Established entry not found in manager."), // see (*) - } - } + pub fn next(&mut self) -> Option> { + if let (Some(id), Some(connections)) = (self.ids.next(), self.connections.as_mut()) { + Some(EstablishedConnection { + entry: connections + .entry(id) + .expect_occupied("Established entry not found in pool."), + }) + } else { + None } - None } /// Turns the iterator into an iterator over just the connection IDs. @@ -773,22 +1083,19 @@ where } /// Returns the first connection, if any, consuming the iterator. - pub fn into_first<'b>(mut self) -> Option>> + pub fn into_first<'b>(mut self) -> Option> where 'a: 'b, { - while let Some(id) = self.ids.next() { - if self.pool.manager.is_established(&id) { - // (*) - match self.pool.manager.entry(id) { - Some(manager::Entry::Established(entry)) => { - return Some(EstablishedConnection { entry }) - } - _ => panic!("Established entry not found in manager."), // see (*) - } - } + if let (Some(id), Some(connections)) = (self.ids.next(), self.connections) { + Some(EstablishedConnection { + entry: connections + .entry(id) + .expect_occupied("Established entry not found in pool."), + }) + } else { + None } - None } } @@ -858,23 +1165,27 @@ impl ConnectionCounters { self.established_outgoing + self.established_incoming } - fn inc_pending(&mut self, endpoint: &ConnectedPoint) { + fn inc_pending(&mut self, endpoint: &PendingPoint) { match endpoint { - ConnectedPoint::Dialer { .. } => { + PendingPoint::Dialer => { self.pending_outgoing += 1; } - ConnectedPoint::Listener { .. } => { + PendingPoint::Listener { .. } => { self.pending_incoming += 1; } } } - fn dec_pending(&mut self, endpoint: &ConnectedPoint) { + fn inc_pending_incoming(&mut self) { + self.pending_incoming += 1; + } + + fn dec_pending(&mut self, endpoint: &PendingPoint) { match endpoint { - ConnectedPoint::Dialer { .. } => { + PendingPoint::Dialer => { self.pending_outgoing -= 1; } - ConnectedPoint::Listener { .. } => { + PendingPoint::Listener { .. } => { self.pending_incoming -= 1; } } @@ -941,11 +1252,11 @@ impl ConnectionCounters { } /// Counts the number of established connections to the given peer. -fn num_peer_established( - established: &FnvHashMap>, - peer: &PeerId, +fn num_peer_established( + established: &FnvHashMap>>, + peer: PeerId, ) -> u32 { - established.get(peer).map_or(0, |conns| { + established.get(&peer).map_or(0, |conns| { u32::try_from(conns.len()).expect("Unexpectedly large number of connections for a peer.") }) } @@ -1006,3 +1317,47 @@ impl ConnectionLimits { self } } + +/// Configuration options when creating a [`Pool`]. +/// +/// The default configuration specifies no dedicated task executor, a +/// task event buffer size of 32, and a task command buffer size of 7. +pub struct PoolConfig { + /// Executor to use to spawn tasks. + pub executor: Option>, + + /// Size of the task command buffer (per task). + pub task_command_buffer_size: usize, + + /// Size of the pending connection task event buffer and the established connection task event + /// buffer. + pub task_event_buffer_size: usize, + + /// Number of addresses concurrently dialed for a single outbound connection attempt. + pub dial_concurrency_factor: NonZeroU8, +} + +impl Default for PoolConfig { + fn default() -> Self { + PoolConfig { + executor: None, + task_event_buffer_size: 32, + task_command_buffer_size: 7, + // By default, addresses of a single connection attempt are dialed in sequence. + dial_concurrency_factor: NonZeroU8::new(1).expect("1 > 0"), + } + } +} + +trait EntryExt<'a, K, V> { + fn expect_occupied(self, msg: &'static str) -> hash_map::OccupiedEntry<'a, K, V>; +} + +impl<'a, K: 'a, V: 'a> EntryExt<'a, K, V> for hash_map::Entry<'a, K, V> { + fn expect_occupied(self, msg: &'static str) -> hash_map::OccupiedEntry<'a, K, V> { + match self { + hash_map::Entry::Occupied(entry) => entry, + hash_map::Entry::Vacant(_) => panic!("{}", msg), + } + } +} diff --git a/core/src/connection/pool/concurrent_dial.rs b/core/src/connection/pool/concurrent_dial.rs new file mode 100644 index 00000000000..a7434a23fe9 --- /dev/null +++ b/core/src/connection/pool/concurrent_dial.rs @@ -0,0 +1,158 @@ +// Copyright 2021 Protocol Labs. +// +// 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. + +pub use crate::connection::{ConnectionCounters, ConnectionLimits}; + +use crate::{ + transport::{Transport, TransportError}, + Multiaddr, PeerId, +}; +use futures::{ + future::{BoxFuture, Future, FutureExt}, + ready, + stream::{FuturesUnordered, StreamExt}, +}; +use std::{ + num::NonZeroU8, + pin::Pin, + task::{Context, Poll}, +}; + +type Dial = BoxFuture< + 'static, + ( + Multiaddr, + Result<::Output, TransportError<::Error>>, + ), +>; + +pub struct ConcurrentDial { + dials: FuturesUnordered>, + pending_dials: Box> + Send>, + errors: Vec<(Multiaddr, TransportError)>, +} + +impl Unpin for ConcurrentDial {} + +impl ConcurrentDial +where + TTrans: Transport + Clone + Send + 'static, + TTrans::Output: Send, + TTrans::Error: Send, + TTrans::Dial: Send + 'static, +{ + pub(crate) fn new( + transport: TTrans, + peer: Option, + addresses: impl Iterator + Send + 'static, + concurrency_factor: NonZeroU8, + ) -> Self { + let mut pending_dials = addresses.map(move |address| match p2p_addr(peer, address) { + Ok(address) => match transport.clone().dial(address.clone()) { + Ok(fut) => fut + .map(|r| (address, r.map_err(|e| TransportError::Other(e)))) + .boxed(), + Err(err) => futures::future::ready((address, Err(err))).boxed(), + }, + Err(address) => futures::future::ready(( + address.clone(), + Err(TransportError::MultiaddrNotSupported(address)), + )) + .boxed(), + }); + + let dials = FuturesUnordered::new(); + while let Some(dial) = pending_dials.next() { + dials.push(dial); + if dials.len() == concurrency_factor.get().into() { + break; + } + } + + Self { + dials, + errors: Default::default(), + pending_dials: Box::new(pending_dials), + } + } +} + +impl Future for ConcurrentDial +where + TTrans: Transport, +{ + type Output = Result< + // Either one dial succeeded, returning the negotiated [`PeerId`], the address, the + // muxer and the addresses and errors of the dials that failed before. + ( + Multiaddr, + TTrans::Output, + Vec<(Multiaddr, TransportError)>, + ), + // Or all dials failed, thus returning the address and error for each dial. + Vec<(Multiaddr, TransportError)>, + >; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { + loop { + match ready!(self.dials.poll_next_unpin(cx)) { + Some((addr, Ok(output))) => { + let errors = std::mem::replace(&mut self.errors, vec![]); + return Poll::Ready(Ok((addr, output, errors))); + } + Some((addr, Err(e))) => { + self.errors.push((addr, e)); + if let Some(dial) = self.pending_dials.next() { + self.dials.push(dial) + } + } + None => { + return Poll::Ready(Err(std::mem::replace(&mut self.errors, vec![]))); + } + } + } + } +} + +/// Ensures a given `Multiaddr` is a `/p2p/...` address for the given peer. +/// +/// If the given address is already a `p2p` address for the given peer, +/// i.e. the last encapsulated protocol is `/p2p/`, this is a no-op. +/// +/// If the given address is already a `p2p` address for a different peer +/// than the one given, the given `Multiaddr` is returned as an `Err`. +/// +/// If the given address is not yet a `p2p` address for the given peer, +/// the `/p2p/` protocol is appended to the returned address. +fn p2p_addr(peer: Option, addr: Multiaddr) -> Result { + let peer = match peer { + Some(p) => p, + None => return Ok(addr), + }; + + if let Some(multiaddr::Protocol::P2p(hash)) = addr.iter().last() { + if &hash != peer.as_ref() { + return Err(addr); + } + Ok(addr) + } else { + Ok(addr.with(multiaddr::Protocol::P2p(peer.into()))) + } +} diff --git a/core/src/connection/pool/task.rs b/core/src/connection/pool/task.rs new file mode 100644 index 00000000000..9062583fd79 --- /dev/null +++ b/core/src/connection/pool/task.rs @@ -0,0 +1,260 @@ +// Copyright 2021 Protocol Labs. +// 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. + +//! Async functions driving pending and established connections in the form of a task. + +use super::concurrent_dial::ConcurrentDial; +use crate::{ + connection::{ + self, + handler::{THandlerError, THandlerInEvent, THandlerOutEvent}, + ConnectionError, ConnectionHandler, ConnectionId, IntoConnectionHandler, + PendingInboundConnectionError, PendingOutboundConnectionError, Substream, + }, + muxing::StreamMuxer, + transport::{Transport, TransportError}, + Multiaddr, PeerId, +}; +use futures::{ + channel::{mpsc, oneshot}, + future::{poll_fn, Either, Future}, + SinkExt, StreamExt, +}; +use std::pin::Pin; +use void::Void; + +/// Commands that can be sent to a task. +#[derive(Debug)] +pub enum Command { + /// Notify the connection handler of an event. + NotifyHandler(T), + /// Gracefully close the connection (active close) before + /// terminating the task. + Close, +} + +#[derive(Debug)] +pub enum PendingConnectionEvent +where + TTrans: Transport, +{ + ConnectionEstablished { + id: ConnectionId, + output: TTrans::Output, + /// [`Some`] when the new connection is an outgoing connection. + /// Addresses are dialed in parallel. Contains the addresses and errors + /// of dial attempts that failed before the one successful dial. + outgoing: Option<(Multiaddr, Vec<(Multiaddr, TransportError)>)>, + }, + /// A pending connection failed. + PendingFailed { + id: ConnectionId, + error: Either< + PendingOutboundConnectionError, + PendingInboundConnectionError, + >, + }, +} + +#[derive(Debug)] +pub enum EstablishedConnectionEvent { + /// A node we are connected to has changed its address. + AddressChange { + id: ConnectionId, + peer_id: PeerId, + new_address: Multiaddr, + }, + /// Notify the manager of an event from the connection. + Notify { + id: ConnectionId, + peer_id: PeerId, + event: THandlerOutEvent, + }, + /// A connection closed, possibly due to an error. + /// + /// If `error` is `None`, the connection has completed + /// an active orderly close. + Closed { + id: ConnectionId, + peer_id: PeerId, + error: Option>>, + handler: THandler::Handler, + }, +} + +pub async fn new_for_pending_outgoing_connection( + connection_id: ConnectionId, + dial: ConcurrentDial, + drop_receiver: oneshot::Receiver, + mut events: mpsc::Sender>, +) where + TTrans: Transport, +{ + match futures::future::select(drop_receiver, Box::pin(dial)).await { + Either::Left((Err(oneshot::Canceled), _)) => { + let _ = events + .send(PendingConnectionEvent::PendingFailed { + id: connection_id, + error: Either::Left(PendingOutboundConnectionError::Aborted), + }) + .await; + } + Either::Left((Ok(v), _)) => void::unreachable(v), + Either::Right((Ok((address, output, errors)), _)) => { + let _ = events + .send(PendingConnectionEvent::ConnectionEstablished { + id: connection_id, + output, + outgoing: Some((address, errors)), + }) + .await; + } + Either::Right((Err(e), _)) => { + let _ = events + .send(PendingConnectionEvent::PendingFailed { + id: connection_id, + error: Either::Left(PendingOutboundConnectionError::Transport(e)), + }) + .await; + } + } +} + +pub async fn new_for_pending_incoming_connection( + connection_id: ConnectionId, + future: TFut, + drop_receiver: oneshot::Receiver, + mut events: mpsc::Sender>, +) where + TTrans: Transport, + TFut: Future> + Send + 'static, +{ + match futures::future::select(drop_receiver, Box::pin(future)).await { + Either::Left((Err(oneshot::Canceled), _)) => { + let _ = events + .send(PendingConnectionEvent::PendingFailed { + id: connection_id, + error: Either::Right(PendingInboundConnectionError::Aborted), + }) + .await; + } + Either::Left((Ok(v), _)) => void::unreachable(v), + Either::Right((Ok(output), _)) => { + let _ = events + .send(PendingConnectionEvent::ConnectionEstablished { + id: connection_id, + output, + outgoing: None, + }) + .await; + } + Either::Right((Err(e), _)) => { + let _ = events + .send(PendingConnectionEvent::PendingFailed { + id: connection_id, + error: Either::Right(PendingInboundConnectionError::Transport( + TransportError::Other(e), + )), + }) + .await; + } + } +} + +pub async fn new_for_established_connection( + connection_id: ConnectionId, + peer_id: PeerId, + mut connection: crate::connection::Connection, + mut command_receiver: mpsc::Receiver>>, + mut events: mpsc::Sender>, +) where + TMuxer: StreamMuxer, + THandler: IntoConnectionHandler, + THandler::Handler: ConnectionHandler>, +{ + loop { + match futures::future::select( + command_receiver.next(), + poll_fn(|cx| Pin::new(&mut connection).poll(cx)), + ) + .await + { + Either::Left((Some(command), _)) => match command { + Command::NotifyHandler(event) => connection.inject_event(event), + Command::Close => { + command_receiver.close(); + let (handler, closing_muxer) = connection.close(); + + let error = closing_muxer.await.err().map(ConnectionError::IO); + let _ = events + .send(EstablishedConnectionEvent::Closed { + id: connection_id, + peer_id, + error, + handler, + }) + .await; + return; + } + }, + + // The manager has disappeared; abort. + Either::Left((None, _)) => return, + + Either::Right((event, _)) => { + match event { + Ok(connection::Event::Handler(event)) => { + let _ = events + .send(EstablishedConnectionEvent::Notify { + id: connection_id, + peer_id, + event, + }) + .await; + } + Ok(connection::Event::AddressChange(new_address)) => { + let _ = events + .send(EstablishedConnectionEvent::AddressChange { + id: connection_id, + peer_id, + new_address, + }) + .await; + } + Err(error) => { + command_receiver.close(); + let (handler, _closing_muxer) = connection.close(); + // Terminate the task with the error, dropping the connection. + let _ = events + .send(EstablishedConnectionEvent::Closed { + id: connection_id, + peer_id, + error: Some(error), + handler, + }) + .await; + return; + } + } + } + } + } +} diff --git a/core/src/identity/rsa.rs b/core/src/identity/rsa.rs index cd18f6a848c..97e8f23cf42 100644 --- a/core/src/identity/rsa.rs +++ b/core/src/identity/rsa.rs @@ -298,7 +298,7 @@ impl DerDecodable<'_> for Asn1SubjectPublicKeyInfo { mod tests { use super::*; use quickcheck::*; - use rand::seq::SliceRandom; + use rand07::seq::SliceRandom; use std::fmt; const KEY1: &'static [u8] = include_bytes!("test/rsa-2048.pk8"); diff --git a/core/src/network.rs b/core/src/network.rs index 831a99c4b01..3478680bc02 100644 --- a/core/src/network.rs +++ b/core/src/network.rs @@ -22,30 +22,24 @@ mod event; pub mod peer; pub use crate::connection::{ConnectionCounters, ConnectionLimits}; -pub use event::{DialAttemptsRemaining, IncomingConnection, NetworkEvent}; +pub use event::{IncomingConnection, NetworkEvent}; pub use peer::Peer; use crate::{ connection::{ handler::{THandlerInEvent, THandlerOutEvent}, - manager::ManagerConfig, - pool::{Pool, PoolEvent}, + pool::{Pool, PoolConfig, PoolEvent}, ConnectionHandler, ConnectionId, ConnectionLimit, IncomingInfo, IntoConnectionHandler, - ListenerId, ListenersEvent, ListenersStream, OutgoingInfo, PendingConnectionError, - Substream, + ListenerId, ListenersEvent, ListenersStream, PendingPoint, Substream, }, muxing::StreamMuxer, transport::{Transport, TransportError}, - ConnectedPoint, Executor, Multiaddr, PeerId, + Executor, Multiaddr, PeerId, }; -use fnv::FnvHashMap; -use futures::{future, prelude::*}; -use smallvec::SmallVec; use std::{ - collections::hash_map, convert::TryFrom as _, error, fmt, - num::{NonZeroU32, NonZeroUsize}, + num::{NonZeroU8, NonZeroUsize}, pin::Pin, task::{Context, Poll}, }; @@ -63,21 +57,7 @@ where listeners: ListenersStream, /// The nodes currently active. - pool: Pool, - - /// The ongoing dialing attempts. - /// - /// There may be multiple ongoing dialing attempts to the same peer. - /// Each dialing attempt is associated with a new connection and hence - /// a new connection ID. - /// - /// > **Note**: `dialing` must be consistent with the pending outgoing - /// > connections in `pool`. That is, for every entry in `dialing` - /// > there must exist a pending outgoing connection in `pool` with - /// > the same connection ID. This is ensured by the implementation of - /// > `Network` (see `dial_peer_impl` and `on_connection_failed`) - /// > together with the implementation of `DialingAttempt::abort`. - dialing: FnvHashMap>, + pool: Pool, } impl fmt::Debug for Network @@ -90,7 +70,6 @@ where .field("local_peer_id", &self.local_peer_id) .field("listeners", &self.listeners) .field("peers", &self.pool) - .field("dialing", &self.dialing) .finish() } } @@ -107,28 +86,42 @@ where TTrans: Transport, THandler: IntoConnectionHandler, { + /// Checks whether the network has an established connection to a peer. + pub fn is_connected(&self, peer: &PeerId) -> bool { + self.pool.is_connected(peer) + } + + fn dialing_attempts(&self, peer: PeerId) -> impl Iterator { + self.pool + .iter_pending_info() + .filter(move |(_, endpoint, peer_id)| { + matches!(endpoint, PendingPoint::Dialer) && peer_id.as_ref() == Some(&peer) + }) + .map(|(connection_id, _, _)| connection_id) + } + + /// Checks whether the network has an ongoing dialing attempt to a peer. + pub fn is_dialing(&self, peer: &PeerId) -> bool { + self.dialing_attempts(*peer).next().is_some() + } + fn disconnect(&mut self, peer: &PeerId) { self.pool.disconnect(peer); - self.dialing.remove(peer); } } -impl Network +impl Network where - TTrans: Transport + Clone, - TMuxer: StreamMuxer, + TTrans: Transport + Clone + 'static, + ::Error: Send + 'static, THandler: IntoConnectionHandler + Send + 'static, - ::OutboundOpenInfo: Send, - ::Error: error::Error + Send, - THandler::Handler: ConnectionHandler> + Send, { /// Creates a new node events stream. pub fn new(transport: TTrans, local_peer_id: PeerId, config: NetworkConfig) -> Self { Network { local_peer_id, listeners: ListenersStream::new(transport), - pool: Pool::new(local_peer_id, config.manager_config, config.limits), - dialing: Default::default(), + pool: Pool::new(local_peer_id, config.pool_config, config.limits), } } @@ -147,8 +140,9 @@ where /// Remove a previously added listener. /// - /// Returns `Ok(())` if a listener with this ID was in the list. - pub fn remove_listener(&mut self, id: ListenerId) -> Result<(), ()> { + /// Returns `true` if there was a listener with this ID, `false` + /// otherwise. + pub fn remove_listener(&mut self, id: ListenerId) -> bool { self.listeners.remove_listener(id) } @@ -171,7 +165,6 @@ where /// The translation is transport-specific. See [`Transport::address_translation`]. pub fn address_translation<'a>(&'a self, observed_addr: &'a Multiaddr) -> Vec where - TMuxer: 'a, THandler: 'a, { let transport = self.listeners.transport(); @@ -204,11 +197,11 @@ where handler: THandler, ) -> Result> where - TTrans: Transport, + TTrans: Transport + Send, + TTrans::Output: Send + 'static, + TTrans::Dial: Send + 'static, TTrans::Error: Send + 'static, TTrans::Dial: Send + 'static, - TMuxer: Send + Sync + 'static, - TMuxer::OutboundSubstream: Send, { // If the address ultimately encapsulates an expected peer ID, dial that peer // such that any mismatch is detected. We do not "pop off" the `P2p` protocol @@ -218,30 +211,40 @@ where if let Ok(peer) = PeerId::try_from(ma) { return self.dial_peer(DialingOpts { peer, - address: address.clone(), + addresses: std::iter::once(address.clone()), handler, - remaining: Vec::new(), }); } } - // The address does not specify an expected peer, so just try to dial it as-is, - // accepting any peer ID that the remote identifies as. - let info = OutgoingInfo { - address, - peer_id: None, - }; - match self.transport().clone().dial(address.clone()) { - Ok(f) => { - let f = - f.map_err(|err| PendingConnectionError::Transport(TransportError::Other(err))); - self.pool.add_outgoing(f, handler, info) - } - Err(err) => { - let f = future::err(PendingConnectionError::Transport(err)); - self.pool.add_outgoing(f, handler, info) - } - } + self.pool.add_outgoing( + self.transport().clone(), + std::iter::once(address.clone()), + None, + handler, + ) + } + + /// Initiates a connection attempt to a known peer. + fn dial_peer( + &mut self, + opts: DialingOpts, + ) -> Result> + where + I: Iterator + Send + 'static, + TTrans: Transport + Send, + TTrans::Output: Send + 'static, + TTrans::Dial: Send + 'static, + TTrans::Error: Send + 'static, + { + let id = self.pool.add_outgoing( + self.transport().clone(), + opts.addresses, + Some(opts.peer), + opts.handler, + )?; + + Ok(id) } /// Returns information about the state of the `Network`. @@ -259,33 +262,12 @@ where self.pool.iter_pending_incoming() } - /// Returns the list of addresses we're currently dialing without knowing the `PeerId` of. - pub fn unknown_dials(&self) -> impl Iterator { - self.pool.iter_pending_outgoing().filter_map(|info| { - if info.peer_id.is_none() { - Some(info.address) - } else { - None - } - }) - } - /// Returns a list of all connected peers, i.e. peers to whom the `Network` /// has at least one established connection. pub fn connected_peers(&self) -> impl Iterator { self.pool.iter_connected() } - /// Checks whether the network has an established connection to a peer. - pub fn is_connected(&self, peer: &PeerId) -> bool { - self.pool.is_connected(peer) - } - - /// Checks whether the network has an ongoing dialing attempt to a peer. - pub fn is_dialing(&self, peer: &PeerId) -> bool { - self.dialing.contains_key(peer) - } - /// Checks whether the network has neither an ongoing dialing attempt, /// nor an established connection to a peer. pub fn is_disconnected(&self, peer: &PeerId) -> bool { @@ -295,7 +277,10 @@ where /// Returns a list of all the peers to whom a new outgoing connection /// is currently being established. pub fn dialing_peers(&self) -> impl Iterator { - self.dialing.keys() + self.pool + .iter_pending_info() + .filter(|(_, endpoint, _)| matches!(endpoint, PendingPoint::Dialer)) + .filter_map(|(_, _, peer)| peer.as_ref()) } /// Obtains a view of a [`Peer`] with the given ID in the network. @@ -310,28 +295,31 @@ where /// completed, the connection is associated with the provided `handler`. pub fn accept( &mut self, - connection: IncomingConnection, + IncomingConnection { + upgrade, + local_addr, + send_back_addr, + }: IncomingConnection, handler: THandler, - ) -> Result + ) -> Result where - TMuxer: StreamMuxer + Send + Sync + 'static, - TMuxer::OutboundSubstream: Send, - TTrans: Transport, + TTrans: Transport, + TTrans::Output: Send + 'static, TTrans::Error: Send + 'static, TTrans::ListenerUpgrade: Send + 'static, { - let upgrade = connection - .upgrade - .map_err(|err| PendingConnectionError::Transport(TransportError::Other(err))); - let info = IncomingInfo { - local_addr: &connection.local_addr, - send_back_addr: &connection.send_back_addr, - }; - self.pool.add_incoming(upgrade, handler, info) + self.pool.add_incoming( + upgrade, + handler, + IncomingInfo { + local_addr: &local_addr, + send_back_addr: &send_back_addr, + }, + ) } - /// Provides an API similar to `Stream`, except that it cannot error. - pub fn poll<'a>( + /// Provides an API similar to `Stream`, except that it does not terminate. + pub fn poll<'a, TMuxer>( &'a mut self, cx: &mut Context<'_>, ) -> Poll< @@ -342,10 +330,14 @@ where TTrans::Error: Send + 'static, TTrans::Dial: Send + 'static, TTrans::ListenerUpgrade: Send + 'static, - TMuxer: Send + Sync + 'static, + TMuxer: StreamMuxer + Send + Sync + 'static, + TMuxer::Error: std::fmt::Debug, TMuxer::OutboundSubstream: Send, THandler: IntoConnectionHandler + Send + 'static, ::Error: error::Error + Send + 'static, + ::OutboundOpenInfo: Send, + ::Error: error::Error + Send, + THandler::Handler: ConnectionHandler> + Send, { // Poll the listener(s) for new connections. match ListenersStream::poll(Pin::new(&mut self.listeners), cx) { @@ -405,37 +397,40 @@ where Poll::Ready(PoolEvent::ConnectionEstablished { connection, num_established, - }) => { - if let hash_map::Entry::Occupied(mut e) = self.dialing.entry(connection.peer_id()) { - e.get_mut().retain(|s| s.current.0 != connection.id()); - if e.get().is_empty() { - e.remove(); - } - } - - NetworkEvent::ConnectionEstablished { - connection, - num_established, - } - } - Poll::Ready(PoolEvent::PendingConnectionError { - id, - endpoint, + concurrent_dial_errors, + }) => NetworkEvent::ConnectionEstablished { + connection, + num_established, + concurrent_dial_errors, + }, + Poll::Ready(PoolEvent::PendingOutboundConnectionError { + id: _, error, handler, - pool, - .. + peer, }) => { - let dialing = &mut self.dialing; - let (next, event) = on_connection_failed(dialing, id, endpoint, error, handler); - if let Some(dial) = next { - let transport = self.listeners.transport().clone(); - if let Err(e) = dial_peer_impl(transport, pool, dialing, dial) { - log::warn!("Dialing aborted: {:?}", e); + if let Some(peer) = peer { + NetworkEvent::DialError { + handler, + peer_id: peer, + error, } + } else { + NetworkEvent::UnknownPeerDialError { error, handler } } - event } + Poll::Ready(PoolEvent::PendingInboundConnectionError { + id: _, + send_back_addr, + local_addr, + error, + handler, + }) => NetworkEvent::IncomingConnectionError { + error, + handler, + send_back_addr, + local_addr, + }, Poll::Ready(PoolEvent::ConnectionClosed { id, connected, @@ -466,185 +461,14 @@ where Poll::Ready(event) } - - /// Initiates a connection attempt to a known peer. - fn dial_peer( - &mut self, - opts: DialingOpts, - ) -> Result> - where - TTrans: Transport, - TTrans::Dial: Send + 'static, - TTrans::Error: Send + 'static, - TMuxer: Send + Sync + 'static, - TMuxer::OutboundSubstream: Send, - { - dial_peer_impl( - self.transport().clone(), - &mut self.pool, - &mut self.dialing, - opts, - ) - } } /// Options for a dialing attempt (i.e. repeated connection attempt /// via a list of address) to a peer. -struct DialingOpts { +struct DialingOpts { peer: PeerId, handler: THandler, - address: Multiaddr, - remaining: Vec, -} - -/// Standalone implementation of `Network::dial_peer` for more granular borrowing. -fn dial_peer_impl( - transport: TTrans, - pool: &mut Pool, - dialing: &mut FnvHashMap>, - opts: DialingOpts, -) -> Result> -where - THandler: IntoConnectionHandler + Send + 'static, - ::Error: error::Error + Send + 'static, - ::OutboundOpenInfo: Send + 'static, - THandler::Handler: ConnectionHandler> + Send + 'static, - TTrans: Transport, - TTrans::Dial: Send + 'static, - TTrans::Error: error::Error + Send + 'static, - TMuxer: StreamMuxer + Send + Sync + 'static, - TMuxer::OutboundSubstream: Send + 'static, -{ - // Ensure the address to dial encapsulates the `p2p` protocol for the - // targeted peer, so that the transport has a "fully qualified" address - // to work with. - let addr = match p2p_addr(opts.peer, opts.address) { - Ok(address) => address, - Err(address) => { - return Err(DialError::InvalidAddress { - address, - handler: opts.handler, - }) - } - }; - - let result = match transport.dial(addr.clone()) { - Ok(fut) => { - let fut = fut.map_err(|e| PendingConnectionError::Transport(TransportError::Other(e))); - let info = OutgoingInfo { - address: &addr, - peer_id: Some(&opts.peer), - }; - pool.add_outgoing(fut, opts.handler, info) - } - Err(err) => { - let fut = future::err(PendingConnectionError::Transport(err)); - let info = OutgoingInfo { - address: &addr, - peer_id: Some(&opts.peer), - }; - pool.add_outgoing(fut, opts.handler, info) - } - }; - - if let Ok(id) = &result { - dialing - .entry(opts.peer) - .or_default() - .push(peer::DialingState { - current: (*id, addr), - remaining: opts.remaining, - }); - } - - result -} - -/// Callback for handling a failed connection attempt, returning an -/// event to emit from the `Network`. -/// -/// If the failed connection attempt was a dialing attempt and there -/// are more addresses to try, new `DialingOpts` are returned. -fn on_connection_failed<'a, TTrans, THandler>( - dialing: &mut FnvHashMap>, - id: ConnectionId, - endpoint: ConnectedPoint, - error: PendingConnectionError, - handler: THandler, -) -> ( - Option>, - NetworkEvent<'a, TTrans, THandlerInEvent, THandlerOutEvent, THandler>, -) -where - TTrans: Transport, - THandler: IntoConnectionHandler, -{ - // Check if the failed connection is associated with a dialing attempt. - let dialing_failed = dialing.iter_mut().find_map(|(peer, attempts)| { - if let Some(pos) = attempts.iter().position(|s| s.current.0 == id) { - let attempt = attempts.remove(pos); - let last = attempts.is_empty(); - Some((*peer, attempt, last)) - } else { - None - } - }); - - if let Some((peer_id, mut attempt, last)) = dialing_failed { - if last { - dialing.remove(&peer_id); - } - - let num_remain = u32::try_from(attempt.remaining.len()).unwrap(); - let failed_addr = attempt.current.1.clone(); - - let (opts, attempts_remaining) = if let Some(num_remain) = NonZeroU32::new(num_remain) { - let next_attempt = attempt.remaining.remove(0); - let opts = DialingOpts { - peer: peer_id, - handler, - address: next_attempt, - remaining: attempt.remaining, - }; - (Some(opts), DialAttemptsRemaining::Some(num_remain)) - } else { - (None, DialAttemptsRemaining::None(handler)) - }; - - ( - opts, - NetworkEvent::DialError { - attempts_remaining, - peer_id, - multiaddr: failed_addr, - error, - }, - ) - } else { - // A pending incoming connection or outgoing connection to an unknown peer failed. - match endpoint { - ConnectedPoint::Dialer { address } => ( - None, - NetworkEvent::UnknownPeerDialError { - multiaddr: address, - error, - handler, - }, - ), - ConnectedPoint::Listener { - local_addr, - send_back_addr, - } => ( - None, - NetworkEvent::IncomingConnectionError { - local_addr, - send_back_addr, - error, - handler, - }, - ), - } - } + addresses: I, } /// Information about the network obtained by [`Network::info()`]. @@ -676,10 +500,8 @@ impl NetworkInfo { /// `notify_handler` buffer size of 8. #[derive(Default)] pub struct NetworkConfig { - /// Note that the `ManagerConfig`s task command buffer always provides - /// one "free" slot per task. Thus the given total `notify_handler_buffer_size` - /// exposed for configuration on the `Network` is reduced by one. - manager_config: ManagerConfig, + /// Connection [`Pool`] configuration. + pool_config: PoolConfig, /// The effective connection limits. limits: ConnectionLimits, } @@ -687,7 +509,7 @@ pub struct NetworkConfig { impl NetworkConfig { /// Configures the executor to use for spawning connection background tasks. pub fn with_executor(mut self, e: Box) -> Self { - self.manager_config.executor = Some(e); + self.pool_config.executor = Some(e); self } @@ -697,7 +519,7 @@ impl NetworkConfig { where F: FnOnce() -> Option>, { - self.manager_config.executor = self.manager_config.executor.or_else(f); + self.pool_config.executor = self.pool_config.executor.or_else(f); self } @@ -709,7 +531,7 @@ impl NetworkConfig { /// longer be able to deliver events to the associated `ConnectionHandler`, /// thus exerting back-pressure on the connection and peer API. pub fn with_notify_handler_buffer_size(mut self, n: NonZeroUsize) -> Self { - self.manager_config.task_command_buffer_size = n.get() - 1; + self.pool_config.task_command_buffer_size = n.get() - 1; self } @@ -720,7 +542,13 @@ impl NetworkConfig { /// In this way, the consumers of network events exert back-pressure on /// the network connection I/O. pub fn with_connection_event_buffer_size(mut self, n: usize) -> Self { - self.manager_config.task_event_buffer_size = n; + self.pool_config.task_event_buffer_size = n; + self + } + + /// Number of addresses concurrently dialed for a single outbound connection attempt. + pub fn with_dial_concurrency_factor(mut self, factor: NonZeroU8) -> Self { + self.pool_config.dial_concurrency_factor = factor; self } @@ -731,27 +559,6 @@ impl NetworkConfig { } } -/// Ensures a given `Multiaddr` is a `/p2p/...` address for the given peer. -/// -/// If the given address is already a `p2p` address for the given peer, -/// i.e. the last encapsulated protocol is `/p2p/`, this is a no-op. -/// -/// If the given address is already a `p2p` address for a different peer -/// than the one given, the given `Multiaddr` is returned as an `Err`. -/// -/// If the given address is not yet a `p2p` address for the given peer, -/// the `/p2p/` protocol is appended to the returned address. -fn p2p_addr(peer: PeerId, addr: Multiaddr) -> Result { - if let Some(multiaddr::Protocol::P2p(hash)) = addr.iter().last() { - if &hash != peer.as_ref() { - return Err(addr); - } - Ok(addr) - } else { - Ok(addr.with(multiaddr::Protocol::P2p(peer.into()))) - } -} - /// Possible (synchronous) errors when dialing a peer. #[derive(Clone)] pub enum DialError { @@ -760,12 +567,6 @@ pub enum DialError { limit: ConnectionLimit, handler: THandler, }, - /// The address being dialed is invalid, e.g. if it refers to a different - /// remote peer than the one being dialed. - InvalidAddress { - address: Multiaddr, - handler: THandler, - }, /// The dialing attempt is rejected because the peer being dialed is the local peer. LocalPeerId { handler: THandler }, } @@ -777,13 +578,6 @@ impl fmt::Debug for DialError { .debug_struct("DialError::ConnectionLimit") .field("limit", limit) .finish(), - DialError::InvalidAddress { - address, - handler: _, - } => f - .debug_struct("DialError::InvalidAddress") - .field("address", address) - .finish(), DialError::LocalPeerId { handler: _ } => { f.debug_struct("DialError::LocalPeerId").finish() } @@ -794,6 +588,7 @@ impl fmt::Debug for DialError { #[cfg(test)] mod tests { use super::*; + use futures::future::Future; struct Dummy; diff --git a/core/src/network/event.rs b/core/src/network/event.rs index cea5bbddc21..6e016e82159 100644 --- a/core/src/network/event.rs +++ b/core/src/network/event.rs @@ -23,9 +23,10 @@ use crate::{ connection::{ Connected, ConnectedPoint, ConnectionError, ConnectionHandler, ConnectionId, - EstablishedConnection, IntoConnectionHandler, ListenerId, PendingConnectionError, + EstablishedConnection, IntoConnectionHandler, ListenerId, PendingInboundConnectionError, + PendingOutboundConnectionError, }, - transport::Transport, + transport::{Transport, TransportError}, Multiaddr, PeerId, }; use std::{fmt, num::NonZeroU32}; @@ -91,7 +92,7 @@ where /// Address used to send back data to the remote. send_back_addr: Multiaddr, /// The error that happened. - error: PendingConnectionError, + error: PendingInboundConnectionError, handler: THandler, }, @@ -102,6 +103,10 @@ where /// The total number of established connections to the same peer, /// including the one that has just been opened. num_established: NonZeroU32, + /// [`Some`] when the new connection is an outgoing connection. + /// Addresses are dialed in parallel. Contains the addresses and errors + /// of dial attempts that failed before the one successful dial. + concurrent_dial_errors: Option)>>, }, /// An established connection to a peer has been closed. @@ -131,25 +136,19 @@ where /// A dialing attempt to an address of a peer failed. DialError { /// The number of remaining dialing attempts. - attempts_remaining: DialAttemptsRemaining, + handler: THandler, /// Id of the peer we were trying to dial. peer_id: PeerId, - /// The multiaddr we failed to reach. - multiaddr: Multiaddr, - /// The error that happened. - error: PendingConnectionError, + error: PendingOutboundConnectionError, }, /// Failed to reach a peer that we were trying to dial. UnknownPeerDialError { - /// The multiaddr we failed to reach. - multiaddr: Multiaddr, - /// The error that happened. - error: PendingConnectionError, + error: PendingOutboundConnectionError, handler: THandler, }, @@ -173,20 +172,6 @@ where }, } -pub enum DialAttemptsRemaining { - Some(NonZeroU32), - None(THandler), -} - -impl DialAttemptsRemaining { - pub fn get_attempts(&self) -> u32 { - match self { - DialAttemptsRemaining::Some(attempts) => (*attempts).into(), - DialAttemptsRemaining::None(_) => 0, - } - } -} - impl fmt::Debug for NetworkEvent<'_, TTrans, TInEvent, TOutEvent, THandler> where @@ -246,9 +231,14 @@ where .field("send_back_addr", send_back_addr) .field("error", error) .finish(), - NetworkEvent::ConnectionEstablished { connection, .. } => f - .debug_struct("ConnectionEstablished") + NetworkEvent::ConnectionEstablished { + connection, + concurrent_dial_errors, + .. + } => f + .debug_struct("OutgoingConnectionEstablished") .field("connection", connection) + .field("concurrent_dial_errors", concurrent_dial_errors) .finish(), NetworkEvent::ConnectionClosed { id, @@ -262,22 +252,21 @@ where .field("error", error) .finish(), NetworkEvent::DialError { - attempts_remaining, + handler: _, peer_id, - multiaddr, error, } => f .debug_struct("DialError") - .field("attempts_remaining", &attempts_remaining.get_attempts()) .field("peer_id", peer_id) - .field("multiaddr", multiaddr) .field("error", error) .finish(), NetworkEvent::UnknownPeerDialError { - multiaddr, error, .. + // multiaddr, + error, + .. } => f .debug_struct("UnknownPeerDialError") - .field("multiaddr", multiaddr) + // .field("multiaddr", multiaddr) .field("error", error) .finish(), NetworkEvent::ConnectionEvent { connection, event } => f diff --git a/core/src/network/peer.rs b/core/src/network/peer.rs index 1eda5dde9e0..3a286c56c90 100644 --- a/core/src/network/peer.rs +++ b/core/src/network/peer.rs @@ -21,15 +21,12 @@ use super::{DialError, DialingOpts, Network}; use crate::{ connection::{ - handler::THandlerInEvent, pool::Pool, ConnectedPoint, ConnectionHandler, ConnectionId, + handler::THandlerInEvent, pool::Pool, ConnectionHandler, ConnectionId, EstablishedConnection, EstablishedConnectionIter, IntoConnectionHandler, PendingConnection, - Substream, }, - Multiaddr, PeerId, StreamMuxer, Transport, + Multiaddr, PeerId, Transport, }; -use fnv::FnvHashMap; -use smallvec::SmallVec; -use std::{collections::hash_map, error, fmt}; +use std::{collections::VecDeque, error, fmt}; /// The possible representations of a peer in a [`Network`], as /// seen by the local node. @@ -62,6 +59,7 @@ where impl<'a, TTrans, THandler> fmt::Debug for Peer<'a, TTrans, THandler> where TTrans: Transport, + TTrans::Error: Send + 'static, THandler: IntoConnectionHandler, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { @@ -77,6 +75,7 @@ where impl<'a, TTrans, THandler> Peer<'a, TTrans, THandler> where TTrans: Transport, + TTrans::Error: Send + 'static, THandler: IntoConnectionHandler, { pub(super) fn new(network: &'a mut Network, peer_id: PeerId) -> Self { @@ -88,7 +87,7 @@ where return Self::connected(network, peer_id); } - if network.dialing.get_mut(&peer_id).is_some() { + if network.is_dialing(&peer_id) { return Self::dialing(network, peer_id); } @@ -108,15 +107,14 @@ where } } -impl<'a, TTrans, TMuxer, THandler> Peer<'a, TTrans, THandler> +impl<'a, TTrans, THandler> Peer<'a, TTrans, THandler> where - TTrans: Transport + Clone, + TTrans: Transport + Clone + Send + 'static, + TTrans::Output: Send + 'static, TTrans::Error: Send + 'static, TTrans::Dial: Send + 'static, - TMuxer: StreamMuxer + Send + Sync + 'static, - TMuxer::OutboundSubstream: Send, THandler: IntoConnectionHandler + Send + 'static, - THandler::Handler: ConnectionHandler> + Send, + THandler::Handler: ConnectionHandler + Send, ::OutboundOpenInfo: Send, ::Error: error::Error + Send + 'static, { @@ -160,12 +158,12 @@ where /// attempt to the first address fails. pub fn dial( self, - address: Multiaddr, - remaining: I, + addresses: I, handler: THandler, ) -> Result<(ConnectionId, DialingPeer<'a, TTrans, THandler>), DialError> where I: IntoIterator, + I::IntoIter: Send + 'static, { let (peer_id, network) = match self { Peer::Connected(p) => (p.peer_id, p.network), @@ -177,8 +175,7 @@ where let id = network.dial_peer(DialingOpts { peer: peer_id, handler, - address, - remaining: remaining.into_iter().collect(), + addresses: addresses.into_iter(), })?; Ok((id, DialingPeer { network, peer_id })) @@ -233,6 +230,7 @@ where impl<'a, TTrans, THandler> ConnectedPeer<'a, TTrans, THandler> where TTrans: Transport, + ::Error: Send + 'static, THandler: IntoConnectionHandler, { pub fn id(&self) -> &PeerId { @@ -254,20 +252,20 @@ where /// The number of established connections to the peer. pub fn num_connections(&self) -> u32 { - self.network.pool.num_peer_established(&self.peer_id) + self.network.pool.num_peer_established(self.peer_id) } /// Checks whether there is an ongoing dialing attempt to the peer. /// /// Returns `true` iff [`ConnectedPeer::into_dialing`] returns `Some`. pub fn is_dialing(&self) -> bool { - self.network.dialing.contains_key(&self.peer_id) + self.network.is_dialing(&self.peer_id) } /// Converts this peer into a [`DialingPeer`], if there is an ongoing /// dialing attempt, `None` otherwise. pub fn into_dialing(self) -> Option> { - if self.network.dialing.contains_key(&self.peer_id) { + if self.network.is_dialing(&self.peer_id) { Some(DialingPeer { network: self.network, peer_id: self.peer_id, @@ -280,7 +278,7 @@ where /// Gets an iterator over all established connections to the peer. pub fn connections( &mut self, - ) -> EstablishedConnectionIter, THandler, TTrans::Error> + ) -> EstablishedConnectionIter, THandlerInEvent> { self.network.pool.iter_peer_established(&self.peer_id) } @@ -305,6 +303,7 @@ where impl<'a, TTrans, THandler> fmt::Debug for ConnectedPeer<'a, TTrans, THandler> where TTrans: Transport, + TTrans::Error: Send + 'static, THandler: IntoConnectionHandler, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { @@ -312,9 +311,13 @@ where .field("peer_id", &self.peer_id) .field( "established", - &self.network.pool.iter_peer_established_info(&self.peer_id), + &self + .network + .pool + .iter_peer_established_info(&self.peer_id) + .collect::>(), ) - .field("attempts", &self.network.dialing.get(&self.peer_id)) + .field("attempts", &self.network.is_dialing(&self.peer_id)) .finish() } } @@ -334,6 +337,7 @@ where impl<'a, TTrans, THandler> DialingPeer<'a, TTrans, THandler> where TTrans: Transport, + TTrans::Error: Send + 'static, THandler: IntoConnectionHandler, { pub fn id(&self) -> &PeerId { @@ -376,37 +380,22 @@ where /// Obtains a dialing attempt to the peer by connection ID of /// the current connection attempt. - pub fn attempt( - &mut self, - id: ConnectionId, - ) -> Option>> { - if let hash_map::Entry::Occupied(attempts) = self.network.dialing.entry(self.peer_id) { - if let Some(pos) = attempts.get().iter().position(|s| s.current.0 == id) { - if let Some(inner) = self.network.pool.get_outgoing(id) { - return Some(DialingAttempt { - pos, - inner, - attempts, - }); - } - } - } - None + pub fn attempt(&mut self, id: ConnectionId) -> Option> { + Some(DialingAttempt { + peer_id: self.peer_id, + inner: self.network.pool.get_outgoing(id)?, + }) } /// Gets an iterator over all dialing (i.e. pending outgoing) connections to the peer. - pub fn attempts(&mut self) -> DialingAttemptIter<'_, THandler, TTrans::Error> { - DialingAttemptIter::new( - &self.peer_id, - &mut self.network.pool, - &mut self.network.dialing, - ) + pub fn attempts(&mut self) -> DialingAttemptIter<'_, THandler, TTrans> { + DialingAttemptIter::new(&self.peer_id, &mut self.network) } /// Obtains some dialing connection to the peer. /// /// At least one dialing connection is guaranteed to exist on a `DialingPeer`. - pub fn some_attempt(&mut self) -> DialingAttempt<'_, THandlerInEvent> { + pub fn some_attempt(&mut self) -> DialingAttempt<'_, THandler> { self.attempts() .into_first() .expect("By `Peer::new` and the definition of `DialingPeer`.") @@ -416,6 +405,7 @@ where impl<'a, TTrans, THandler> fmt::Debug for DialingPeer<'a, TTrans, THandler> where TTrans: Transport, + TTrans::Error: Send + 'static, THandler: IntoConnectionHandler, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { @@ -423,9 +413,12 @@ where .field("peer_id", &self.peer_id) .field( "established", - &self.network.pool.iter_peer_established_info(&self.peer_id), + &self + .network + .pool + .iter_peer_established_info(&self.peer_id) + .collect::>(), ) - .field("attempts", &self.network.dialing.get(&self.peer_id)) .finish() } } @@ -469,29 +462,15 @@ where } } -/// The (internal) state of a `DialingAttempt`, tracking the -/// current connection attempt as well as remaining addresses. -#[derive(Debug, Clone)] -pub(super) struct DialingState { - /// The ID and (remote) address of the current connection attempt. - pub(super) current: (ConnectionId, Multiaddr), - /// Multiaddresses to attempt if the current one fails. - pub(super) remaining: Vec, -} - -/// A `DialingAttempt` is an ongoing outgoing connection attempt to -/// a known / expected remote peer ID and a list of alternative addresses -/// to connect to, if the current connection attempt fails. -pub struct DialingAttempt<'a, TInEvent> { +/// A [`DialingAttempt`] is a pending outgoing connection attempt to a known / +/// expected remote peer ID. +pub struct DialingAttempt<'a, THandler: IntoConnectionHandler> { + peer_id: PeerId, /// The underlying pending connection in the `Pool`. - inner: PendingConnection<'a, TInEvent>, - /// All current dialing attempts of the peer. - attempts: hash_map::OccupiedEntry<'a, PeerId, SmallVec<[DialingState; 10]>>, - /// The position of the current `DialingState` of this connection in the `attempts`. - pos: usize, + inner: PendingConnection<'a, THandler>, } -impl<'a, TInEvent> DialingAttempt<'a, TInEvent> { +impl<'a, THandler: IntoConnectionHandler> DialingAttempt<'a, THandler> { /// Returns the ID of the current connection attempt. pub fn id(&self) -> ConnectionId { self.inner.id() @@ -499,132 +478,65 @@ impl<'a, TInEvent> DialingAttempt<'a, TInEvent> { /// Returns the (expected) peer ID of the dialing attempt. pub fn peer_id(&self) -> &PeerId { - self.attempts.key() - } - - /// Returns the remote address of the current connection attempt. - pub fn address(&self) -> &Multiaddr { - match self.inner.endpoint() { - ConnectedPoint::Dialer { address } => address, - ConnectedPoint::Listener { .. } => unreachable!("by definition of a `DialingAttempt`."), - } + &self.peer_id } /// Aborts the dialing attempt. - /// - /// Aborting a dialing attempt involves aborting the current connection - /// attempt and dropping any remaining addresses given to [`Peer::dial()`] - /// that have not yet been tried. - pub fn abort(mut self) { - self.attempts.get_mut().remove(self.pos); - if self.attempts.get().is_empty() { - self.attempts.remove(); - } + pub fn abort(self) { self.inner.abort(); } - - /// Adds an address to the end of the remaining addresses - /// for this dialing attempt. Duplicates are ignored. - pub fn add_address(&mut self, addr: Multiaddr) { - let remaining = &mut self.attempts.get_mut()[self.pos].remaining; - if remaining.iter().all(|a| a != &addr) { - remaining.push(addr); - } - } } /// An iterator over the ongoing dialing attempts to a peer. -pub struct DialingAttemptIter<'a, THandler: IntoConnectionHandler, TTransErr> { +pub struct DialingAttemptIter<'a, THandler: IntoConnectionHandler, TTrans: Transport> { /// The peer whose dialing attempts are being iterated. peer_id: &'a PeerId, /// The underlying connection `Pool` of the `Network`. - pool: &'a mut Pool, - /// The state of all current dialing attempts known to the `Network`. - /// - /// Ownership of the `OccupiedEntry` for `peer_id` containing all attempts must be - /// borrowed to each `DialingAttempt` in order for it to remove the entry if the - /// last dialing attempt is aborted. - dialing: &'a mut FnvHashMap>, - /// The current position of the iterator in `dialing[peer_id]`. - pos: usize, - /// The total number of elements in `dialing[peer_id]` to iterate over. - end: usize, + pool: &'a mut Pool, + /// [`ConnectionId`]s of the dialing attempts of the peer. + connections: VecDeque, } // Note: Ideally this would be an implementation of `Iterator`, but that // requires GATs (cf. https://github.com/rust-lang/rust/issues/44265) and // a different definition of `Iterator`. -impl<'a, THandler: IntoConnectionHandler, TTransErr> DialingAttemptIter<'a, THandler, TTransErr> { - fn new( - peer_id: &'a PeerId, - pool: &'a mut Pool, - dialing: &'a mut FnvHashMap>, - ) -> Self { - let end = dialing.get(peer_id).map_or(0, |conns| conns.len()); +impl<'a, THandler: IntoConnectionHandler, TTrans: Transport> + DialingAttemptIter<'a, THandler, TTrans> +{ + fn new(peer_id: &'a PeerId, network: &'a mut Network) -> Self { + let connections = network.dialing_attempts(*peer_id).map(|id| *id).collect(); Self { - pos: 0, - end, - pool, - dialing, + pool: &mut network.pool, peer_id, + connections, } } /// Obtains the next dialing connection, if any. #[allow(clippy::should_implement_trait)] - pub fn next(&mut self) -> Option>> { - // If the number of elements reduced, the current `DialingAttempt` has been - // aborted and iteration needs to continue from the previous position to - // account for the removed element. - let end = self - .dialing - .get(self.peer_id) - .map_or(0, |conns| conns.len()); - if self.end > end { - self.end = end; - self.pos -= 1; - } + pub fn next(&mut self) -> Option> { + let connection_id = self.connections.pop_front()?; - if self.pos == self.end { - return None; - } + let inner = self.pool.get_outgoing(connection_id)?; - if let hash_map::Entry::Occupied(attempts) = self.dialing.entry(*self.peer_id) { - let id = attempts.get()[self.pos].current.0; - if let Some(inner) = self.pool.get_outgoing(id) { - let conn = DialingAttempt { - pos: self.pos, - inner, - attempts, - }; - self.pos += 1; - return Some(conn); - } - } - - None + Some(DialingAttempt { + peer_id: *self.peer_id, + inner, + }) } /// Returns the first connection, if any, consuming the iterator. - pub fn into_first<'b>(self) -> Option>> + pub fn into_first<'b>(mut self) -> Option> where 'a: 'b, { - if self.pos == self.end { - return None; - } + let connection_id = self.connections.pop_front()?; - if let hash_map::Entry::Occupied(attempts) = self.dialing.entry(*self.peer_id) { - let id = attempts.get()[self.pos].current.0; - if let Some(inner) = self.pool.get_outgoing(id) { - return Some(DialingAttempt { - pos: self.pos, - inner, - attempts, - }); - } - } + let inner = self.pool.get_outgoing(connection_id)?; - None + Some(DialingAttempt { + peer_id: *self.peer_id, + inner, + }) } } diff --git a/core/tests/concurrent_dialing.rs b/core/tests/concurrent_dialing.rs new file mode 100644 index 00000000000..08b1b5782e0 --- /dev/null +++ b/core/tests/concurrent_dialing.rs @@ -0,0 +1,167 @@ +// Copyright 2021 Protocol Labs. +// +// 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. + +mod util; + +use futures::executor::block_on; +use futures::future::poll_fn; +use futures::ready; +use libp2p_core::{ + multiaddr::Protocol, + network::{NetworkConfig, NetworkEvent}, + ConnectedPoint, +}; +use quickcheck::*; +use rand07::Rng; +use std::num::NonZeroU8; +use std::task::Poll; +use util::{test_network, TestHandler}; + +#[test] +fn concurrent_dialing() { + #[derive(Clone, Debug)] + struct DialConcurrencyFactor(NonZeroU8); + + impl Arbitrary for DialConcurrencyFactor { + fn arbitrary(g: &mut G) -> Self { + Self(NonZeroU8::new(g.gen_range(1, 11)).unwrap()) + } + } + + fn prop(concurrency_factor: DialConcurrencyFactor) { + block_on(async { + let mut network_1 = test_network(NetworkConfig::default()); + let mut network_2 = test_network( + NetworkConfig::default().with_dial_concurrency_factor(concurrency_factor.0), + ); + + // Listen on `concurrency_factor + 1` addresses. + // + // `+ 1` to ensure a subset of addresses is dialed by network_2. + let num_listen_addrs = concurrency_factor.0.get() + 2; + let mut network_1_listen_addresses = Vec::new(); + for _ in 0..num_listen_addrs { + network_1.listen_on("/memory/0".parse().unwrap()).unwrap(); + + poll_fn(|cx| match ready!(network_1.poll(cx)) { + NetworkEvent::NewListenerAddress { listen_addr, .. } => { + network_1_listen_addresses.push(listen_addr); + return Poll::Ready(()); + } + _ => panic!("Expected `NewListenerAddress` event."), + }) + .await; + } + + // Have network 2 dial network 1 and wait for network 1 to receive the incoming + // connections. + network_2 + .peer(*network_1.local_peer_id()) + .dial(network_1_listen_addresses.clone(), TestHandler()) + .unwrap(); + let mut network_1_incoming_connections = Vec::new(); + for i in 0..concurrency_factor.0.get() { + poll_fn(|cx| { + match network_2.poll(cx) { + Poll::Ready(e) => panic!("Unexpected event: {:?}", e), + Poll::Pending => {} + } + + match ready!(network_1.poll(cx)) { + NetworkEvent::IncomingConnection { connection, .. } => { + assert_eq!( + connection.local_addr, network_1_listen_addresses[i as usize], + "Expect network 2 to prioritize by address order." + ); + + network_1_incoming_connections.push(connection); + return Poll::Ready(()); + } + _ => panic!("Expected `NewListenerAddress` event."), + } + }) + .await; + } + + // Have network 1 accept the incoming connection and wait for network 1 and network 2 to + // report a shared established connection. + let accepted_addr = network_1_incoming_connections[0].local_addr.clone(); + network_1 + .accept(network_1_incoming_connections.remove(0), TestHandler()) + .unwrap(); + let mut network_1_connection_established = false; + let mut network_2_connection_established = false; + poll_fn(|cx| { + match network_2.poll(cx) { + Poll::Ready(NetworkEvent::ConnectionEstablished { + connection, + concurrent_dial_errors, + .. + }) => { + match connection.endpoint() { + ConnectedPoint::Dialer { address } => { + assert_eq!( + *address, + accepted_addr + .clone() + .with(Protocol::P2p((*network_1.local_peer_id()).into())) + ) + } + ConnectedPoint::Listener { .. } => panic!("Expected dialer."), + } + assert!(concurrent_dial_errors.unwrap().is_empty()); + network_2_connection_established = true; + if network_1_connection_established { + return Poll::Ready(()); + } + } + Poll::Ready(e) => panic!("Expected `ConnectionEstablished` event: {:?}.", e), + Poll::Pending => {} + } + + match ready!(network_1.poll(cx)) { + NetworkEvent::ConnectionEstablished { + connection, + concurrent_dial_errors, + .. + } => { + match connection.endpoint() { + ConnectedPoint::Listener { local_addr, .. } => { + assert_eq!(*local_addr, accepted_addr) + } + ConnectedPoint::Dialer { .. } => panic!("Expected listener."), + } + assert!(concurrent_dial_errors.is_none()); + network_1_connection_established = true; + if network_2_connection_established { + return Poll::Ready(()); + } + } + e => panic!("Expected `ConnectionEstablished` event: {:?}.", e), + } + + Poll::Pending + }) + .await; + }) + } + + QuickCheck::new().quickcheck(prop as fn(_) -> _); +} diff --git a/core/tests/connection_limits.rs b/core/tests/connection_limits.rs index d5156664ffb..bee42b53055 100644 --- a/core/tests/connection_limits.rs +++ b/core/tests/connection_limits.rs @@ -23,34 +23,38 @@ mod util; use futures::{future::poll_fn, ready}; use libp2p_core::multiaddr::{multiaddr, Multiaddr}; use libp2p_core::{ - connection::ConnectionError, + connection::PendingConnectionError, network::{ConnectionLimits, DialError, NetworkConfig, NetworkEvent}, PeerId, }; -use rand::Rng; +use quickcheck::*; use std::task::Poll; use util::{test_network, TestHandler}; #[test] fn max_outgoing() { - let outgoing_limit = rand::thread_rng().gen_range(1, 10); + use rand::Rng; + + let outgoing_limit = rand::thread_rng().gen_range(1..10); let limits = ConnectionLimits::default().with_max_pending_outgoing(Some(outgoing_limit)); let cfg = NetworkConfig::default().with_connection_limits(limits); let mut network = test_network(cfg); + let addr: Multiaddr = "/memory/1234".parse().unwrap(); + let target = PeerId::random(); for _ in 0..outgoing_limit { network .peer(target.clone()) - .dial(Multiaddr::empty(), Vec::new(), TestHandler()) + .dial(vec![addr.clone()], TestHandler()) .ok() .expect("Unexpected connection limit."); } match network .peer(target.clone()) - .dial(Multiaddr::empty(), Vec::new(), TestHandler()) + .dial(vec![addr.clone()], TestHandler()) .expect_err("Unexpected dialing success.") { DialError::ConnectionLimit { limit, handler: _ } => { @@ -86,89 +90,123 @@ fn max_outgoing() { #[test] fn max_established_incoming() { - let limit = rand::thread_rng().gen_range(1, 10); + use rand07::Rng; + + #[derive(Debug, Clone)] + struct Limit(u32); + + impl Arbitrary for Limit { + fn arbitrary(g: &mut G) -> Self { + Self(g.gen_range(1, 10)) + } + } fn config(limit: u32) -> NetworkConfig { let limits = ConnectionLimits::default().with_max_established_incoming(Some(limit)); NetworkConfig::default().with_connection_limits(limits) } - let mut network1 = test_network(config(limit)); - let mut network2 = test_network(config(limit)); - - let listen_addr = multiaddr![Ip4(std::net::Ipv4Addr::new(127, 0, 0, 1)), Tcp(0u16)]; - let _ = network1.listen_on(listen_addr.clone()).unwrap(); - let (addr_sender, addr_receiver) = futures::channel::oneshot::channel(); - let mut addr_sender = Some(addr_sender); - - // Spawn the listener. - let listener = async_std::task::spawn(poll_fn(move |cx| loop { - match ready!(network1.poll(cx)) { - NetworkEvent::NewListenerAddress { listen_addr, .. } => { - addr_sender.take().unwrap().send(listen_addr).unwrap(); - } - NetworkEvent::IncomingConnection { connection, .. } => { - network1.accept(connection, TestHandler()).unwrap(); - } - NetworkEvent::ConnectionEstablished { .. } => {} - NetworkEvent::ConnectionClosed { - error: Some(ConnectionError::ConnectionLimit(err)), - .. - } => { - assert_eq!(err.limit, limit); - assert_eq!(err.limit, err.current); - let info = network1.info(); - let counters = info.connection_counters(); - assert_eq!(counters.num_established_incoming(), limit); - assert_eq!(counters.num_established(), limit); - return Poll::Ready(()); - } - e => panic!("Unexpected network event: {:?}", e), - } - })); - - // Spawn and block on the dialer. - async_std::task::block_on(async move { - let addr = addr_receiver.await.unwrap(); - let mut n = 0; - let _ = network2.dial(&addr, TestHandler()).unwrap(); - let mut expected_closed = None; - poll_fn(|cx| { - loop { - match ready!(network2.poll(cx)) { - NetworkEvent::ConnectionEstablished { connection, .. } => { - n += 1; + fn prop(limit: Limit) { + let limit = limit.0; + + let mut network1 = test_network(config(limit)); + let mut network2 = test_network(config(limit)); + + let _ = network1.listen_on(multiaddr![Memory(0u64)]).unwrap(); + let listen_addr = + async_std::task::block_on(poll_fn(|cx| match ready!(network1.poll(cx)) { + NetworkEvent::NewListenerAddress { listen_addr, .. } => Poll::Ready(listen_addr), + e => panic!("Unexpected network event: {:?}", e), + })); + + // Spawn and block on the dialer. + async_std::task::block_on({ + let mut n = 0; + let _ = network2.dial(&listen_addr, TestHandler()).unwrap(); + + let mut expected_closed = false; + let mut network_1_established = false; + let mut network_2_established = false; + let mut network_1_limit_reached = false; + let mut network_2_limit_reached = false; + poll_fn(move |cx| { + loop { + let mut network_1_pending = false; + let mut network_2_pending = false; + + match network1.poll(cx) { + Poll::Ready(NetworkEvent::IncomingConnection { connection, .. }) => { + network1.accept(connection, TestHandler()).unwrap(); + } + Poll::Ready(NetworkEvent::ConnectionEstablished { .. }) => { + network_1_established = true; + } + Poll::Ready(NetworkEvent::IncomingConnectionError { + error: PendingConnectionError::ConnectionLimit(err), + .. + }) => { + assert_eq!(err.limit, limit); + assert_eq!(err.limit, err.current); + let info = network1.info(); + let counters = info.connection_counters(); + assert_eq!(counters.num_established_incoming(), limit); + assert_eq!(counters.num_established(), limit); + network_1_limit_reached = true; + } + Poll::Pending => { + network_1_pending = true; + } + e => panic!("Unexpected network event: {:?}", e), + } + + match network2.poll(cx) { + Poll::Ready(NetworkEvent::ConnectionEstablished { .. }) => { + network_2_established = true; + } + Poll::Ready(NetworkEvent::ConnectionClosed { .. }) => { + assert!(expected_closed); + let info = network2.info(); + let counters = info.connection_counters(); + assert_eq!(counters.num_established_outgoing(), limit); + assert_eq!(counters.num_established(), limit); + network_2_limit_reached = true; + } + Poll::Pending => { + network_2_pending = true; + } + e => panic!("Unexpected network event: {:?}", e), + } + + if network_1_pending && network_2_pending { + return Poll::Pending; + } + + if network_1_established && network_2_established { + network_1_established = false; + network_2_established = false; + if n <= limit { // Dial again until the limit is exceeded. - let id = network2.dial(&addr, TestHandler()).unwrap(); + n += 1; + network2.dial(&listen_addr, TestHandler()).unwrap(); + if n == limit { // The the next dialing attempt exceeds the limit, this // is the connection we expected to get closed. - expected_closed = Some(id); + expected_closed = true; } } else { - // This connection exceeds the limit for the listener and - // is expected to close shortly. For the dialer, these connections - // will first appear established before the listener closes them as - // a result of the limit violation. - assert_eq!(Some(connection.id()), expected_closed); + panic!("Expect networks not to establish connections beyond the limit.") } } - NetworkEvent::ConnectionClosed { id, .. } => { - assert_eq!(Some(id), expected_closed); - let info = network2.info(); - let counters = info.connection_counters(); - assert_eq!(counters.num_established_outgoing(), limit); - assert_eq!(counters.num_established(), limit); + + if network_1_limit_reached && network_2_limit_reached { return Poll::Ready(()); } - e => panic!("Unexpected network event: {:?}", e), } - } - }) - .await - }); + }) + }); + } - // Wait for the listener to complete. - async_std::task::block_on(listener); + quickcheck(prop as fn(_)); } diff --git a/core/tests/network_dial_error.rs b/core/tests/network_dial_error.rs index bed4c06e023..827db92e444 100644 --- a/core/tests/network_dial_error.rs +++ b/core/tests/network_dial_error.rs @@ -39,9 +39,7 @@ fn deny_incoming_connec() { let mut swarm1 = test_network(NetworkConfig::default()); let mut swarm2 = test_network(NetworkConfig::default()); - swarm1 - .listen_on("/ip4/127.0.0.1/tcp/0".parse().unwrap()) - .unwrap(); + swarm1.listen_on("/memory/0".parse().unwrap()).unwrap(); let address = async_std::task::block_on(future::poll_fn(|cx| match swarm1.poll(cx) { Poll::Ready(NetworkEvent::NewListenerAddress { listen_addr, .. }) => { @@ -53,7 +51,7 @@ fn deny_incoming_connec() { swarm2 .peer(swarm1.local_peer_id().clone()) - .dial(address.clone(), Vec::new(), TestHandler()) + .dial(vec![address.clone()], TestHandler()) .unwrap(); async_std::task::block_on(future::poll_fn(|cx| -> Poll> { @@ -65,15 +63,13 @@ fn deny_incoming_connec() { match swarm2.poll(cx) { Poll::Ready(NetworkEvent::DialError { - attempts_remaining, peer_id, - multiaddr, - error: PendingConnectionError::Transport(_), + error: PendingConnectionError::Transport(errors), + handler: _, }) => { - assert_eq!(0u32, attempts_remaining.get_attempts()); assert_eq!(&peer_id, swarm1.local_peer_id()); assert_eq!( - multiaddr, + errors.get(0).expect("One error.").0, address.clone().with(Protocol::P2p(peer_id.into())) ); return Poll::Ready(Ok(())); @@ -100,9 +96,7 @@ fn dial_self() { // The last two can happen in any order. let mut swarm = test_network(NetworkConfig::default()); - swarm - .listen_on("/ip4/127.0.0.1/tcp/0".parse().unwrap()) - .unwrap(); + swarm.listen_on("/memory/0".parse().unwrap()).unwrap(); let local_address = async_std::task::block_on(future::poll_fn(|cx| match swarm.poll(cx) { Poll::Ready(NetworkEvent::NewListenerAddress { listen_addr, .. }) => { @@ -119,13 +113,13 @@ fn dial_self() { async_std::task::block_on(future::poll_fn(|cx| -> Poll> { loop { match swarm.poll(cx) { - Poll::Ready(NetworkEvent::UnknownPeerDialError { - multiaddr, + Poll::Ready(NetworkEvent::DialError { + peer_id, error: PendingConnectionError::InvalidPeerId { .. }, .. }) => { + assert_eq!(&peer_id, swarm.local_peer_id()); assert!(!got_dial_err); - assert_eq!(multiaddr, local_address); got_dial_err = true; if got_inc_err { return Poll::Ready(Ok(())); @@ -179,34 +173,34 @@ fn multiple_addresses_err() { } addresses.shuffle(&mut rand::thread_rng()); - let first = addresses[0].clone(); - let rest = (&addresses[1..]).iter().cloned(); - swarm .peer(target.clone()) - .dial(first, rest, TestHandler()) + .dial(addresses.clone(), TestHandler()) .unwrap(); async_std::task::block_on(future::poll_fn(|cx| -> Poll> { loop { match swarm.poll(cx) { Poll::Ready(NetworkEvent::DialError { - attempts_remaining, peer_id, - multiaddr, - error: PendingConnectionError::Transport(_), + // multiaddr, + error: PendingConnectionError::Transport(errors), + handler: _, }) => { assert_eq!(peer_id, target); - let expected = addresses - .remove(0) - .with(Protocol::P2p(target.clone().into())); - assert_eq!(multiaddr, expected); - if addresses.is_empty() { - assert_eq!(attempts_remaining.get_attempts(), 0); - return Poll::Ready(Ok(())); - } else { - assert_eq!(attempts_remaining.get_attempts(), addresses.len() as u32); - } + + let failed_addresses = + errors.into_iter().map(|(addr, _)| addr).collect::>(); + assert_eq!( + failed_addresses, + addresses + .clone() + .into_iter() + .map(|addr| addr.with(Protocol::P2p(target.into()))) + .collect::>() + ); + + return Poll::Ready(Ok(())); } Poll::Ready(_) => unreachable!(), Poll::Pending => break Poll::Pending, diff --git a/core/tests/util.rs b/core/tests/util.rs index 0c175448336..9592daca9eb 100644 --- a/core/tests/util.rs +++ b/core/tests/util.rs @@ -6,11 +6,11 @@ use libp2p_core::{ identity, muxing::{StreamMuxer, StreamMuxerBox}, network::{Network, NetworkConfig}, - transport, upgrade, Multiaddr, PeerId, Transport, + transport::{self, memory::MemoryTransport}, + upgrade, Multiaddr, PeerId, Transport, }; use libp2p_mplex as mplex; use libp2p_noise as noise; -use libp2p_tcp as tcp; use std::{io, pin::Pin, task::Context, task::Poll}; type TestNetwork = Network; @@ -23,7 +23,7 @@ pub fn test_network(cfg: NetworkConfig) -> TestNetwork { let noise_keys = noise::Keypair::::new() .into_authentic(&local_key) .unwrap(); - let transport: TestTransport = tcp::TcpConfig::new() + let transport: TestTransport = MemoryTransport::default() .upgrade(upgrade::Version::V1) .authenticate(noise::NoiseConfig::xx(noise_keys).into_authenticated()) .multiplex(mplex::MplexConfig::new()) @@ -32,6 +32,7 @@ pub fn test_network(cfg: NetworkConfig) -> TestNetwork { TestNetwork::new(transport, local_public_key.into(), cfg) } +#[derive(Debug)] pub struct TestHandler(); impl ConnectionHandler for TestHandler { diff --git a/examples/file-sharing.rs b/examples/file-sharing.rs index 65ba87feaef..f7d7abb8356 100644 --- a/examples/file-sharing.rs +++ b/examples/file-sharing.rs @@ -492,13 +492,8 @@ mod network { } } SwarmEvent::ConnectionClosed { .. } => {} - SwarmEvent::UnreachableAddr { - peer_id, - attempts_remaining, - error, - .. - } => { - if attempts_remaining == 0 { + SwarmEvent::OutgoingConnectionError { peer_id, error, .. } => { + if let Some(peer_id) = peer_id { if let Some(sender) = self.pending_dial.remove(&peer_id) { let _ = sender.send(Err(Box::new(error))); } diff --git a/misc/metrics/CHANGELOG.md b/misc/metrics/CHANGELOG.md index cad93476b9a..23ef43d5dd1 100644 --- a/misc/metrics/CHANGELOG.md +++ b/misc/metrics/CHANGELOG.md @@ -1,3 +1,3 @@ -## Version 0.1.0 [unreleased] +## Version 0.1.0 [2021-10-15] - Add initial version. \ No newline at end of file diff --git a/misc/metrics/Cargo.toml b/misc/metrics/Cargo.toml index f34c33d9d94..7e9fb94c5cb 100644 --- a/misc/metrics/Cargo.toml +++ b/misc/metrics/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-metrics" edition = "2018" description = "Metrics for libp2p" -version = "0.1.0" +version = "0.1.0-rc.1" authors = ["Max Inden "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -15,11 +15,11 @@ kad = ["libp2p-kad"] ping = ["libp2p-ping"] [dependencies] -libp2p-core= { version = "0.30.0", path = "../../core" } -libp2p-identify = { version = "0.31.0", path = "../../protocols/identify", optional = true } -libp2p-kad = { version = "0.32.0", path = "../../protocols/kad", optional = true } -libp2p-ping = { version = "0.31.0", path = "../../protocols/ping", optional = true } -libp2p-swarm = { version = "0.31.0", path = "../../swarm" } +libp2p-core= { version = "0.30.0-rc.1", path = "../../core" } +libp2p-identify = { version = "0.31.0-rc.1", path = "../../protocols/identify", optional = true } +libp2p-kad = { version = "0.32.0-rc.1", path = "../../protocols/kad", optional = true } +libp2p-ping = { version = "0.31.0-rc.1", path = "../../protocols/ping", optional = true } +libp2p-swarm = { version = "0.31.0-rc.1", path = "../../swarm" } open-metrics-client = "0.12.0" [dev-dependencies] diff --git a/misc/metrics/examples/metrics.rs b/misc/metrics/examples/metrics.rs index f7ad8cf0fc6..3671664a826 100644 --- a/misc/metrics/examples/metrics.rs +++ b/misc/metrics/examples/metrics.rs @@ -106,7 +106,11 @@ pub async fn metrics_server(registry: Registry) -> std::result::Result<(), std:: .get(|req: tide::Request>>| async move { let mut encoded = Vec::new(); encode(&mut encoded, &req.state().lock().unwrap()).unwrap(); - Ok(String::from_utf8(encoded).unwrap()) + let response = tide::Response::builder(200) + .body(encoded) + .content_type("application/openmetrics-text; version=1.0.0; charset=utf-8") + .build(); + Ok(response) }); app.listen("0.0.0.0:0").await?; diff --git a/misc/metrics/src/swarm.rs b/misc/metrics/src/swarm.rs index a0c9c72fedf..ea6f3da20b8 100644 --- a/misc/metrics/src/swarm.rs +++ b/misc/metrics/src/swarm.rs @@ -37,7 +37,7 @@ pub struct Metrics { listener_error: Counter, dial_attempt: Counter, - dial_unreachable_addr: Family, Counter>, + outgoing_connection_error: Family, connected_to_banned_peer: Counter, } @@ -94,11 +94,11 @@ impl Metrics { Box::new(dial_attempt.clone()), ); - let dial_unreachable_addr = Family::default(); + let outgoing_connection_error = Family::default(); sub_registry.register( - "dial_unreachable_addr", - "Number of unreachable addresses dialed", - Box::new(dial_unreachable_addr.clone()), + "outgoing_connection_error", + "Number outgoing connection errors", + Box::new(outgoing_connection_error.clone()), ); let connected_to_banned_peer = Counter::default(); @@ -132,7 +132,7 @@ impl Metrics { listener_closed, listener_error, dial_attempt, - dial_unreachable_addr, + outgoing_connection_error, connected_to_banned_peer, } } @@ -171,21 +171,62 @@ impl super::Recorder { + let peer = match peer_id { + Some(_) => PeerStatus::Known, + None => PeerStatus::Unknown, + }; + + let record = |error| { + self.swarm + .outgoing_connection_error + .get_or_create(&OutgoingConnectionErrorLabels { peer, error }) + .inc(); + }; + + match error { + libp2p_swarm::DialError::Transport(errors) => { + for (_multiaddr, error) in errors { + match error { + libp2p_core::transport::TransportError::MultiaddrNotSupported( + _, + ) => record( + OutgoingConnectionErrorError::TransportMultiaddrNotSupported, + ), + libp2p_core::transport::TransportError::Other(_) => { + record(OutgoingConnectionErrorError::TransportOther) + } + }; + } + } + + libp2p_swarm::DialError::Banned => record(OutgoingConnectionErrorError::Banned), + libp2p_swarm::DialError::ConnectionLimit(_) => { + record(OutgoingConnectionErrorError::ConnectionLimit) + } + libp2p_swarm::DialError::LocalPeerId => { + record(OutgoingConnectionErrorError::LocalPeerId) + } + libp2p_swarm::DialError::NoAddresses => { + record(OutgoingConnectionErrorError::NoAddresses) + } + libp2p_swarm::DialError::DialPeerConditionFalse(_) => { + record(OutgoingConnectionErrorError::DialPeerConditionFalse) + } + libp2p_swarm::DialError::Aborted => { + record(OutgoingConnectionErrorError::Aborted) + } + libp2p_swarm::DialError::InvalidPeerId => { + record(OutgoingConnectionErrorError::InvalidPeerId) + } + libp2p_swarm::DialError::ConnectionIo(_) => { + record(OutgoingConnectionErrorError::ConnectionIo) + } + }; + } libp2p_swarm::SwarmEvent::BannedPeer { .. } => { self.swarm.connected_to_banned_peer.inc(); } - libp2p_swarm::SwarmEvent::UnreachableAddr { .. } => { - self.swarm - .dial_unreachable_addr - .get_or_create(&vec![("peer".into(), "known".into())]) - .inc(); - } - libp2p_swarm::SwarmEvent::UnknownPeerUnreachableAddr { .. } => { - self.swarm - .dial_unreachable_addr - .get_or_create(&vec![("peer".into(), "unknown".into())]) - .inc(); - } libp2p_swarm::SwarmEvent::NewListenAddr { .. } => { self.swarm.new_listen_addr.inc(); } @@ -230,38 +271,70 @@ impl From<&libp2p_core::ConnectedPoint> for Role { } } +#[derive(Encode, Hash, Clone, Eq, PartialEq)] +struct OutgoingConnectionErrorLabels { + peer: PeerStatus, + error: OutgoingConnectionErrorError, +} + +#[derive(Encode, Hash, Clone, Eq, PartialEq, Copy)] +enum PeerStatus { + Known, + Unknown, +} + +#[derive(Encode, Hash, Clone, Eq, PartialEq)] +enum OutgoingConnectionErrorError { + Banned, + ConnectionLimit, + LocalPeerId, + NoAddresses, + DialPeerConditionFalse, + Aborted, + InvalidPeerId, + ConnectionIo, + TransportMultiaddrNotSupported, + TransportOther, +} + #[derive(Encode, Hash, Clone, Eq, PartialEq)] struct IncomingConnectionErrorLabels { - error: PendingConnectionError, + error: PendingInboundConnectionError, } #[derive(Encode, Hash, Clone, Eq, PartialEq)] -enum PendingConnectionError { +enum PendingInboundConnectionError { InvalidPeerId, TransportErrorMultiaddrNotSupported, TransportErrorOther, Aborted, Io, + ConnectionLimit, } -impl From<&libp2p_core::connection::PendingConnectionError> - for PendingConnectionError +impl From<&libp2p_core::connection::PendingInboundConnectionError> + for PendingInboundConnectionError { - fn from(point: &libp2p_core::connection::PendingConnectionError) -> Self { - match point { - libp2p_core::connection::PendingConnectionError::InvalidPeerId => { - PendingConnectionError::InvalidPeerId + fn from(error: &libp2p_core::connection::PendingInboundConnectionError) -> Self { + match error { + libp2p_core::connection::PendingInboundConnectionError::InvalidPeerId => { + PendingInboundConnectionError::InvalidPeerId } - libp2p_core::connection::PendingConnectionError::Transport( + libp2p_core::connection::PendingInboundConnectionError::ConnectionLimit(_) => { + PendingInboundConnectionError::ConnectionLimit + } + libp2p_core::connection::PendingInboundConnectionError::Transport( libp2p_core::transport::TransportError::MultiaddrNotSupported(_), - ) => PendingConnectionError::TransportErrorMultiaddrNotSupported, - libp2p_core::connection::PendingConnectionError::Transport( + ) => PendingInboundConnectionError::TransportErrorMultiaddrNotSupported, + libp2p_core::connection::PendingInboundConnectionError::Transport( libp2p_core::transport::TransportError::Other(_), - ) => PendingConnectionError::TransportErrorOther, - libp2p_core::connection::PendingConnectionError::Aborted => { - PendingConnectionError::Aborted + ) => PendingInboundConnectionError::TransportErrorOther, + libp2p_core::connection::PendingInboundConnectionError::Aborted => { + PendingInboundConnectionError::Aborted + } + libp2p_core::connection::PendingInboundConnectionError::IO(_) => { + PendingInboundConnectionError::Io } - libp2p_core::connection::PendingConnectionError::IO(_) => PendingConnectionError::Io, } } } diff --git a/misc/multistream-select/CHANGELOG.md b/misc/multistream-select/CHANGELOG.md index 7a10a7267f5..2c875b4d57a 100644 --- a/misc/multistream-select/CHANGELOG.md +++ b/misc/multistream-select/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.10.4 [unreleased] +# 0.10.4-rc.1 [2021-10-15] - Implement `From for ProtocolError` instead of `Into`. [PR 2169](https://github.com/libp2p/rust-libp2p/pull/2169) diff --git a/misc/multistream-select/tests/transport.rs b/misc/multistream-select/tests/transport.rs index f42797f13ca..1c48af37715 100644 --- a/misc/multistream-select/tests/transport.rs +++ b/misc/multistream-select/tests/transport.rs @@ -106,6 +106,7 @@ fn transport_upgrade() { run(upgrade::Version::V1Lazy); } +#[derive(Debug)] struct TestHandler(); impl ConnectionHandler for TestHandler { diff --git a/muxers/mplex/CHANGELOG.md b/muxers/mplex/CHANGELOG.md index 6cc5d5c6b0b..3ca0f23d9f8 100644 --- a/muxers/mplex/CHANGELOG.md +++ b/muxers/mplex/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.30.0 [unreleased] +# 0.30.0-rc.1 [2021-10-15] - Make default features of `libp2p-core` optional. [PR 2181](https://github.com/libp2p/rust-libp2p/pull/2181) diff --git a/muxers/mplex/Cargo.toml b/muxers/mplex/Cargo.toml index 285fe386517..9d036004461 100644 --- a/muxers/mplex/Cargo.toml +++ b/muxers/mplex/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-mplex" edition = "2018" description = "Mplex multiplexing protocol for libp2p" -version = "0.30.0" +version = "0.30.0-rc.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -13,7 +13,7 @@ categories = ["network-programming", "asynchronous"] bytes = "1" futures = "0.3.1" asynchronous-codec = "0.6" -libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } +libp2p-core = { version = "0.30.0-rc.1", path = "../../core", default-features = false } log = "0.4" nohash-hasher = "0.2" parking_lot = "0.11" diff --git a/muxers/yamux/CHANGELOG.md b/muxers/yamux/CHANGELOG.md index 7ca9c0bbbb6..1b66a650858 100644 --- a/muxers/yamux/CHANGELOG.md +++ b/muxers/yamux/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.34.0 [unreleased] +# 0.34.0-rc.1 [2021-10-15] - Make default features of `libp2p-core` optional. [PR 2181](https://github.com/libp2p/rust-libp2p/pull/2181) diff --git a/muxers/yamux/Cargo.toml b/muxers/yamux/Cargo.toml index c9984b6514a..65b7767c039 100644 --- a/muxers/yamux/Cargo.toml +++ b/muxers/yamux/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-yamux" edition = "2018" description = "Yamux multiplexing protocol for libp2p" -version = "0.34.0" +version = "0.34.0-rc.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -11,7 +11,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] futures = "0.3.1" -libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } +libp2p-core = { version = "0.30.0-rc.1", path = "../../core", default-features = false } parking_lot = "0.11" thiserror = "1.0" yamux = "0.9.0" diff --git a/protocols/autonat/Cargo.toml b/protocols/autonat/Cargo.toml index a60c4c3be82..da22e162605 100644 --- a/protocols/autonat/Cargo.toml +++ b/protocols/autonat/Cargo.toml @@ -14,7 +14,7 @@ prost-build = "0.6" [dependencies] async-trait = "0.1.51" futures = "0.3.17" -libp2p-core = { version = "0.30.0", path = "../../core" } -libp2p-swarm = { version = "0.31.0", path = "../../swarm" } -libp2p-request-response = { version = "0.13.0", path = "../request-response" } +libp2p-core = { version = "0.30.0-rc.1", path = "../../core" } +libp2p-swarm = { version = "0.31.0-rc.1", path = "../../swarm" } +libp2p-request-response = { version = "0.13.0-rc.1", path = "../request-response" } prost = "0.8.0" diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index f16355535d3..8e1280d1167 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -27,10 +27,7 @@ use libp2p_request_response::{ handler::RequestResponseHandlerEvent, ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent, RequestResponseMessage, ResponseChannel, }; -use libp2p_swarm::{ - AddressScore, DialPeerCondition, IntoProtocolsHandler, NetworkBehaviour, - NetworkBehaviourAction, PollParameters, -}; +use libp2p_swarm::{AddressScore, DialError, DialPeerCondition, IntoProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction, PollParameters}; use std::{ collections::{HashMap, HashSet, VecDeque}, iter, @@ -140,9 +137,10 @@ impl NetworkBehaviour for Behaviour { peer: &PeerId, conn: &ConnectionId, endpoint: &ConnectedPoint, + failed_addresses: Option<&Vec>, ) { self.inner - .inject_connection_established(peer, conn, endpoint); + .inject_connection_established(peer, conn, endpoint, failed_addresses); // Initiate a new dial request if there is none pending. if !self.ongoing_outbound.contains(peer) { @@ -190,23 +188,14 @@ impl NetworkBehaviour for Behaviour { self.inner.inject_event(peer_id, conn, event) } - fn inject_addr_reach_failure( - &mut self, - peer_id: Option<&PeerId>, - addr: &Multiaddr, - error: &dyn std::error::Error, - ) { - self.inner.inject_addr_reach_failure(peer_id, addr, error) - } - fn inject_dial_failure( &mut self, - peer_id: &PeerId, + peer_id: Option, handler: Self::ProtocolsHandler, - error: libp2p_swarm::DialError, + error: &DialError, ) { self.inner.inject_dial_failure(peer_id, handler, error); - if let Some(channel) = self.ongoing_inbound.remove(peer_id) { + if let Some(channel) = peer_id.and_then(|p| self.ongoing_inbound.remove(&p)) { // Failed to dial any of the addresses sent by the remote peer in their dial-request. let _ = self .inner diff --git a/protocols/floodsub/CHANGELOG.md b/protocols/floodsub/CHANGELOG.md index c0f65131c1a..1594e5d6787 100644 --- a/protocols/floodsub/CHANGELOG.md +++ b/protocols/floodsub/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.31.0 [unreleased] +# 0.31.0-rc.1 [2021-10-15] - Make default features of `libp2p-core` optional. [PR 2181](https://github.com/libp2p/rust-libp2p/pull/2181) diff --git a/protocols/floodsub/Cargo.toml b/protocols/floodsub/Cargo.toml index 0b8bf67b0b2..c70fb2214ce 100644 --- a/protocols/floodsub/Cargo.toml +++ b/protocols/floodsub/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-floodsub" edition = "2018" description = "Floodsub protocol for libp2p" -version = "0.31.0" +version = "0.31.0-rc.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -13,12 +13,12 @@ categories = ["network-programming", "asynchronous"] cuckoofilter = "0.5.0" fnv = "1.0" futures = "0.3.1" -libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } -libp2p-swarm = { version = "0.31.0", path = "../../swarm" } +libp2p-core = { version = "0.30.0-rc.1", path = "../../core", default-features = false } +libp2p-swarm = { version = "0.31.0-rc.1", path = "../../swarm" } log = "0.4" -prost = "0.8" +prost = "0.9" rand = "0.7" smallvec = "1.6.1" [build-dependencies] -prost-build = "0.8" +prost-build = "0.9" diff --git a/protocols/gossipsub/CHANGELOG.md b/protocols/gossipsub/CHANGELOG.md index 9cf9c8db4bd..a755c375f01 100644 --- a/protocols/gossipsub/CHANGELOG.md +++ b/protocols/gossipsub/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.33.0 [unreleased] +# 0.33.0-rc.1 [2021-10-15] - Add an event to register peers that do not support the gossipsub protocol [PR 2241](https://github.com/libp2p/rust-libp2p/pull/2241) @@ -14,6 +14,9 @@ - Allow `message_id_fn`s to accept closures that capture variables. [PR 2103](https://github.com/libp2p/rust-libp2p/pull/2103) +- Implement std::error::Error for error types. + [PR 2254](https://github.com/libp2p/rust-libp2p/pull/2254) + # 0.32.0 [2021-07-12] - Update dependencies. diff --git a/protocols/gossipsub/Cargo.toml b/protocols/gossipsub/Cargo.toml index 115051a5ef4..825c174b9f3 100644 --- a/protocols/gossipsub/Cargo.toml +++ b/protocols/gossipsub/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-gossipsub" edition = "2018" description = "Gossipsub protocol for libp2p" -version = "0.33.0" +version = "0.33.0-rc.1" authors = ["Age Manning "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -10,8 +10,8 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -libp2p-swarm = { version = "0.31.0", path = "../../swarm" } -libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } +libp2p-swarm = { version = "0.31.0-rc.1", path = "../../swarm" } +libp2p-core = { version = "0.30.0-rc.1", path = "../../core", default-features = false } bytes = "1.0" byteorder = "1.3.4" fnv = "1.0.7" @@ -24,7 +24,7 @@ log = "0.4.11" sha2 = "0.9.1" base64 = "0.13.0" smallvec = "1.6.1" -prost = "0.8" +prost = "0.9" hex_fmt = "0.3.0" regex = "1.4.0" @@ -40,4 +40,4 @@ hex = "0.4.2" derive_builder = "0.10.0" [build-dependencies] -prost-build = "0.8" +prost-build = "0.9" diff --git a/protocols/gossipsub/src/behaviour.rs b/protocols/gossipsub/src/behaviour.rs index 65c551a2f3c..9c2feb77765 100644 --- a/protocols/gossipsub/src/behaviour.rs +++ b/protocols/gossipsub/src/behaviour.rs @@ -2927,6 +2927,7 @@ where peer_id: &PeerId, connection_id: &ConnectionId, endpoint: &ConnectedPoint, + _: Option<&Vec>, ) { // Check if the peer is an outbound peer if let ConnectedPoint::Dialer { .. } = endpoint { diff --git a/protocols/gossipsub/src/behaviour/tests.rs b/protocols/gossipsub/src/behaviour/tests.rs index 5794f2e0054..d22ce8a3e6d 100644 --- a/protocols/gossipsub/src/behaviour/tests.rs +++ b/protocols/gossipsub/src/behaviour/tests.rs @@ -195,6 +195,7 @@ mod tests { send_back_addr: address, } }, + None, ); as NetworkBehaviour>::inject_connected(gs, &peer); if let Some(kind) = kind { @@ -533,6 +534,7 @@ mod tests { &ConnectedPoint::Dialer { address: "/ip4/127.0.0.1".parse::().unwrap(), }, + None, ); gs.inject_connected(&random_peer); @@ -4085,6 +4087,7 @@ mod tests { &ConnectedPoint::Dialer { address: addr.clone(), }, + None, ); } @@ -4103,6 +4106,7 @@ mod tests { &ConnectedPoint::Dialer { address: addr2.clone(), }, + None, ); } @@ -4130,6 +4134,7 @@ mod tests { &ConnectedPoint::Dialer { address: addr.clone(), }, + None, ); //nothing changed diff --git a/protocols/gossipsub/src/error.rs b/protocols/gossipsub/src/error.rs index 7ea5ff431ba..e469a5a6d51 100644 --- a/protocols/gossipsub/src/error.rs +++ b/protocols/gossipsub/src/error.rs @@ -40,6 +40,22 @@ pub enum PublishError { TransformFailed(std::io::Error), } +impl std::fmt::Display for PublishError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for PublishError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::SigningError(err) => Some(err), + Self::TransformFailed(err) => Some(err), + _ => None, + } + } +} + /// Error associated with subscribing to a topic. #[derive(Debug)] pub enum SubscriptionError { @@ -49,6 +65,21 @@ pub enum SubscriptionError { NotAllowed, } +impl std::fmt::Display for SubscriptionError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for SubscriptionError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::PublishError(err) => Some(err), + _ => None, + } + } +} + impl From for PublishError { fn from(error: SigningError) -> Self { PublishError::SigningError(error) @@ -95,6 +126,14 @@ pub enum ValidationError { TransformFailed, } +impl std::fmt::Display for ValidationError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for ValidationError {} + impl From for GossipsubHandlerError { fn from(error: std::io::Error) -> GossipsubHandlerError { GossipsubHandlerError::Io(error) diff --git a/protocols/identify/CHANGELOG.md b/protocols/identify/CHANGELOG.md index b05cafa75e8..9611597c067 100644 --- a/protocols/identify/CHANGELOG.md +++ b/protocols/identify/CHANGELOG.md @@ -1,10 +1,14 @@ -# 0.31.0 [unreleased] +# 0.31.0-rc.1 [2021-10-15] - Make default features of `libp2p-core` optional. [PR 2181](https://github.com/libp2p/rust-libp2p/pull/2181) - Update dependencies. +- Assist in peer discovery by returning reported listen addresses + of other peers from `addresses_of_peer`. + [PR 2232](https://github.com/libp2p/rust-libp2p/pull/2232) + # 0.30.0 [2021-07-12] - Update dependencies. diff --git a/protocols/identify/Cargo.toml b/protocols/identify/Cargo.toml index 6f6c37d12e2..7afcb590078 100644 --- a/protocols/identify/Cargo.toml +++ b/protocols/identify/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-identify" edition = "2018" description = "Nodes identifcation protocol for libp2p" -version = "0.31.0" +version = "0.31.0-rc.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -11,10 +11,11 @@ categories = ["network-programming", "asynchronous"] [dependencies] futures = "0.3.1" -libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } -libp2p-swarm = { version = "0.31.0", path = "../../swarm" } +libp2p-core = { version = "0.30.0-rc.1", path = "../../core", default-features = false } +libp2p-swarm = { version = "0.31.0-rc.1", path = "../../swarm" } log = "0.4.1" -prost = "0.8" +lru = "0.6" +prost = "0.9" smallvec = "1.6.1" wasm-timer = "0.2" @@ -26,4 +27,4 @@ libp2p-noise = { path = "../../transports/noise" } libp2p-tcp = { path = "../../transports/tcp" } [build-dependencies] -prost-build = "0.8" +prost-build = "0.9" diff --git a/protocols/identify/src/identify.rs b/protocols/identify/src/identify.rs index d75dfb72054..8a5bfc7613e 100644 --- a/protocols/identify/src/identify.rs +++ b/protocols/identify/src/identify.rs @@ -31,9 +31,11 @@ use libp2p_swarm::{ NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters, ProtocolsHandler, ProtocolsHandlerUpgrErr, }; +use lru::LruCache; use std::{ collections::{HashMap, HashSet, VecDeque}, io, + iter::FromIterator, pin::Pin, task::Context, task::Poll, @@ -57,6 +59,8 @@ pub struct Identify { /// Peers to which an active push with current information about /// the local peer should be sent. pending_push: HashSet, + /// The addresses of all peers that we have discovered. + discovered_peers: LruCache>, } /// A pending reply to an inbound identification request. @@ -109,6 +113,10 @@ pub struct IdentifyConfig { /// /// Disabled by default. pub push_listen_addr_updates: bool, + + /// How many entries of discovered peers to keep before we discard + /// the least-recently used one. + pub cache_size: usize, } impl IdentifyConfig { @@ -122,6 +130,7 @@ impl IdentifyConfig { initial_delay: Duration::from_millis(500), interval: Duration::from_secs(5 * 60), push_listen_addr_updates: false, + cache_size: 100, } } @@ -152,17 +161,29 @@ impl IdentifyConfig { self.push_listen_addr_updates = b; self } + + /// Configures the size of the LRU cache, caching addresses of discovered peers. + /// + /// The [`Swarm`](libp2p_swarm::Swarm) may extend the set of addresses of an outgoing connection attempt via + /// [`Identify::addresses_of_peer`]. + pub fn with_cache_size(mut self, cache_size: usize) -> Self { + self.cache_size = cache_size; + self + } } impl Identify { /// Creates a new `Identify` network behaviour. pub fn new(config: IdentifyConfig) -> Self { + let discovered_peers = LruCache::new(config.cache_size); + Identify { config, connected: HashMap::new(), pending_replies: VecDeque::new(), events: VecDeque::new(), pending_push: HashSet::new(), + discovered_peers, } } @@ -199,6 +220,7 @@ impl NetworkBehaviour for Identify { peer_id: &PeerId, conn: &ConnectionId, endpoint: &ConnectedPoint, + failed_addresses: Option<&Vec>, ) { let addr = match endpoint { ConnectedPoint::Dialer { address } => address.clone(), @@ -209,6 +231,16 @@ impl NetworkBehaviour for Identify { .entry(*peer_id) .or_default() .insert(*conn, addr); + + if let Some(entry) = self.discovered_peers.get_mut(peer_id) { + for addr in failed_addresses + .into_iter() + .map(|addresses| addresses.into_iter()) + .flatten() + { + entry.remove(addr); + } + } } fn inject_connection_closed( @@ -223,9 +255,24 @@ impl NetworkBehaviour for Identify { } } - fn inject_dial_failure(&mut self, peer_id: &PeerId, _: Self::ProtocolsHandler, _: DialError) { - if !self.connected.contains_key(peer_id) { - self.pending_push.remove(peer_id); + fn inject_dial_failure( + &mut self, + peer_id: Option, + _: Self::ProtocolsHandler, + error: &DialError, + ) { + if let Some(peer_id) = peer_id { + if !self.connected.contains_key(&peer_id) { + self.pending_push.remove(&peer_id); + } + } + + if let Some(entry) = peer_id.and_then(|id| self.discovered_peers.get_mut(&id)) { + if let DialError::Transport(errors) = error { + for (addr, _error) in errors { + entry.remove(addr); + } + } } } @@ -254,6 +301,10 @@ impl NetworkBehaviour for Identify { ) { match event { IdentifyHandlerEvent::Identified(info) => { + // Replace existing addresses to prevent other peer from filling up our memory. + self.discovered_peers + .put(peer_id, HashSet::from_iter(info.listen_addrs.clone())); + let observed = info.observed_addr.clone(); self.events.push_back(NetworkBehaviourAction::GenerateEvent( IdentifyEvent::Received { peer_id, info }, @@ -388,6 +439,14 @@ impl NetworkBehaviour for Identify { Poll::Pending } + + fn addresses_of_peer(&mut self, peer: &PeerId) -> Vec { + self.discovered_peers + .get(peer) + .cloned() + .map(|addr| Vec::from_iter(addr)) + .unwrap_or_default() + } } /// Event emitted by the `Identify` behaviour. @@ -552,11 +611,7 @@ mod tests { let (mut swarm1, pubkey1) = { let (pubkey, transport) = transport(); - let protocol = Identify::new( - IdentifyConfig::new("a".to_string(), pubkey.clone()) - // Delay identification requests so we can test the push protocol. - .with_initial_delay(Duration::from_secs(u32::MAX as u64)), - ); + let protocol = Identify::new(IdentifyConfig::new("a".to_string(), pubkey.clone())); let swarm = Swarm::new(transport, protocol, pubkey.to_peer_id()); (swarm, pubkey) }; @@ -565,9 +620,7 @@ mod tests { let (pubkey, transport) = transport(); let protocol = Identify::new( IdentifyConfig::new("a".to_string(), pubkey.clone()) - .with_agent_version("b".to_string()) - // Delay identification requests so we can test the push protocol. - .with_initial_delay(Duration::from_secs(u32::MAX as u64)), + .with_agent_version("b".to_string()), ); let swarm = Swarm::new(transport, protocol, pubkey.to_peer_id()); (swarm, pubkey) @@ -626,4 +679,80 @@ mod tests { } }) } + + #[test] + fn discover_peer_after_disconnect() { + let _ = env_logger::try_init(); + + let mut swarm1 = { + let (pubkey, transport) = transport(); + let protocol = Identify::new(IdentifyConfig::new("a".to_string(), pubkey.clone())); + + Swarm::new(transport, protocol, pubkey.to_peer_id()) + }; + + let mut swarm2 = { + let (pubkey, transport) = transport(); + let protocol = Identify::new( + IdentifyConfig::new("a".to_string(), pubkey.clone()) + .with_agent_version("b".to_string()), + ); + + Swarm::new(transport, protocol, pubkey.to_peer_id()) + }; + + let swarm1_peer_id = *swarm1.local_peer_id(); + + let listener = swarm1 + .listen_on("/ip4/127.0.0.1/tcp/0".parse().unwrap()) + .unwrap(); + + let listen_addr = async_std::task::block_on(async { + loop { + match swarm1.select_next_some().await { + SwarmEvent::NewListenAddr { + address, + listener_id, + } if listener_id == listener => return address, + _ => {} + } + } + }); + + async_std::task::spawn(async move { + loop { + swarm1.next().await; + } + }); + + swarm2.dial_addr(listen_addr).unwrap(); + + // wait until we identified + async_std::task::block_on(async { + loop { + if let SwarmEvent::Behaviour(IdentifyEvent::Received { .. }) = + swarm2.select_next_some().await + { + break; + } + } + }); + + swarm2.disconnect_peer_id(swarm1_peer_id).unwrap(); + + // we should still be able to dial now! + swarm2.dial(&swarm1_peer_id).unwrap(); + + let connected_peer = async_std::task::block_on(async { + loop { + if let SwarmEvent::ConnectionEstablished { peer_id, .. } = + swarm2.select_next_some().await + { + break peer_id; + } + } + }); + + assert_eq!(connected_peer, swarm1_peer_id); + } } diff --git a/protocols/kad/CHANGELOG.md b/protocols/kad/CHANGELOG.md index d1557c3401b..b4ebf2f68b9 100644 --- a/protocols/kad/CHANGELOG.md +++ b/protocols/kad/CHANGELOG.md @@ -1,4 +1,10 @@ -# 0.32.0 [unreleased] +# 0.32.0-rc.2 [2021-10-15] + +- Export `KademliaBucketInserts` (see [PR 2294]). + +[PR 2294]: https://github.com/libp2p/rust-libp2p/pull/2294 + +# 0.32.0-rc.1 [2021-10-15] - Make default features of `libp2p-core` optional. [PR 2181](https://github.com/libp2p/rust-libp2p/pull/2181) diff --git a/protocols/kad/Cargo.toml b/protocols/kad/Cargo.toml index 20fe6fe2c75..353943a2a24 100644 --- a/protocols/kad/Cargo.toml +++ b/protocols/kad/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-kad" edition = "2018" description = "Kademlia protocol for libp2p" -version = "0.32.0" +version = "0.32.0-rc.2" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -17,9 +17,9 @@ fnv = "1.0" asynchronous-codec = "0.6" futures = "0.3.1" log = "0.4" -libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } -libp2p-swarm = { version = "0.31.0", path = "../../swarm" } -prost = "0.8" +libp2p-core = { version = "0.30.0-rc.1", path = "../../core", default-features = false } +libp2p-swarm = { version = "0.31.0-rc.1", path = "../../swarm" } +prost = "0.9" rand = "0.7.2" sha2 = "0.9.1" smallvec = "1.6.1" @@ -29,10 +29,11 @@ unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] } void = "1.0" [dev-dependencies] +env_logger = "0.9.0" futures-timer = "3.0" libp2p-noise = { path = "../../transports/noise" } libp2p-yamux = { path = "../../muxers/yamux" } quickcheck = "0.9.0" [build-dependencies] -prost-build = "0.8" +prost-build = "0.9" diff --git a/protocols/kad/src/behaviour.rs b/protocols/kad/src/behaviour.rs index 80236eb5686..65a60b12c76 100644 --- a/protocols/kad/src/behaviour.rs +++ b/protocols/kad/src/behaviour.rs @@ -53,7 +53,7 @@ use std::fmt; use std::num::NonZeroUsize; use std::task::{Context, Poll}; use std::vec; -use std::{borrow::Cow, error, time::Duration}; +use std::{borrow::Cow, time::Duration}; use wasm_timer::Instant; pub use crate::query::QueryStats; @@ -1697,6 +1697,44 @@ where } } } + + fn address_failed(&mut self, peer_id: PeerId, address: &Multiaddr) { + let key = kbucket::Key::from(peer_id); + + if let Some(addrs) = self.kbuckets.entry(&key).value() { + // TODO: Ideally, the address should only be removed if the error can + // be classified as "permanent" but since `err` is currently a borrowed + // trait object without a `'static` bound, even downcasting for inspection + // of the error is not possible (and also not truly desirable or ergonomic). + // The error passed in should rather be a dedicated enum. + if addrs.remove(address).is_ok() { + debug!( + "Address '{}' removed from peer '{}' due to error.", + address, peer_id + ); + } else { + // Despite apparently having no reachable address (any longer), + // the peer is kept in the routing table with the last address to avoid + // (temporary) loss of network connectivity to "flush" the routing + // table. Once in, a peer is only removed from the routing table + // if it is the least recently connected peer, currently disconnected + // and is unreachable in the context of another peer pending insertion + // into the same bucket. This is handled transparently by the + // `KBucketsTable` and takes effect through `KBucketsTable::take_applied_pending` + // within `Kademlia::poll`. + debug!( + "Last remaining address '{}' of peer '{}' is unreachable.", + address, peer_id, + ) + } + } + + for query in self.queries.iter_mut() { + if let Some(addrs) = query.inner.addresses.get_mut(&peer_id) { + addrs.retain(|a| a != address); + } + } + } } /// Exponentially decrease the given duration (base 2). @@ -1743,7 +1781,17 @@ where peer_addrs } - fn inject_connection_established(&mut self, _: &PeerId, _: &ConnectionId, _: &ConnectedPoint) { + fn inject_connection_established( + &mut self, + peer_id: &PeerId, + _: &ConnectionId, + _: &ConnectedPoint, + errors: Option<&Vec>, + ) { + for addr in errors.map(|a| a.into_iter()).into_iter().flatten() { + self.address_failed(*peer_id, addr); + } + // When a connection is established, we don't know yet whether the // remote supports the configured protocol name. Only once a connection // handler reports [`KademliaHandlerEvent::ProtocolConfirmed`] do we @@ -1827,66 +1875,35 @@ where } } - fn inject_addr_reach_failure( - &mut self, - peer_id: Option<&PeerId>, - addr: &Multiaddr, - err: &dyn error::Error, - ) { - if let Some(peer_id) = peer_id { - let key = kbucket::Key::from(*peer_id); - - if let Some(addrs) = self.kbuckets.entry(&key).value() { - // TODO: Ideally, the address should only be removed if the error can - // be classified as "permanent" but since `err` is currently a borrowed - // trait object without a `'static` bound, even downcasting for inspection - // of the error is not possible (and also not truly desirable or ergonomic). - // The error passed in should rather be a dedicated enum. - if addrs.remove(addr).is_ok() { - debug!( - "Address '{}' removed from peer '{}' due to error: {}.", - addr, peer_id, err - ); - } else { - // Despite apparently having no reachable address (any longer), - // the peer is kept in the routing table with the last address to avoid - // (temporary) loss of network connectivity to "flush" the routing - // table. Once in, a peer is only removed from the routing table - // if it is the least recently connected peer, currently disconnected - // and is unreachable in the context of another peer pending insertion - // into the same bucket. This is handled transparently by the - // `KBucketsTable` and takes effect through `KBucketsTable::take_applied_pending` - // within `Kademlia::poll`. - debug!( - "Last remaining address '{}' of peer '{}' is unreachable: {}.", - addr, peer_id, err - ) - } - } - - for query in self.queries.iter_mut() { - if let Some(addrs) = query.inner.addresses.get_mut(peer_id) { - addrs.retain(|a| a != addr); - } - } - } - } - fn inject_dial_failure( &mut self, - peer_id: &PeerId, + peer_id: Option, _: Self::ProtocolsHandler, - error: DialError, + error: &DialError, ) { + let peer_id = match peer_id { + Some(id) => id, + // Not interested in dial failures to unknown peers. + None => return, + }; + match error { DialError::Banned | DialError::ConnectionLimit(_) - | DialError::InvalidAddress(_) - | DialError::UnreachableAddr(_) | DialError::LocalPeerId + | DialError::InvalidPeerId + | DialError::Aborted + | DialError::ConnectionIo(_) + | DialError::Transport(_) | DialError::NoAddresses => { + if let DialError::Transport(addresses) = error { + for (addr, _) in addresses { + self.address_failed(peer_id, addr) + } + } + for query in self.queries.iter_mut() { - query.on_failure(peer_id); + query.on_failure(&peer_id); } } DialError::DialPeerConditionFalse( @@ -2448,12 +2465,6 @@ pub enum InboundRequest { num_provider_peers: usize, }, /// Request to store a peer as a provider. - // - // TODO: In the future one might want to use this event, not only to report - // a new provider, but to allow the upper layer to validate the incoming - // provider record, discarding it or passing it back down to be stored. This - // would follow a similar style to the `KademliaBucketInserts` strategy. - // Same would be applicable to `PutRecord`. AddProvider {}, /// Request to retrieve a record. GetRecord { diff --git a/protocols/kad/src/behaviour/test.rs b/protocols/kad/src/behaviour/test.rs index a39ff5afed9..463ffed7710 100644 --- a/protocols/kad/src/behaviour/test.rs +++ b/protocols/kad/src/behaviour/test.rs @@ -313,6 +313,7 @@ fn query_iter() { #[test] fn unresponsive_not_returned_direct() { + let _ = env_logger::try_init(); // Build one node. It contains fake addresses to non-existing nodes. We ask it to find a // random peer. We make sure that no fake address is returned. @@ -1280,7 +1281,7 @@ fn network_behaviour_inject_address_change() { }; // Mimick a connection being established. - kademlia.inject_connection_established(&remote_peer_id, &connection_id, &endpoint); + kademlia.inject_connection_established(&remote_peer_id, &connection_id, &endpoint, None); kademlia.inject_connected(&remote_peer_id); // At this point the remote is not yet known to support the diff --git a/protocols/kad/src/lib.rs b/protocols/kad/src/lib.rs index 3b8a0d21e91..d8734ec0d80 100644 --- a/protocols/kad/src/lib.rs +++ b/protocols/kad/src/lib.rs @@ -48,7 +48,8 @@ pub use behaviour::{ QueryStats, }; pub use behaviour::{ - Kademlia, KademliaCaching, KademliaConfig, KademliaEvent, KademliaStoreInserts, Quorum, + Kademlia, KademliaBucketInserts, KademliaCaching, KademliaConfig, KademliaEvent, + KademliaStoreInserts, Quorum, }; pub use protocol::KadConnectionType; pub use query::QueryId; diff --git a/protocols/mdns/CHANGELOG.md b/protocols/mdns/CHANGELOG.md index 3b633de5030..3cd057217fd 100644 --- a/protocols/mdns/CHANGELOG.md +++ b/protocols/mdns/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.32.0 [unreleased] +# 0.32.0-rc.1 [2021-10-15] - Make default features of `libp2p-core` optional. [PR 2181](https://github.com/libp2p/rust-libp2p/pull/2181) diff --git a/protocols/mdns/Cargo.toml b/protocols/mdns/Cargo.toml index 43370a9bffd..b7dbf801569 100644 --- a/protocols/mdns/Cargo.toml +++ b/protocols/mdns/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "libp2p-mdns" edition = "2018" -version = "0.32.0" +version = "0.32.0-rc.1" description = "Implementation of the libp2p mDNS discovery method" authors = ["Parity Technologies "] license = "MIT" @@ -16,8 +16,8 @@ dns-parser = "0.8.0" futures = "0.3.13" if-watch = "0.2.0" lazy_static = "1.4.0" -libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } -libp2p-swarm = { version = "0.31.0", path = "../../swarm" } +libp2p-core = { version = "0.30.0-rc.1", path = "../../core", default-features = false } +libp2p-swarm = { version = "0.31.0-rc.1", path = "../../swarm" } log = "0.4.14" rand = "0.8.3" smallvec = "1.6.1" diff --git a/protocols/ping/CHANGELOG.md b/protocols/ping/CHANGELOG.md index e4de6070fc3..5aa997fc9f2 100644 --- a/protocols/ping/CHANGELOG.md +++ b/protocols/ping/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.31.0 [unreleased] +# 0.31.0-rc.1 [2021-10-15] - Make default features of `libp2p-core` optional. [PR 2181](https://github.com/libp2p/rust-libp2p/pull/2181) diff --git a/protocols/ping/Cargo.toml b/protocols/ping/Cargo.toml index 24924c48217..93b0bdf9c39 100644 --- a/protocols/ping/Cargo.toml +++ b/protocols/ping/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-ping" edition = "2018" description = "Ping protocol for libp2p" -version = "0.31.0" +version = "0.31.0-rc.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -11,8 +11,8 @@ categories = ["network-programming", "asynchronous"] [dependencies] futures = "0.3.1" -libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } -libp2p-swarm = { version = "0.31.0", path = "../../swarm" } +libp2p-core = { version = "0.30.0-rc.1", path = "../../core", default-features = false } +libp2p-swarm = { version = "0.31.0-rc.1", path = "../../swarm" } log = "0.4.1" rand = "0.7.2" void = "1.0" diff --git a/protocols/relay/CHANGELOG.md b/protocols/relay/CHANGELOG.md index 9f2857bfcc1..fc50ca28e64 100644 --- a/protocols/relay/CHANGELOG.md +++ b/protocols/relay/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.4.0 [unreleased] +# 0.4.0-rc.1 [2021-10-15] - Make default features of `libp2p-core` optional. [PR 2181](https://github.com/libp2p/rust-libp2p/pull/2181) diff --git a/protocols/relay/Cargo.toml b/protocols/relay/Cargo.toml index aedfe262f0b..d46661d773a 100644 --- a/protocols/relay/Cargo.toml +++ b/protocols/relay/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-relay" edition = "2018" description = "Communications relaying for libp2p" -version = "0.4.0" +version = "0.4.0-rc.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -14,11 +14,11 @@ asynchronous-codec = "0.6" bytes = "1" futures = "0.3.1" futures-timer = "3" -libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } -libp2p-swarm = { version = "0.31.0", path = "../../swarm" } +libp2p-core = { version = "0.30.0-rc.1", path = "../../core", default-features = false } +libp2p-swarm = { version = "0.31.0-rc.1", path = "../../swarm" } log = "0.4" pin-project = "1" -prost = "0.8" +prost = "0.9" rand = "0.7" smallvec = "1.6.1" unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] } @@ -26,7 +26,7 @@ void = "1" wasm-timer = "0.2" [build-dependencies] -prost-build = "0.8" +prost-build = "0.9" [dev-dependencies] env_logger = "0.9.0" diff --git a/protocols/relay/src/behaviour.rs b/protocols/relay/src/behaviour.rs index 8fe510883aa..d69de981590 100644 --- a/protocols/relay/src/behaviour.rs +++ b/protocols/relay/src/behaviour.rs @@ -202,6 +202,7 @@ impl NetworkBehaviour for Relay { peer: &PeerId, connection_id: &ConnectionId, _: &ConnectedPoint, + _: Option<&Vec>, ) { let is_first = self .connected_peers @@ -304,9 +305,9 @@ impl NetworkBehaviour for Relay { fn inject_dial_failure( &mut self, - peer_id: &PeerId, + peer_id: Option, _: Self::ProtocolsHandler, - error: DialError, + error: &DialError, ) { if let DialError::DialPeerConditionFalse( DialPeerCondition::Disconnected | DialPeerCondition::NotDialing, @@ -316,35 +317,37 @@ impl NetworkBehaviour for Relay { return; } - if let Entry::Occupied(o) = self.listeners.entry(*peer_id) { - if matches!(o.get(), RelayListener::Connecting { .. }) { - // By removing the entry, the channel to the listener is dropped and thus the - // listener is notified that dialing the relay failed. - o.remove_entry(); + if let Some(peer_id) = peer_id { + if let Entry::Occupied(o) = self.listeners.entry(peer_id) { + if matches!(o.get(), RelayListener::Connecting { .. }) { + // By removing the entry, the channel to the listener is dropped and thus the + // listener is notified that dialing the relay failed. + o.remove_entry(); + } } - } - if let Some(reqs) = self.outgoing_relay_reqs.dialing.remove(peer_id) { - for req in reqs { - let _ = req.send_back.send(Err(OutgoingRelayReqError::DialingRelay)); + if let Some(reqs) = self.outgoing_relay_reqs.dialing.remove(&peer_id) { + for req in reqs { + let _ = req.send_back.send(Err(OutgoingRelayReqError::DialingRelay)); + } } - } - if let Some(reqs) = self.incoming_relay_reqs.remove(peer_id) { - for req in reqs { - let IncomingRelayReq::DialingDst { - src_peer_id, - incoming_relay_req, - .. - } = req; - self.outbox_to_swarm - .push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: src_peer_id, - handler: NotifyHandler::Any, - event: RelayHandlerIn::DenyIncomingRelayReq( - incoming_relay_req.deny(circuit_relay::Status::HopCantDialDst), - ), - }) + if let Some(reqs) = self.incoming_relay_reqs.remove(&peer_id) { + for req in reqs { + let IncomingRelayReq::DialingDst { + src_peer_id, + incoming_relay_req, + .. + } = req; + self.outbox_to_swarm + .push_back(NetworkBehaviourAction::NotifyHandler { + peer_id: src_peer_id, + handler: NotifyHandler::Any, + event: RelayHandlerIn::DenyIncomingRelayReq( + incoming_relay_req.deny(circuit_relay::Status::HopCantDialDst), + ), + }) + } } } } @@ -411,15 +414,6 @@ impl NetworkBehaviour for Relay { } } - fn inject_addr_reach_failure( - &mut self, - _peer_id: Option<&PeerId>, - _addr: &Multiaddr, - _error: &dyn std::error::Error, - ) { - // Handled in `inject_dial_failure`. - } - fn inject_listener_error(&mut self, _id: ListenerId, _err: &(dyn std::error::Error + 'static)) { } diff --git a/protocols/relay/tests/lib.rs b/protocols/relay/tests/lib.rs index 5f33c293252..427d83f970a 100644 --- a/protocols/relay/tests/lib.rs +++ b/protocols/relay/tests/lib.rs @@ -36,8 +36,8 @@ use libp2p_plaintext::PlainText2Config; use libp2p_relay::{Relay, RelayConfig}; use libp2p_swarm::protocols_handler::KeepAlive; use libp2p_swarm::{ - DummyBehaviour, NetworkBehaviour, NetworkBehaviourAction, NetworkBehaviourEventProcess, - PollParameters, Swarm, SwarmEvent, + DialError, DummyBehaviour, NetworkBehaviour, NetworkBehaviourAction, + NetworkBehaviourEventProcess, PollParameters, Swarm, SwarmEvent, }; use std::task::{Context, Poll}; use std::time::Duration; @@ -391,10 +391,11 @@ fn src_try_connect_to_offline_dst() { loop { match src_swarm.select_next_some().await { - SwarmEvent::UnreachableAddr { - address, peer_id, .. - } if address == dst_addr_via_relay => { - assert_eq!(peer_id, dst_peer_id); + SwarmEvent::OutgoingConnectionError { + error: DialError::Transport(addresses), + peer_id, + } if *addresses.iter().map(|(a, _)| a).next().unwrap() == dst_addr_via_relay => { + assert_eq!(peer_id, Some(dst_peer_id)); break; } SwarmEvent::Behaviour(CombinedEvent::Ping(_)) => {} @@ -448,10 +449,11 @@ fn src_try_connect_to_unsupported_dst() { loop { match src_swarm.select_next_some().await { - SwarmEvent::UnreachableAddr { - address, peer_id, .. - } if address == dst_addr_via_relay => { - assert_eq!(peer_id, dst_peer_id); + SwarmEvent::OutgoingConnectionError { + error: DialError::Transport(addresses), + peer_id, + } if *addresses.iter().map(|(a, _)| a).next().unwrap() == dst_addr_via_relay => { + assert_eq!(peer_id, Some(dst_peer_id)); break; } SwarmEvent::ConnectionClosed { peer_id, .. } if peer_id == relay_peer_id => {} @@ -492,16 +494,18 @@ fn src_try_connect_to_offline_dst_via_offline_relay() { // Source Node fail to reach Relay. match src_swarm.select_next_some().await { - SwarmEvent::UnreachableAddr { peer_id, .. } if peer_id == relay_peer_id => {} + SwarmEvent::OutgoingConnectionError { peer_id, .. } + if peer_id == Some(relay_peer_id) => {} e => panic!("{:?}", e), } // Source Node fail to reach Destination Node due to failure reaching Relay. match src_swarm.select_next_some().await { - SwarmEvent::UnreachableAddr { - address, peer_id, .. - } if address == dst_addr_via_relay => { - assert_eq!(peer_id, dst_peer_id); + SwarmEvent::OutgoingConnectionError { + error: DialError::Transport(addresses), + peer_id, + } if *addresses.iter().map(|(a, _)| a).next().unwrap() == dst_addr_via_relay => { + assert_eq!(peer_id, Some(dst_peer_id)); } e => panic!("{:?}", e), } @@ -1063,10 +1067,13 @@ fn yield_incoming_connection_through_correct_listener() { e => panic!("{:?}", e), }, event = src_3_swarm.select_next_some() => match event { - SwarmEvent::UnreachableAddr { address, peer_id, .. } - if address == dst_addr_via_relay_3 => + SwarmEvent::OutgoingConnectionError { + error: DialError::Transport(addresses), + peer_id, + } if *addresses.iter().map(|(a, _)| a).next().unwrap() + == dst_addr_via_relay_3 => { - assert_eq!(peer_id, dst_peer_id); + assert_eq!(peer_id, Some(dst_peer_id)); break; } SwarmEvent::Dialing { .. } => {} diff --git a/protocols/rendezvous/CHANGELOG.md b/protocols/rendezvous/CHANGELOG.md index ab400cdaf64..8e127776e13 100644 --- a/protocols/rendezvous/CHANGELOG.md +++ b/protocols/rendezvous/CHANGELOG.md @@ -1,3 +1,3 @@ -# 0.1.0 [unreleased] +# 0.1.0-rc.1 [2021-10-15] - Initial release. diff --git a/protocols/rendezvous/Cargo.toml b/protocols/rendezvous/Cargo.toml index fcc176dd484..8396b2f65f8 100644 --- a/protocols/rendezvous/Cargo.toml +++ b/protocols/rendezvous/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-rendezvous" edition = "2018" description = "Rendezvous protocol for libp2p" -version = "0.1.0" +version = "0.1.0-rc.1" authors = ["The COMIT guys "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -11,9 +11,9 @@ categories = ["network-programming", "asynchronous"] [dependencies] asynchronous-codec = "0.6" -libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } -libp2p-swarm = { version = "0.31.0", path = "../../swarm" } -prost = "0.8" +libp2p-core = { version = "0.30.0-rc.1", path = "../../core", default-features = false } +libp2p-swarm = { version = "0.31.0-rc.1", path = "../../swarm" } +prost = "0.9" void = "1" log = "0.4" futures = { version = "0.3", default-features = false, features = ["std"] } @@ -32,4 +32,4 @@ rand = "0.8" tokio = { version = "1", features = [ "rt-multi-thread", "time", "macros", "sync", "process", "fs", "net" ] } [build-dependencies] -prost-build = "0.8" +prost-build = "0.9" diff --git a/protocols/rendezvous/examples/discover.rs b/protocols/rendezvous/examples/discover.rs index 9e60378b261..4466e1caaa0 100644 --- a/protocols/rendezvous/examples/discover.rs +++ b/protocols/rendezvous/examples/discover.rs @@ -76,17 +76,6 @@ async fn main() { rendezvous_point, ); } - SwarmEvent::UnreachableAddr { error, address, .. } - | SwarmEvent::UnknownPeerUnreachableAddr { error, address, .. } - if address == rendezvous_point_address => - { - log::error!( - "Failed to connect to rendezvous point at {}: {}", - address, - error - ); - return; - } SwarmEvent::Behaviour(MyEvent::Rendezvous(rendezvous::client::Event::Discovered { registrations, cookie: new_cookie, diff --git a/protocols/rendezvous/src/lib.rs b/protocols/rendezvous/src/lib.rs index 87c88434db3..1708147f977 100644 --- a/protocols/rendezvous/src/lib.rs +++ b/protocols/rendezvous/src/lib.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -pub use self::codec::{ErrorCode, Namespace, NamespaceTooLong, Registration, Ttl}; +pub use self::codec::{Cookie, ErrorCode, Namespace, NamespaceTooLong, Registration, Ttl}; mod codec; mod handler; diff --git a/protocols/rendezvous/tests/harness/mod.rs b/protocols/rendezvous/tests/harness/mod.rs index 5747f7d19a6..7b37fb740ca 100644 --- a/protocols/rendezvous/tests/harness/mod.rs +++ b/protocols/rendezvous/tests/harness/mod.rs @@ -76,13 +76,29 @@ fn get_rand_memory_address() -> Multiaddr { addr } -pub async fn await_events_or_timeout( - swarm_1: &mut (impl Stream> + FusedStream + Unpin), - swarm_2: &mut (impl Stream> + FusedStream + Unpin), -) -> (SwarmEvent, SwarmEvent) +pub async fn await_event_or_timeout( + swarm: &mut (impl Stream> + FusedStream + Unpin), +) -> SwarmEvent where - SwarmEvent: Debug, - SwarmEvent: Debug, + SwarmEvent: Debug, +{ + tokio::time::timeout( + Duration::from_secs(30), + swarm + .inspect(|event| log::debug!("Swarm emitted {:?}", event)) + .select_next_some(), + ) + .await + .expect("network behaviour to emit an event within 30 seconds") +} + +pub async fn await_events_or_timeout( + swarm_1: &mut (impl Stream> + FusedStream + Unpin), + swarm_2: &mut (impl Stream> + FusedStream + Unpin), +) -> (SwarmEvent, SwarmEvent) +where + SwarmEvent: Debug, + SwarmEvent: Debug, { tokio::time::timeout( Duration::from_secs(30), @@ -96,11 +112,17 @@ where ), ) .await - .expect("network behaviours to emit an event within 10 seconds") + .expect("network behaviours to emit an event within 30 seconds") } #[macro_export] macro_rules! assert_behaviour_events { + ($swarm: ident: $pat: pat, || $body: block) => { + match await_event_or_timeout(&mut $swarm).await { + libp2p::swarm::SwarmEvent::Behaviour($pat) => $body, + _ => panic!("Unexpected combination of events emitted, check logs for details"), + } + }; ($swarm1: ident: $pat1: pat, $swarm2: ident: $pat2: pat, || $body: block) => { match await_events_or_timeout(&mut $swarm1, &mut $swarm2).await { ( @@ -155,9 +177,6 @@ where SwarmEvent::ConnectionEstablished { .. } => { dialer_done = true; } - SwarmEvent::UnknownPeerUnreachableAddr { address, error } if address == addr_to_dial => { - panic!("Failed to dial address {}: {}", addr_to_dial, error) - } other => { log::debug!("Ignoring {:?}", other); } diff --git a/protocols/rendezvous/tests/rendezvous.rs b/protocols/rendezvous/tests/rendezvous.rs index e6ba5fcc39a..cdec79a4ce4 100644 --- a/protocols/rendezvous/tests/rendezvous.rs +++ b/protocols/rendezvous/tests/rendezvous.rs @@ -21,7 +21,7 @@ #[macro_use] pub mod harness; -use crate::harness::{await_events_or_timeout, new_swarm, SwarmExt}; +use crate::harness::{await_event_or_timeout, await_events_or_timeout, new_swarm, SwarmExt}; use futures::stream::FuturesUnordered; use futures::StreamExt; use libp2p_core::identity; @@ -170,15 +170,20 @@ async fn discover_allows_for_dial_by_peer_id() { alice .behaviour_mut() .register(namespace.clone(), roberts_peer_id, None); - bob.behaviour_mut() - .discover(Some(namespace.clone()), None, None, roberts_peer_id); - assert_behaviour_events! { alice: rendezvous::client::Event::Registered { .. }, - bob: rendezvous::client::Event::Discovered { .. }, || { } }; + bob.behaviour_mut() + .discover(Some(namespace.clone()), None, None, roberts_peer_id); + assert_behaviour_events! { + bob: rendezvous::client::Event::Discovered { registrations,.. }, + || { + assert!(!registrations.is_empty()); + } + }; + let alices_peer_id = *alice.local_peer_id(); let bobs_peer_id = *bob.local_peer_id(); @@ -275,14 +280,18 @@ async fn registration_on_clients_expire() { alice .behaviour_mut() .register(namespace.clone(), roberts_peer_id, Some(registration_ttl)); - bob.behaviour_mut() - .discover(Some(namespace), None, None, roberts_peer_id); - assert_behaviour_events! { alice: rendezvous::client::Event::Registered { .. }, - bob: rendezvous::client::Event::Discovered { .. }, || { } }; + bob.behaviour_mut() + .discover(Some(namespace), None, None, roberts_peer_id); + assert_behaviour_events! { + bob: rendezvous::client::Event::Discovered { registrations,.. }, + || { + assert!(!registrations.is_empty()); + } + }; tokio::time::sleep(Duration::from_secs(registration_ttl + 5)).await; diff --git a/protocols/request-response/CHANGELOG.md b/protocols/request-response/CHANGELOG.md index c33ac7b6c20..a4620fc9a58 100644 --- a/protocols/request-response/CHANGELOG.md +++ b/protocols/request-response/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.13.0 [unreleased] +# 0.13.0-rc.1 [2021-10-15] - Make default features of `libp2p-core` optional. [PR 2181](https://github.com/libp2p/rust-libp2p/pull/2181) diff --git a/protocols/request-response/Cargo.toml b/protocols/request-response/Cargo.toml index 288ee60bf6e..87d581490f0 100644 --- a/protocols/request-response/Cargo.toml +++ b/protocols/request-response/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-request-response" edition = "2018" description = "Generic Request/Response Protocols" -version = "0.13.0" +version = "0.13.0-rc.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -13,10 +13,10 @@ categories = ["network-programming", "asynchronous"] async-trait = "0.1" bytes = "1" futures = "0.3.1" -libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } -libp2p-swarm = { version = "0.31.0", path = "../../swarm" } +libp2p-core = { version = "0.30.0-rc.1", path = "../../core", default-features = false } +libp2p-swarm = { version = "0.31.0-rc.1", path = "../../swarm" } log = "0.4.11" -lru = "0.6" +lru = "0.7" rand = "0.7" smallvec = "1.6.1" unsigned-varint = { version = "0.7", features = ["std", "futures"] } diff --git a/protocols/request-response/src/lib.rs b/protocols/request-response/src/lib.rs index 3df2b40b856..c04f229b64e 100644 --- a/protocols/request-response/src/lib.rs +++ b/protocols/request-response/src/lib.rs @@ -606,6 +606,7 @@ where peer: &PeerId, conn: &ConnectionId, endpoint: &ConnectedPoint, + _errors: Option<&Vec>, ) { let address = match endpoint { ConnectedPoint::Dialer { address } => Some(address.clone()), @@ -666,23 +667,30 @@ where self.connected.remove(peer); } - fn inject_dial_failure(&mut self, peer: &PeerId, _: Self::ProtocolsHandler, _: DialError) { - // If there are pending outgoing requests when a dial failure occurs, - // it is implied that we are not connected to the peer, since pending - // outgoing requests are drained when a connection is established and - // only created when a peer is not connected when a request is made. - // Thus these requests must be considered failed, even if there is - // another, concurrent dialing attempt ongoing. - if let Some(pending) = self.pending_outbound_requests.remove(peer) { - for request in pending { - self.pending_events - .push_back(NetworkBehaviourAction::GenerateEvent( - RequestResponseEvent::OutboundFailure { - peer: *peer, - request_id: request.request_id, - error: OutboundFailure::DialFailure, - }, - )); + fn inject_dial_failure( + &mut self, + peer: Option, + _: Self::ProtocolsHandler, + _: &DialError, + ) { + if let Some(peer) = peer { + // If there are pending outgoing requests when a dial failure occurs, + // it is implied that we are not connected to the peer, since pending + // outgoing requests are drained when a connection is established and + // only created when a peer is not connected when a request is made. + // Thus these requests must be considered failed, even if there is + // another, concurrent dialing attempt ongoing. + if let Some(pending) = self.pending_outbound_requests.remove(&peer) { + for request in pending { + self.pending_events + .push_back(NetworkBehaviourAction::GenerateEvent( + RequestResponseEvent::OutboundFailure { + peer: peer, + request_id: request.request_id, + error: OutboundFailure::DialFailure, + }, + )); + } } } } diff --git a/swarm-derive/CHANGELOG.md b/swarm-derive/CHANGELOG.md index dc31bd83453..58c2096dbb7 100644 --- a/swarm-derive/CHANGELOG.md +++ b/swarm-derive/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.25.0 [unreleased] +# 0.25.0-rc.1 [2021-10-15] - Update to latest `libp2p-swarm` changes (see [PR 2191]). diff --git a/swarm-derive/Cargo.toml b/swarm-derive/Cargo.toml index 2da7fc8a34b..6d9387eb068 100644 --- a/swarm-derive/Cargo.toml +++ b/swarm-derive/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-swarm-derive" edition = "2018" description = "Procedural macros of libp2p-core" -version = "0.25.0" +version = "0.25.0-rc.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/swarm-derive/src/lib.rs b/swarm-derive/src/lib.rs index ded16461146..512e0923670 100644 --- a/swarm-derive/src/lib.rs +++ b/swarm-derive/src/lib.rs @@ -55,6 +55,7 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream { let into_proto_select_ident = quote! {::libp2p::swarm::IntoProtocolsHandlerSelect}; let peer_id = quote! {::libp2p::core::PeerId}; let connection_id = quote! {::libp2p::core::connection::ConnectionId}; + let dial_errors = quote! {Option<&Vec<::libp2p::core::Multiaddr>>}; let connected_point = quote! {::libp2p::core::ConnectedPoint}; let listener_id = quote! {::libp2p::core::connection::ListenerId}; let dial_error = quote! {::libp2p::swarm::DialError}; @@ -203,8 +204,8 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream { return None; } Some(match field.ident { - Some(ref i) => quote!{ self.#i.inject_connection_established(peer_id, connection_id, endpoint); }, - None => quote!{ self.#field_n.inject_connection_established(peer_id, connection_id, endpoint); }, + Some(ref i) => quote!{ self.#i.inject_connection_established(peer_id, connection_id, endpoint, errors); }, + None => quote!{ self.#field_n.inject_connection_established(peer_id, connection_id, endpoint, errors); }, }) }) }; @@ -253,21 +254,6 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream { }) }; - // Build the list of statements to put in the body of `inject_addr_reach_failure()`. - let inject_addr_reach_failure_stmts = - { - data_struct.fields.iter().enumerate().filter_map(move |(field_n, field)| { - if is_ignored(field) { - return None; - } - - Some(match field.ident { - Some(ref i) => quote!{ self.#i.inject_addr_reach_failure(peer_id, addr, error); }, - None => quote!{ self.#field_n.inject_addr_reach_failure(peer_id, addr, error); }, - }) - }) - }; - // Build the list of statements to put in the body of `inject_dial_failure()`. let inject_dial_failure_stmts = { data_struct @@ -674,7 +660,7 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream { #(#inject_disconnected_stmts);* } - fn inject_connection_established(&mut self, peer_id: &#peer_id, connection_id: &#connection_id, endpoint: &#connected_point) { + fn inject_connection_established(&mut self, peer_id: &#peer_id, connection_id: &#connection_id, endpoint: &#connected_point, errors: #dial_errors) { #(#inject_connection_established_stmts);* } @@ -686,11 +672,7 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream { #(#inject_connection_closed_stmts);* } - fn inject_addr_reach_failure(&mut self, peer_id: Option<&#peer_id>, addr: &#multiaddr, error: &dyn std::error::Error) { - #(#inject_addr_reach_failure_stmts);* - } - - fn inject_dial_failure(&mut self, peer_id: &#peer_id, handlers: Self::ProtocolsHandler, error: #dial_error) { + fn inject_dial_failure(&mut self, peer_id: Option<#peer_id>, handlers: Self::ProtocolsHandler, error: &#dial_error) { #(#inject_dial_failure_stmts);* } diff --git a/swarm/CHANGELOG.md b/swarm/CHANGELOG.md index 5894591c3bd..bf67f53d91c 100644 --- a/swarm/CHANGELOG.md +++ b/swarm/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.31.0 [unreleased] +# 0.31.0-rc.1 [2021-10-15] - Make default features of `libp2p-core` optional. [PR 2181](https://github.com/libp2p/rust-libp2p/pull/2181) @@ -42,11 +42,33 @@ parameters to `NetworkBehaviourAction`. See [PR 2191]. +- Return `bool` instead of `Result<(), ()>` for `Swarm::remove_listener`(see + [PR 2261]). + +- Concurrently dial address candidates within a single dial attempt (see [PR 2248]) configured via + `Swarm::dial_concurrency_factor`. + + - On success of a single address, report errors of the thus far failed dials via + `SwarmEvent::ConnectionEstablished::outgoing`. + + - On failure of all addresses, report errors via the new `SwarmEvent::OutgoingConnectionError`. + + - Remove `SwarmEvent::UnreachableAddr` and `SwarmEvent::UnknownPeerUnreachableAddr` event. + + - In `NetworkBehaviour::inject_connection_established` provide errors of all thus far failed addresses. + + - On unknown peer dial failures, call `NetworkBehaviour::inject_dial_failure` with a peer ID of `None`. + + - Remove `NetworkBehaviour::inject_addr_reach_failure`. Information is now provided via + `NetworkBehaviour::inject_connection_established` and `NetworkBehaviour::inject_dial_failure`. + [PR 2150]: https://github.com/libp2p/rust-libp2p/pull/2150 [PR 2182]: https://github.com/libp2p/rust-libp2p/pull/2182 [PR 2183]: https://github.com/libp2p/rust-libp2p/pull/2183 [PR 2192]: https://github.com/libp2p/rust-libp2p/pull/2192 [PR 2191]: https://github.com/libp2p/rust-libp2p/pull/2191 +[PR 2248]: https://github.com/libp2p/rust-libp2p/pull/2248 +[PR 2261]: https://github.com/libp2p/rust-libp2p/pull/2261 # 0.30.0 [2021-07-12] diff --git a/swarm/Cargo.toml b/swarm/Cargo.toml index b8e5d77a242..1e4dc7f396f 100644 --- a/swarm/Cargo.toml +++ b/swarm/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-swarm" edition = "2018" description = "The libp2p swarm" -version = "0.31.0" +version = "0.31.0-rc.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -12,7 +12,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] either = "1.6.0" futures = "0.3.1" -libp2p-core = { version = "0.30.0", path = "../core", default-features = false } +libp2p-core = { version = "0.30.0-rc.1", path = "../core", default-features = false } log = "0.4" rand = "0.7" smallvec = "1.6.1" diff --git a/swarm/src/behaviour.rs b/swarm/src/behaviour.rs index c75049e6a9b..a8217eda066 100644 --- a/swarm/src/behaviour.rs +++ b/swarm/src/behaviour.rs @@ -24,7 +24,7 @@ use libp2p_core::{ connection::{ConnectionId, ListenerId}, ConnectedPoint, Multiaddr, PeerId, }; -use std::{error, task::Context, task::Poll}; +use std::{task::Context, task::Poll}; /// Custom event that can be received by the [`ProtocolsHandler`]. type THandlerInEvent = @@ -113,7 +113,14 @@ pub trait NetworkBehaviour: Send + 'static { fn inject_disconnected(&mut self, _: &PeerId) {} /// Informs the behaviour about a newly established connection to a peer. - fn inject_connection_established(&mut self, _: &PeerId, _: &ConnectionId, _: &ConnectedPoint) {} + fn inject_connection_established( + &mut self, + _peer_id: &PeerId, + _connection_id: &ConnectionId, + _endpoint: &ConnectedPoint, + _failed_addresses: Option<&Vec>, + ) { + } /// Informs the behaviour about a closed connection to a peer. /// @@ -151,28 +158,12 @@ pub trait NetworkBehaviour: Send + 'static { event: <::Handler as ProtocolsHandler>::OutEvent, ); - /// Indicates to the behaviour that we tried to reach an address, but failed. - /// - /// If we were trying to reach a specific node, its ID is passed as parameter. If this is the - /// last address to attempt for the given node, then `inject_dial_failure` is called afterwards. - fn inject_addr_reach_failure( - &mut self, - _peer_id: Option<&PeerId>, - _addr: &Multiaddr, - _error: &dyn error::Error, - ) { - } - - /// Indicates to the behaviour that we tried to dial all the addresses known for a node, but - /// failed. - /// - /// The `peer_id` is guaranteed to be in a disconnected state. In other words, - /// `inject_connected` has not been called, or `inject_disconnected` has been called since then. + /// Indicates to the behaviour that the dial to a known or unknown node failed. fn inject_dial_failure( &mut self, - _peer_id: &PeerId, + _peer_id: Option, _handler: Self::ProtocolsHandler, - _error: DialError, + _error: &DialError, ) { } @@ -399,9 +390,9 @@ pub enum NetworkBehaviourAction< /// # /// fn inject_dial_failure( /// &mut self, - /// _: &PeerId, + /// _: Option, /// handler: Self::ProtocolsHandler, - /// _: DialError, + /// _: &DialError, /// ) { /// // As expected, sending the message failed. But lucky us, we got the handler back, thus /// // the precious message is not lost and we can return it back to the user. @@ -707,18 +698,10 @@ pub enum DialPeerCondition { /// A new dialing attempt is initiated _only if_ the peer is currently /// considered disconnected, i.e. there is no established connection /// and no ongoing dialing attempt. - /// - /// If there is an ongoing dialing attempt, the addresses reported by - /// [`NetworkBehaviour::addresses_of_peer`] are added to the ongoing - /// dialing attempt, ignoring duplicates. Disconnected, /// A new dialing attempt is initiated _only if_ there is currently /// no ongoing dialing attempt, i.e. the peer is either considered /// disconnected or connected but without an ongoing dialing attempt. - /// - /// If there is an ongoing dialing attempt, the addresses reported by - /// [`NetworkBehaviour::addresses_of_peer`] are added to the ongoing - /// dialing attempt, ignoring duplicates. NotDialing, /// A new dialing attempt is always initiated, only subject to the /// configured connection limits. diff --git a/swarm/src/lib.rs b/swarm/src/lib.rs index a903113c2a9..9b3972eab2a 100644 --- a/swarm/src/lib.rs +++ b/swarm/src/lib.rs @@ -78,12 +78,12 @@ use libp2p_core::{ connection::{ ConnectedPoint, ConnectionError, ConnectionHandler, ConnectionId, ConnectionLimit, EstablishedConnection, IntoConnectionHandler, ListenerId, PendingConnectionError, - Substream, + PendingInboundConnectionError, PendingOutboundConnectionError, Substream, }, muxing::StreamMuxerBox, network::{ - self, peer::ConnectedPeer, ConnectionLimits, DialAttemptsRemaining, Network, NetworkConfig, - NetworkEvent, NetworkInfo, + self, peer::ConnectedPeer, ConnectionLimits, Network, NetworkConfig, NetworkEvent, + NetworkInfo, }, transport::{self, TransportError}, upgrade::ProtocolName, @@ -93,7 +93,7 @@ use protocols_handler::{NodeHandlerWrapperBuilder, NodeHandlerWrapperError}; use registry::{AddressIntoIter, Addresses}; use smallvec::SmallVec; use std::collections::HashSet; -use std::num::{NonZeroU32, NonZeroUsize}; +use std::num::{NonZeroU32, NonZeroU8, NonZeroUsize}; use std::{ error, fmt, io, pin::Pin, @@ -141,6 +141,10 @@ pub enum SwarmEvent { /// Number of established connections to this peer, including the one that has just been /// opened. num_established: NonZeroU32, + /// [`Some`] when the new connection is an outgoing connection. + /// Addresses are dialed concurrently. Contains the addresses and errors + /// of dial attempts that failed before the one successful dial. + concurrent_dial_errors: Option)>>, }, /// A connection with the given peer has been closed, /// possibly as a result of an error. @@ -181,7 +185,14 @@ pub enum SwarmEvent { /// Address used to send back data to the remote. send_back_addr: Multiaddr, /// The error that happened. - error: PendingConnectionError, + error: PendingInboundConnectionError, + }, + /// Outgoing connection attempt failed. + OutgoingConnectionError { + /// If known, [`PeerId`] of the peer we tried to reach. + peer_id: Option, + /// Error that has been encountered. + error: DialError, }, /// We connected to a peer, but we immediately closed the connection because that peer is banned. BannedPeer { @@ -190,26 +201,6 @@ pub enum SwarmEvent { /// Endpoint of the connection that has been closed. endpoint: ConnectedPoint, }, - /// Tried to dial an address but it ended up being unreachaable. - UnreachableAddr { - /// `PeerId` that we were trying to reach. - peer_id: PeerId, - /// Address that we failed to reach. - address: Multiaddr, - /// Error that has been encountered. - error: PendingConnectionError, - /// Number of remaining connection attempts that are being tried for this peer. - attempts_remaining: u32, - }, - /// Tried to dial an address but it ended up being unreachaable. - /// Contrary to `UnreachableAddr`, we don't know the identity of the peer that we were trying - /// to reach. - UnknownPeerUnreachableAddr { - /// Address that we failed to reach. - address: Multiaddr, - /// Error that has been encountered. - error: PendingConnectionError, - }, /// One of our listeners has reported a new local listening address. NewListenAddr { /// The listener that is listening on the new address. @@ -245,10 +236,10 @@ pub enum SwarmEvent { }, /// A new dialing attempt has been initiated. /// - /// A [`ConnectionEstablished`](SwarmEvent::ConnectionEstablished) - /// event is reported if the dialing attempt succeeds, otherwise a - /// [`UnreachableAddr`](SwarmEvent::UnreachableAddr) event is reported - /// with `attempts_remaining` equal to 0. + /// A [`ConnectionEstablished`](SwarmEvent::ConnectionEstablished) event is + /// reported if the dialing attempt succeeds, otherwise a + /// [`OutgoingConnectionError`](SwarmEvent::OutgoingConnectionError) event + /// is reported with `attempts_remaining` equal to 0. Dialing(PeerId), } @@ -324,8 +315,9 @@ where /// Remove some listener. /// - /// Returns `Ok(())` if there was a listener with this ID. - pub fn remove_listener(&mut self, id: ListenerId) -> Result<(), ()> { + /// Returns `true` if there was a listener with this ID, `false` + /// otherwise. + pub fn remove_listener(&mut self, id: ListenerId) -> bool { self.network.remove_listener(id) } @@ -363,38 +355,36 @@ where if self.banned_peers.contains(peer_id) { let error = DialError::Banned; self.behaviour - .inject_dial_failure(peer_id, handler, error.clone()); + .inject_dial_failure(Some(*peer_id), handler, &error); return Err(error); } - let self_listening = &self.listened_addrs; + let self_listening = self.listened_addrs.clone(); let mut addrs = self .behaviour .addresses_of_peer(peer_id) .into_iter() - .filter(|a| !self_listening.contains(a)); - - let first = match addrs.next() { - Some(first) => first, - None => { - let error = DialError::NoAddresses; - self.behaviour - .inject_dial_failure(peer_id, handler, error.clone()); - return Err(error); - } + .filter(move |a| !self_listening.contains(a)) + .peekable(); + + if addrs.peek().is_none() { + let error = DialError::NoAddresses; + self.behaviour + .inject_dial_failure(Some(*peer_id), handler, &error); + return Err(error); }; let handler = handler .into_node_handler_builder() .with_substream_upgrade_protocol_override(self.substream_upgrade_protocol_override); - match self.network.peer(*peer_id).dial(first, addrs, handler) { + match self.network.peer(*peer_id).dial(addrs, handler) { Ok(_connection_id) => Ok(()), Err(error) => { let (error, handler) = DialError::from_network_dial_error(error); self.behaviour.inject_dial_failure( - peer_id, + Some(*peer_id), handler.into_protocols_handler(), - error.clone(), + &error, ); Err(error) } @@ -552,6 +542,7 @@ where Poll::Ready(NetworkEvent::ConnectionEstablished { connection, num_established, + concurrent_dial_errors, }) => { let peer_id = connection.peer_id(); let endpoint = connection.endpoint().clone(); @@ -564,15 +555,20 @@ where return Poll::Ready(SwarmEvent::BannedPeer { peer_id, endpoint }); } else { log::debug!( - "Connection established: {:?}; Total (peer): {}.", - connection.connected(), + "Connection established: {:?} {:?}; Total (peer): {}.", + connection.peer_id(), + connection.endpoint(), num_established ); let endpoint = connection.endpoint().clone(); + let failed_addresses = concurrent_dial_errors + .as_ref() + .map(|es| es.iter().map(|(a, _)| a).cloned().collect()); this.behaviour.inject_connection_established( &peer_id, &connection.id(), &endpoint, + failed_addresses.as_ref(), ); if num_established.get() == 1 { this.behaviour.inject_connected(&peer_id); @@ -581,6 +577,7 @@ where peer_id, num_established, endpoint, + concurrent_dial_errors, }); } } @@ -624,13 +621,22 @@ where ); let local_addr = connection.local_addr.clone(); let send_back_addr = connection.send_back_addr.clone(); - if let Err(e) = this.network.accept(connection, handler) { - log::warn!("Incoming connection rejected: {:?}", e); + match this.network.accept(connection, handler) { + Ok(_connection_id) => { + return Poll::Ready(SwarmEvent::IncomingConnection { + local_addr, + send_back_addr, + }); + } + Err((connection_limit, handler)) => { + this.behaviour.inject_listen_failure( + &local_addr, + &send_back_addr, + handler.into_protocols_handler(), + ); + log::warn!("Incoming connection rejected: {:?}", connection_limit); + } } - return Poll::Ready(SwarmEvent::IncomingConnection { - local_addr, - send_back_addr, - }); } Poll::Ready(NetworkEvent::NewListenerAddress { listener_id, @@ -710,53 +716,39 @@ where } Poll::Ready(NetworkEvent::DialError { peer_id, - multiaddr, error, - attempts_remaining, + handler, }) => { - this.behaviour - .inject_addr_reach_failure(Some(&peer_id), &multiaddr, &error); + let error = error.into(); - let num_remaining: u32; - match attempts_remaining { - DialAttemptsRemaining::Some(n) => { - num_remaining = n.into(); - } - DialAttemptsRemaining::None(handler) => { - num_remaining = 0; - this.behaviour.inject_dial_failure( - &peer_id, - handler.into_protocols_handler(), - DialError::UnreachableAddr(multiaddr.clone()), - ); - } - } + this.behaviour.inject_dial_failure( + Some(peer_id), + handler.into_protocols_handler(), + &error, + ); log::debug!( - "Connection attempt to {:?} via {:?} failed with {:?}. Attempts remaining: {}.", - peer_id, multiaddr, error, num_remaining, + "Connection attempt to {:?} failed with {:?}.", + peer_id, + error, ); - return Poll::Ready(SwarmEvent::UnreachableAddr { - peer_id, - address: multiaddr, + return Poll::Ready(SwarmEvent::OutgoingConnectionError { + peer_id: Some(peer_id), error, - attempts_remaining: num_remaining, }); } - Poll::Ready(NetworkEvent::UnknownPeerDialError { - multiaddr, error, .. - }) => { - log::debug!( - "Connection attempt to address {:?} of unknown peer failed with {:?}", - multiaddr, - error + Poll::Ready(NetworkEvent::UnknownPeerDialError { error, handler }) => { + log::debug!("Connection attempt to unknown peer failed with {:?}", error); + let error = error.into(); + this.behaviour.inject_dial_failure( + None, + handler.into_protocols_handler(), + &error, ); - this.behaviour - .inject_addr_reach_failure(None, &multiaddr, &error); - return Poll::Ready(SwarmEvent::UnknownPeerUnreachableAddr { - address: multiaddr, - error, + return Poll::Ready(SwarmEvent::OutgoingConnectionError { + peer_id: None, + error: error, }); } } @@ -826,29 +818,16 @@ where return Poll::Ready(SwarmEvent::Dialing(peer_id)); } } else { - // Even if the condition for a _new_ dialing attempt is not met, - // we always add any potentially new addresses of the peer to an - // ongoing dialing attempt, if there is one. log::trace!( "Condition for new dialing attempt to {:?} not met: {:?}", peer_id, condition ); - let self_listening = &this.listened_addrs; - if let Some(mut peer) = this.network.peer(peer_id).into_dialing() { - let addrs = this.behaviour.addresses_of_peer(peer.id()); - let mut attempt = peer.some_attempt(); - for a in addrs { - if !self_listening.contains(&a) { - attempt.add_address(a); - } - } - } this.behaviour.inject_dial_failure( - &peer_id, + Some(peer_id), handler, - DialError::DialPeerConditionFalse(condition), + &DialError::DialPeerConditionFalse(condition), ); } } @@ -961,6 +940,7 @@ fn notify_any<'a, TTrans, THandler, TBehaviour>( ) -> Option<(THandlerInEvent, SmallVec<[ConnectionId; 10]>)> where TTrans: Transport, + TTrans::Error: Send + 'static, TBehaviour: NetworkBehaviour, THandler: IntoConnectionHandler, THandler::Handler: ConnectionHandler< @@ -1137,6 +1117,12 @@ where self } + /// Number of addresses concurrently dialed for a single outbound connection attempt. + pub fn dial_concurrency_factor(mut self, factor: NonZeroU8) -> Self { + self.network_config = self.network_config.with_dial_concurrency_factor(factor); + self + } + /// Configures the connection limits. pub fn connection_limits(mut self, limits: ConnectionLimits) -> Self { self.network_config = self.network_config.with_connection_limits(limits); @@ -1200,17 +1186,13 @@ where } /// The possible failures of dialing. -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum DialError { /// The peer is currently banned. Banned, /// The configured limit for simultaneous outgoing connections /// has been reached. ConnectionLimit(ConnectionLimit), - /// The address given for dialing is invalid. - InvalidAddress(Multiaddr), - /// Tried to dial an address but it ended up being unreachaable. - UnreachableAddr(Multiaddr), /// The peer being dialed is the local peer and thus the dial was aborted. LocalPeerId, /// [`NetworkBehaviour::addresses_of_peer`] returned no addresses @@ -1218,6 +1200,15 @@ pub enum DialError { NoAddresses, /// The provided [`DialPeerCondition`] evaluated to false and thus the dial was aborted. DialPeerConditionFalse(DialPeerCondition), + /// Pending connection attempt has been aborted. + Aborted, + /// The peer identity obtained on the connection did not + /// match the one that was expected or is otherwise invalid. + InvalidPeerId, + /// An I/O error occurred on the connection. + ConnectionIo(io::Error), + /// An error occurred while negotiating the transport protocol(s) on a connection. + Transport(Vec<(Multiaddr, TransportError)>), } impl DialError { @@ -1226,22 +1217,29 @@ impl DialError { network::DialError::ConnectionLimit { limit, handler } => { (DialError::ConnectionLimit(limit), handler) } - network::DialError::InvalidAddress { address, handler } => { - (DialError::InvalidAddress(address), handler) - } network::DialError::LocalPeerId { handler } => (DialError::LocalPeerId, handler), } } } +impl From> for DialError { + fn from(error: PendingOutboundConnectionError) -> Self { + match error { + PendingConnectionError::ConnectionLimit(limit) => DialError::ConnectionLimit(limit), + PendingConnectionError::Aborted => DialError::Aborted, + PendingConnectionError::InvalidPeerId => DialError::InvalidPeerId, + PendingConnectionError::IO(e) => DialError::ConnectionIo(e), + PendingConnectionError::Transport(e) => DialError::Transport(e), + } + } +} + impl fmt::Display for DialError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { DialError::ConnectionLimit(err) => write!(f, "Dial error: {}", err), DialError::NoAddresses => write!(f, "Dial error: no addresses for peer."), DialError::LocalPeerId => write!(f, "Dial error: tried to dial local peer id."), - DialError::InvalidAddress(a) => write!(f, "Dial error: invalid address: {}", a), - DialError::UnreachableAddr(a) => write!(f, "Dial error: unreachable address: {}", a), DialError::Banned => write!(f, "Dial error: peer is banned."), DialError::DialPeerConditionFalse(c) => { write!( @@ -1250,6 +1248,16 @@ impl fmt::Display for DialError { c ) } + DialError::Aborted => write!( + f, + "Dial error: Pending connection attempt has been aborted." + ), + DialError::InvalidPeerId => write!(f, "Dial error: Invalid peer ID."), + DialError::ConnectionIo(e) => write!( + f, + "Dial error: An I/O error occurred on the connection: {:?}.", e + ), + DialError::Transport(e) => write!(f, "An error occurred while negotiating the transport protocol(s) on a connection: {:?}.", e), } } } @@ -1258,12 +1266,14 @@ impl error::Error for DialError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { DialError::ConnectionLimit(err) => Some(err), - DialError::InvalidAddress(_) => None, - DialError::UnreachableAddr(_) => None, DialError::LocalPeerId => None, DialError::NoAddresses => None, DialError::Banned => None, DialError::DialPeerConditionFalse(_) => None, + DialError::Aborted => None, + DialError::InvalidPeerId => None, + DialError::ConnectionIo(_) => None, + DialError::Transport(_) => None, } } } diff --git a/swarm/src/test.rs b/swarm/src/test.rs index 457701899a8..54a806ca6d2 100644 --- a/swarm/src/test.rs +++ b/swarm/src/test.rs @@ -108,8 +108,7 @@ where ConnectionId, <::Handler as ProtocolsHandler>::OutEvent, )>, - pub inject_addr_reach_failure: Vec<(Option, Multiaddr)>, - pub inject_dial_failure: Vec, + pub inject_dial_failure: Vec>, pub inject_new_listener: Vec, pub inject_new_listen_addr: Vec<(ListenerId, Multiaddr)>, pub inject_new_external_addr: Vec, @@ -133,7 +132,6 @@ where inject_connection_established: Vec::new(), inject_connection_closed: Vec::new(), inject_event: Vec::new(), - inject_addr_reach_failure: Vec::new(), inject_dial_failure: Vec::new(), inject_new_listener: Vec::new(), inject_new_listen_addr: Vec::new(), @@ -153,7 +151,6 @@ where self.inject_connection_established = Vec::new(); self.inject_connection_closed = Vec::new(); self.inject_event = Vec::new(); - self.inject_addr_reach_failure = Vec::new(); self.inject_dial_failure = Vec::new(); self.inject_new_listen_addr = Vec::new(); self.inject_new_external_addr = Vec::new(); @@ -191,10 +188,16 @@ where self.inner.inject_connected(peer); } - fn inject_connection_established(&mut self, p: &PeerId, c: &ConnectionId, e: &ConnectedPoint) { + fn inject_connection_established( + &mut self, + p: &PeerId, + c: &ConnectionId, + e: &ConnectedPoint, + errors: Option<&Vec>, + ) { self.inject_connection_established .push((p.clone(), c.clone(), e.clone())); - self.inner.inject_connection_established(p, c, e); + self.inner.inject_connection_established(p, c, e, errors); } fn inject_disconnected(&mut self, peer: &PeerId) { @@ -224,23 +227,13 @@ where self.inner.inject_event(p, c, e); } - fn inject_addr_reach_failure( - &mut self, - p: Option<&PeerId>, - a: &Multiaddr, - e: &dyn std::error::Error, - ) { - self.inject_addr_reach_failure.push((p.cloned(), a.clone())); - self.inner.inject_addr_reach_failure(p, a, e); - } - fn inject_dial_failure( &mut self, - p: &PeerId, + p: Option, handler: Self::ProtocolsHandler, - error: DialError, + error: &DialError, ) { - self.inject_dial_failure.push(p.clone()); + self.inject_dial_failure.push(p); self.inner.inject_dial_failure(p, handler, error); } diff --git a/swarm/src/toggle.rs b/swarm/src/toggle.rs index 575d4e46809..b6af0e38237 100644 --- a/swarm/src/toggle.rs +++ b/swarm/src/toggle.rs @@ -34,7 +34,7 @@ use libp2p_core::{ upgrade::{DeniedUpgrade, EitherUpgrade}, ConnectedPoint, Multiaddr, PeerId, }; -use std::{error, task::Context, task::Poll}; +use std::{task::Context, task::Poll}; /// Implementation of `NetworkBehaviour` that can be either in the disabled or enabled state. /// @@ -103,9 +103,10 @@ where peer_id: &PeerId, connection: &ConnectionId, endpoint: &ConnectedPoint, + errors: Option<&Vec>, ) { if let Some(inner) = self.inner.as_mut() { - inner.inject_connection_established(peer_id, connection, endpoint) + inner.inject_connection_established(peer_id, connection, endpoint, errors) } } @@ -146,22 +147,11 @@ where } } - fn inject_addr_reach_failure( - &mut self, - peer_id: Option<&PeerId>, - addr: &Multiaddr, - error: &dyn error::Error, - ) { - if let Some(inner) = self.inner.as_mut() { - inner.inject_addr_reach_failure(peer_id, addr, error) - } - } - fn inject_dial_failure( &mut self, - peer_id: &PeerId, + peer_id: Option, handler: Self::ProtocolsHandler, - error: DialError, + error: &DialError, ) { if let Some(inner) = self.inner.as_mut() { if let Some(handler) = handler.inner { diff --git a/transports/deflate/CHANGELOG.md b/transports/deflate/CHANGELOG.md index 72bdaba8828..58faa3c59eb 100644 --- a/transports/deflate/CHANGELOG.md +++ b/transports/deflate/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.30.0 [unreleased] +# 0.30.0-rc.1 [2021-10-15] - Make default features of `libp2p-core` optional. [PR 2181](https://github.com/libp2p/rust-libp2p/pull/2181) diff --git a/transports/deflate/Cargo.toml b/transports/deflate/Cargo.toml index 3ce579f0dea..0e966fad25b 100644 --- a/transports/deflate/Cargo.toml +++ b/transports/deflate/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-deflate" edition = "2018" description = "Deflate encryption protocol for libp2p" -version = "0.30.0" +version = "0.30.0-rc.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -11,7 +11,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] futures = "0.3.1" -libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } +libp2p-core = { version = "0.30.0-rc.1", path = "../../core", default-features = false } flate2 = "1.0" [dev-dependencies] diff --git a/transports/dns/CHANGELOG.md b/transports/dns/CHANGELOG.md index 41c0cc06b5e..a1ec630831c 100644 --- a/transports/dns/CHANGELOG.md +++ b/transports/dns/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.30.0 [unreleased] +# 0.30.0-rc.1 [2021-10-15] - Make default features of `libp2p-core` optional. [PR 2181](https://github.com/libp2p/rust-libp2p/pull/2181) diff --git a/transports/dns/Cargo.toml b/transports/dns/Cargo.toml index 51c9a8fadd1..13f50d705a2 100644 --- a/transports/dns/Cargo.toml +++ b/transports/dns/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-dns" edition = "2018" description = "DNS transport implementation for libp2p" -version = "0.30.0" +version = "0.30.0-rc.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -10,7 +10,7 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } +libp2p-core = { version = "0.30.0-rc.1", path = "../../core", default-features = false } log = "0.4.1" futures = "0.3.1" trust-dns-resolver = { version = "0.20", default-features = false, features = ["system-config"] } diff --git a/transports/noise/CHANGELOG.md b/transports/noise/CHANGELOG.md index 67830fb01fc..8f6327114cd 100644 --- a/transports/noise/CHANGELOG.md +++ b/transports/noise/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.33.0 [unreleased] +# 0.33.0-rc.1 [2021-10-15] - Make default features of `libp2p-core` optional. [PR 2181](https://github.com/libp2p/rust-libp2p/pull/2181) diff --git a/transports/noise/Cargo.toml b/transports/noise/Cargo.toml index 201d7293aa4..f1f17c5ccca 100644 --- a/transports/noise/Cargo.toml +++ b/transports/noise/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "libp2p-noise" description = "Cryptographic handshake protocol using the noise framework." -version = "0.33.0" +version = "0.33.0-rc.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -12,9 +12,9 @@ bytes = "1" curve25519-dalek = "3.0.0" futures = "0.3.1" lazy_static = "1.2" -libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } +libp2p-core = { version = "0.30.0-rc.1", path = "../../core", default-features = false } log = "0.4" -prost = "0.8" +prost = "0.9" rand = "0.8.3" sha2 = "0.9.1" static_assertions = "1" @@ -35,4 +35,4 @@ quickcheck = "0.9.0" sodiumoxide = "0.2.5" [build-dependencies] -prost-build = "0.8" +prost-build = "0.9" diff --git a/transports/noise/src/protocol/x25519.rs b/transports/noise/src/protocol/x25519.rs index fb216909806..14ca8c60689 100644 --- a/transports/noise/src/protocol/x25519.rs +++ b/transports/noise/src/protocol/x25519.rs @@ -219,7 +219,7 @@ impl SecretKey { // let ed25519_sk = ed25519::SecretKey::from(ed); let mut curve25519_sk: [u8; 32] = [0; 32]; let hash = Sha512::digest(ed25519_sk.as_ref()); - curve25519_sk.copy_from_slice(&hash.as_ref()[..32]); + curve25519_sk.copy_from_slice(&hash[..32]); let sk = SecretKey(X25519(curve25519_sk)); // Copy curve25519_sk.zeroize(); sk diff --git a/transports/plaintext/CHANGELOG.md b/transports/plaintext/CHANGELOG.md index dfbf8d07bf9..d163633f9dd 100644 --- a/transports/plaintext/CHANGELOG.md +++ b/transports/plaintext/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.30.0 [unreleased] +# 0.30.0-rc.1 [2021-10-15] - Make default features of `libp2p-core` optional. [PR 2181](https://github.com/libp2p/rust-libp2p/pull/2181) diff --git a/transports/plaintext/Cargo.toml b/transports/plaintext/Cargo.toml index e0f7d6778da..f3afb9e3624 100644 --- a/transports/plaintext/Cargo.toml +++ b/transports/plaintext/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-plaintext" edition = "2018" description = "Plaintext encryption dummy protocol for libp2p" -version = "0.30.0" +version = "0.30.0-rc.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -13,9 +13,9 @@ categories = ["network-programming", "asynchronous"] bytes = "1" futures = "0.3.1" asynchronous-codec = "0.6" -libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } +libp2p-core = { version = "0.30.0-rc.1", path = "../../core", default-features = false } log = "0.4.8" -prost = "0.8" +prost = "0.9" unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] } void = "1.0.2" @@ -25,4 +25,4 @@ quickcheck = "0.9.0" rand = "0.7" [build-dependencies] -prost-build = "0.8" +prost-build = "0.9" diff --git a/transports/pnet/CHANGELOG.md b/transports/pnet/CHANGELOG.md index b32b95f8739..3e34debed16 100644 --- a/transports/pnet/CHANGELOG.md +++ b/transports/pnet/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.22.0 [unreleased] +# 0.22.0-rc.1 [2021-10-15] - Update dependencies. diff --git a/transports/pnet/Cargo.toml b/transports/pnet/Cargo.toml index 7b354c1e853..3191ae48e59 100644 --- a/transports/pnet/Cargo.toml +++ b/transports/pnet/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-pnet" edition = "2018" description = "Private swarm support for libp2p" -version = "0.22.0" +version = "0.22.0-rc.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/transports/tcp/CHANGELOG.md b/transports/tcp/CHANGELOG.md index 325672e194f..26e30a9969b 100644 --- a/transports/tcp/CHANGELOG.md +++ b/transports/tcp/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.30.0 [unreleased] +# 0.30.0-rc.1 [2021-10-15] - Make default features of `libp2p-core` optional. [PR 2181](https://github.com/libp2p/rust-libp2p/pull/2181) diff --git a/transports/tcp/Cargo.toml b/transports/tcp/Cargo.toml index 7e56026a628..ed457096763 100644 --- a/transports/tcp/Cargo.toml +++ b/transports/tcp/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-tcp" edition = "2018" description = "TCP/IP transport protocol for libp2p" -version = "0.30.0" +version = "0.30.0-rc.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -17,7 +17,7 @@ if-watch = { version = "0.2.0", optional = true } if-addrs = { version = "0.6.4", optional = true } ipnet = "2.0.0" libc = "0.2.80" -libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } +libp2p-core = { version = "0.30.0-rc.1", path = "../../core", default-features = false } log = "0.4.11" socket2 = { version = "0.4.0", features = ["all"] } tokio-crate = { package = "tokio", version = "1.0.1", default-features = false, features = ["net"], optional = true } diff --git a/transports/uds/CHANGELOG.md b/transports/uds/CHANGELOG.md index fe900a22918..dee2eef5595 100644 --- a/transports/uds/CHANGELOG.md +++ b/transports/uds/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.30.0 [unreleased] +# 0.30.0-rc.1 [2021-10-15] - Make default features of `libp2p-core` optional. [PR 2181](https://github.com/libp2p/rust-libp2p/pull/2181) diff --git a/transports/uds/Cargo.toml b/transports/uds/Cargo.toml index 84aca6450a1..402aa6432cf 100644 --- a/transports/uds/Cargo.toml +++ b/transports/uds/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-uds" edition = "2018" description = "Unix domain sockets transport for libp2p" -version = "0.30.0" +version = "0.30.0-rc.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -11,7 +11,7 @@ categories = ["network-programming", "asynchronous"] [target.'cfg(all(unix, not(target_os = "emscripten")))'.dependencies] async-std = { version = "1.6.2", optional = true } -libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } +libp2p-core = { version = "0.30.0-rc.1", path = "../../core", default-features = false } log = "0.4.1" futures = "0.3.1" tokio = { version = "1.0.1", default-features = false, features = ["net"], optional = true } diff --git a/transports/wasm-ext/CHANGELOG.md b/transports/wasm-ext/CHANGELOG.md index 278aaa01791..29b062adf20 100644 --- a/transports/wasm-ext/CHANGELOG.md +++ b/transports/wasm-ext/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.30.0 [unreleased] +# 0.30.0-rc.1 [2021-10-15] - Make default features of `libp2p-core` optional. [PR 2181](https://github.com/libp2p/rust-libp2p/pull/2181) diff --git a/transports/wasm-ext/Cargo.toml b/transports/wasm-ext/Cargo.toml index d5d0139ba03..5b2ad32a04a 100644 --- a/transports/wasm-ext/Cargo.toml +++ b/transports/wasm-ext/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libp2p-wasm-ext" -version = "0.30.0" +version = "0.30.0-rc.1" authors = ["Pierre Krieger "] edition = "2018" description = "Allows passing in an external transport in a WASM environment" @@ -12,7 +12,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] futures = "0.3.1" js-sys = "0.3.50" -libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } +libp2p-core = { version = "0.30.0-rc.1", path = "../../core", default-features = false } parity-send-wrapper = "0.1.0" wasm-bindgen = "0.2.42" wasm-bindgen-futures = "0.4.4" diff --git a/transports/websocket/CHANGELOG.md b/transports/websocket/CHANGELOG.md index c3cfd407eda..37d40248370 100644 --- a/transports/websocket/CHANGELOG.md +++ b/transports/websocket/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.31.0 [unreleased] +# 0.31.0-rc.1 [2021-10-15] - Make default features of `libp2p-core` optional. [PR 2181](https://github.com/libp2p/rust-libp2p/pull/2181) diff --git a/transports/websocket/Cargo.toml b/transports/websocket/Cargo.toml index a7feeac7c86..b4a11d3934e 100644 --- a/transports/websocket/Cargo.toml +++ b/transports/websocket/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-websocket" edition = "2018" description = "WebSocket transport for libp2p" -version = "0.31.0" +version = "0.31.0-rc.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -13,11 +13,11 @@ categories = ["network-programming", "asynchronous"] futures-rustls = "0.21" either = "1.5.3" futures = "0.3.1" -libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } +libp2p-core = { version = "0.30.0-rc.1", path = "../../core", default-features = false } log = "0.4.8" quicksink = "0.1" rw-stream-sink = "0.2.0" -soketto = { version = "0.4.1", features = ["deflate"] } +soketto = { version = "0.7.0", features = ["deflate"] } url = "2.1" webpki-roots = "0.21" diff --git a/transports/websocket/src/framed.rs b/transports/websocket/src/framed.rs index 52635ec5ba0..02529036944 100644 --- a/transports/websocket/src/framed.rs +++ b/transports/websocket/src/framed.rs @@ -205,13 +205,13 @@ where .receive_request() .map_err(|e| Error::Handshake(Box::new(e))) .await?; - request.into_key() + request.key() }; trace!("accepting websocket handshake request from {}", remote2); let response = handshake::server::Response::Accept { - key: &ws_key, + key: ws_key, protocol: None, }; @@ -583,6 +583,7 @@ where Ok(soketto::Incoming::Pong(pong)) => { Some((Ok(IncomingData::Pong(Vec::from(pong))), (data, receiver))) } + Ok(soketto::Incoming::Closed(_)) => None, Err(connection::Error::Closed) => None, Err(e) => Some((Err(e), (data, receiver))), }