From decfeadc0dd5092a011138508e184bb57d9b328f Mon Sep 17 00:00:00 2001 From: McFranko Date: Fri, 1 Oct 2021 16:23:08 +0000 Subject: [PATCH 01/21] protocols/gossipsub: Implement std::error::Error for error types (#2254) Co-authored-by: Thomas Eizinger --- protocols/gossipsub/CHANGELOG.md | 3 +++ protocols/gossipsub/src/error.rs | 39 ++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/protocols/gossipsub/CHANGELOG.md b/protocols/gossipsub/CHANGELOG.md index 9cf9c8db4bd..ac66461ceed 100644 --- a/protocols/gossipsub/CHANGELOG.md +++ b/protocols/gossipsub/CHANGELOG.md @@ -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/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) From 9f331e517f524dd112bf9e12b26d9dfae9162022 Mon Sep 17 00:00:00 2001 From: Oliver Wangler Date: Fri, 1 Oct 2021 18:45:19 +0200 Subject: [PATCH 02/21] protocols/rendezvous: Export Cookie (#2256) Co-authored-by: Max Inden --- protocols/rendezvous/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From 5cbd4735ddd687080ff05b16c5b3e38de3b56072 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sat, 2 Oct 2021 02:07:42 +0200 Subject: [PATCH 03/21] README: Add Thomas as maintainer (#2257) Co-authored-by: Thomas Eizinger --- README.md | 5 +++++ 1 file changed, 5 insertions(+) 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) From c7abb6f70c97cdc75aed021870f5902ec2cb268e Mon Sep 17 00:00:00 2001 From: Sztergbaum Roman Date: Mon, 4 Oct 2021 20:14:50 +0200 Subject: [PATCH 04/21] transports/noise: Fix compilation with additional generic-array features (#2264) For crate that depends on `generic-array = { version = "0.14.3", features = ["serde", "more_lengths"] }` It's seems that `as_ref()` is ambiguous. --- transports/noise/src/protocol/x25519.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From e9ac6d2a6ae48391c104e586ad4c4c5cecf1d3a4 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 5 Oct 2021 18:46:08 +1100 Subject: [PATCH 05/21] .github/workflows/ci: Improve cache effectiveness (#2260) To have caches operate at its maximum usefulness, we need to have a 1-to-1 mapping between build-comamnd and cache key. Otherwise rustc has to rebuild certain artifacts because they were not in the cache. By using matrices, we make github parallelise some of our jobs. This has a double positive impact on CI runtime. Not only are our caches more effective now, several jobs are now run in parallel. Co-authored-by: Max Inden --- .github/workflows/ci.yml | 48 ++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d4148477108..05391381678 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 @@ -20,19 +27,20 @@ jobs: - uses: actions/checkout@v2.3.4 - 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: @@ -46,18 +54,11 @@ jobs: - 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 - - - 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 @@ -94,6 +92,8 @@ jobs: - uses: actions/checkout@v2.3.4 + - 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 From d60a6d55dc1c5aa9afdd19282c3627fb76d2c506 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 5 Oct 2021 23:10:28 +0200 Subject: [PATCH 06/21] transports/websocket/: Update to soketto v0.7.0 #2271 Co-authored-by: James Wilson --- transports/websocket/Cargo.toml | 2 +- transports/websocket/src/framed.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/transports/websocket/Cargo.toml b/transports/websocket/Cargo.toml index a7feeac7c86..d0663746d08 100644 --- a/transports/websocket/Cargo.toml +++ b/transports/websocket/Cargo.toml @@ -17,7 +17,7 @@ libp2p-core = { version = "0.30.0", path = "../../core", default-features = fals 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))), } From 937b59de8946eb3773e292691dac4e5f3e081706 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Oct 2021 23:26:13 +0200 Subject: [PATCH 07/21] build(deps): Update lru requirement from 0.6 to 0.7 (#2267) Updates the requirements on [lru](https://github.com/jeromefroe/lru-rs) to permit the latest version. - [Release notes](https://github.com/jeromefroe/lru-rs/releases) - [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/jeromefroe/lru-rs/compare/0.6.0...0.7.0) --- updated-dependencies: - dependency-name: lru dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- protocols/request-response/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/request-response/Cargo.toml b/protocols/request-response/Cargo.toml index 288ee60bf6e..db6d2162490 100644 --- a/protocols/request-response/Cargo.toml +++ b/protocols/request-response/Cargo.toml @@ -16,7 +16,7 @@ futures = "0.3.1" libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } libp2p-swarm = { version = "0.31.0", 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"] } From 5ef430b8d8d390c904440043f6a8d8bb83b68507 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 7 Oct 2021 14:07:25 +0200 Subject: [PATCH 08/21] misc/metrics/examples: Set openmetrics-text content-type (#2278) Set "openmetrics-text" content type on HTTP GET response. Makes sure Prometheus server parses returned metrics via OpenMetrics format instead of legacy Prometheus format. --- misc/metrics/examples/metrics.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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?; From 57f34f6f266ad67e33e53236bd0c95af0ed9f833 Mon Sep 17 00:00:00 2001 From: r-zig Date: Fri, 8 Oct 2021 11:18:34 +0300 Subject: [PATCH 09/21] core/src/connections: Avoid call to contains twice (#2279) --- core/src/connection/listeners.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/connection/listeners.rs b/core/src/connection/listeners.rs index cf6daa17f5f..f9a0c6ac11d 100644 --- a/core/src/connection/listeners.rs +++ b/core/src/connection/listeners.rs @@ -264,8 +264,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; From 3eb0344832c78421890b149608f15bee5225c793 Mon Sep 17 00:00:00 2001 From: Vishaal Selvaraj Date: Sun, 10 Oct 2021 17:13:58 +0530 Subject: [PATCH 10/21] protocols/kad: Remove outdated TODO (#2282) Signed-off-by: supercmmetry --- protocols/kad/src/behaviour.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/protocols/kad/src/behaviour.rs b/protocols/kad/src/behaviour.rs index 80236eb5686..7721d5a9a83 100644 --- a/protocols/kad/src/behaviour.rs +++ b/protocols/kad/src/behaviour.rs @@ -2448,12 +2448,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 { From 7718d1de387c63786cffdcbfcfdf1c4df40bc21a Mon Sep 17 00:00:00 2001 From: Elena Frank <57632201+elenaf9@users.noreply.github.com> Date: Mon, 11 Oct 2021 22:38:55 +0200 Subject: [PATCH 11/21] core/connection/listeners: Create event on remove_listener (#2261) Create a `ListenersEvent::Closed` when a listener is removed via `Swarm::remove_listener`. This makes it more consistent with `Swarm::listen_on`, and also informs the Swarm about the associated expired addresses. Co-authored-by: Max Inden --- core/CHANGELOG.md | 4 ++ core/src/connection/listeners.rs | 73 +++++++++++++++++++++++++++----- core/src/network.rs | 5 ++- swarm/CHANGELOG.md | 4 ++ swarm/src/lib.rs | 5 ++- 5 files changed, 77 insertions(+), 14 deletions(-) diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 1c4ace6835f..d013c7d67f5 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -36,6 +36,9 @@ - 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]). + [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 +47,7 @@ [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 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/src/connection/listeners.rs b/core/src/connection/listeners.rs index f9a0c6ac11d..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() { @@ -292,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), - }) + }); } } } @@ -537,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/network.rs b/core/src/network.rs index 831a99c4b01..8c68a55ea38 100644 --- a/core/src/network.rs +++ b/core/src/network.rs @@ -147,8 +147,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) } diff --git a/swarm/CHANGELOG.md b/swarm/CHANGELOG.md index 5894591c3bd..8bc85e4bb6d 100644 --- a/swarm/CHANGELOG.md +++ b/swarm/CHANGELOG.md @@ -42,11 +42,15 @@ parameters to `NetworkBehaviourAction`. See [PR 2191]. +- Return `bool` instead of `Result<(), ()>` for `Swarm::remove_listener`(see + [PR 2261]). + [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 2261]: https://github.com/libp2p/rust-libp2p/pull/2261 # 0.30.0 [2021-07-12] diff --git a/swarm/src/lib.rs b/swarm/src/lib.rs index a903113c2a9..2a2dca6675d 100644 --- a/swarm/src/lib.rs +++ b/swarm/src/lib.rs @@ -324,8 +324,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) } From 6d3ab8a3debe8d69dcd004173999732f12d0da96 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 14 Oct 2021 06:46:34 +1100 Subject: [PATCH 12/21] protocols/identify: Assist in peer discovery based on reported listen addresses from other peers (#2232) Co-authored-by: Max Inden --- protocols/identify/CHANGELOG.md | 4 + protocols/identify/Cargo.toml | 1 + protocols/identify/src/identify.rs | 132 +++++++++++++++++++++++++++-- 3 files changed, 129 insertions(+), 8 deletions(-) diff --git a/protocols/identify/CHANGELOG.md b/protocols/identify/CHANGELOG.md index b05cafa75e8..1d7378d0771 100644 --- a/protocols/identify/CHANGELOG.md +++ b/protocols/identify/CHANGELOG.md @@ -5,6 +5,10 @@ - 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..f540dc4c687 100644 --- a/protocols/identify/Cargo.toml +++ b/protocols/identify/Cargo.toml @@ -14,6 +14,7 @@ futures = "0.3.1" libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } libp2p-swarm = { version = "0.31.0", path = "../../swarm" } log = "0.4.1" +lru = "0.6" prost = "0.8" smallvec = "1.6.1" wasm-timer = "0.2" diff --git a/protocols/identify/src/identify.rs b/protocols/identify/src/identify.rs index d75dfb72054..cc157216b30 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, } } @@ -254,6 +275,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 +413,27 @@ 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() + } + + fn inject_addr_reach_failure( + &mut self, + peer_id: Option<&PeerId>, + addr: &Multiaddr, + _: &dyn std::error::Error, + ) { + if let Some(peer) = peer_id { + if let Some(entry) = self.discovered_peers.get_mut(peer) { + entry.remove(addr); + } + } + } } /// Event emitted by the `Identify` behaviour. @@ -552,11 +598,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 +607,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 +666,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); + } } From 3cc102da91c62fb72548130e20ddbf1dae520878 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Oct 2021 14:16:54 +0200 Subject: [PATCH 13/21] build(deps): Update prost requirement from 0.8 to 0.9 (#2287) Updates the requirements on [prost](https://github.com/tokio-rs/prost) to permit the latest version. - [Release notes](https://github.com/tokio-rs/prost/releases) - [Commits](https://github.com/tokio-rs/prost/compare/v0.8.0...v0.9.0) --- updated-dependencies: - dependency-name: prost dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- core/Cargo.toml | 2 +- protocols/floodsub/Cargo.toml | 2 +- protocols/gossipsub/Cargo.toml | 2 +- protocols/identify/Cargo.toml | 2 +- protocols/kad/Cargo.toml | 2 +- protocols/relay/Cargo.toml | 2 +- protocols/rendezvous/Cargo.toml | 2 +- transports/noise/Cargo.toml | 2 +- transports/plaintext/Cargo.toml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index e2ca3996738..649e8f73a15 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -25,7 +25,7 @@ multihash = { version = "0.14", default-features = false, features = ["std", "mu multistream-select = { version = "0.10", path = "../misc/multistream-select" } parking_lot = "0.11.0" pin-project = "1.0.0" -prost = "0.8" +prost = "0.9" rand = "0.7" rw-stream-sink = "0.2.0" sha2 = "0.9.1" diff --git a/protocols/floodsub/Cargo.toml b/protocols/floodsub/Cargo.toml index 0b8bf67b0b2..d494b1f2724 100644 --- a/protocols/floodsub/Cargo.toml +++ b/protocols/floodsub/Cargo.toml @@ -16,7 +16,7 @@ futures = "0.3.1" libp2p-core = { version = "0.30.0", path = "../../core", default-features = false } libp2p-swarm = { version = "0.31.0", path = "../../swarm" } log = "0.4" -prost = "0.8" +prost = "0.9" rand = "0.7" smallvec = "1.6.1" diff --git a/protocols/gossipsub/Cargo.toml b/protocols/gossipsub/Cargo.toml index 115051a5ef4..a0ecbd5d2fe 100644 --- a/protocols/gossipsub/Cargo.toml +++ b/protocols/gossipsub/Cargo.toml @@ -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" diff --git a/protocols/identify/Cargo.toml b/protocols/identify/Cargo.toml index f540dc4c687..7a7333739b1 100644 --- a/protocols/identify/Cargo.toml +++ b/protocols/identify/Cargo.toml @@ -15,7 +15,7 @@ libp2p-core = { version = "0.30.0", path = "../../core", default-features = fals libp2p-swarm = { version = "0.31.0", path = "../../swarm" } log = "0.4.1" lru = "0.6" -prost = "0.8" +prost = "0.9" smallvec = "1.6.1" wasm-timer = "0.2" diff --git a/protocols/kad/Cargo.toml b/protocols/kad/Cargo.toml index 20fe6fe2c75..8e150779d46 100644 --- a/protocols/kad/Cargo.toml +++ b/protocols/kad/Cargo.toml @@ -19,7 +19,7 @@ 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" +prost = "0.9" rand = "0.7.2" sha2 = "0.9.1" smallvec = "1.6.1" diff --git a/protocols/relay/Cargo.toml b/protocols/relay/Cargo.toml index aedfe262f0b..a9e71711a08 100644 --- a/protocols/relay/Cargo.toml +++ b/protocols/relay/Cargo.toml @@ -18,7 +18,7 @@ libp2p-core = { version = "0.30.0", path = "../../core", default-features = fals libp2p-swarm = { version = "0.31.0", 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"] } diff --git a/protocols/rendezvous/Cargo.toml b/protocols/rendezvous/Cargo.toml index fcc176dd484..ac11088d164 100644 --- a/protocols/rendezvous/Cargo.toml +++ b/protocols/rendezvous/Cargo.toml @@ -13,7 +13,7 @@ categories = ["network-programming", "asynchronous"] 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" +prost = "0.9" void = "1" log = "0.4" futures = { version = "0.3", default-features = false, features = ["std"] } diff --git a/transports/noise/Cargo.toml b/transports/noise/Cargo.toml index 201d7293aa4..66f7e7f5294 100644 --- a/transports/noise/Cargo.toml +++ b/transports/noise/Cargo.toml @@ -14,7 +14,7 @@ futures = "0.3.1" lazy_static = "1.2" libp2p-core = { version = "0.30.0", 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" diff --git a/transports/plaintext/Cargo.toml b/transports/plaintext/Cargo.toml index e0f7d6778da..da7a7ef6774 100644 --- a/transports/plaintext/Cargo.toml +++ b/transports/plaintext/Cargo.toml @@ -15,7 +15,7 @@ futures = "0.3.1" asynchronous-codec = "0.6" libp2p-core = { version = "0.30.0", 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" From c0d7d4a9ebdf25e60147ae25657f03f8278d386c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Oct 2021 14:44:34 +0200 Subject: [PATCH 14/21] build(deps): Update prost-build requirement from 0.8 to 0.9 (#2288) Updates the requirements on [prost-build](https://github.com/tokio-rs/prost) to permit the latest version. - [Release notes](https://github.com/tokio-rs/prost/releases) - [Commits](https://github.com/tokio-rs/prost/compare/v0.8.0...v0.9.0) --- updated-dependencies: - dependency-name: prost-build dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- core/Cargo.toml | 2 +- protocols/floodsub/Cargo.toml | 2 +- protocols/gossipsub/Cargo.toml | 2 +- protocols/identify/Cargo.toml | 2 +- protocols/kad/Cargo.toml | 2 +- protocols/relay/Cargo.toml | 2 +- protocols/rendezvous/Cargo.toml | 2 +- transports/noise/Cargo.toml | 2 +- transports/plaintext/Cargo.toml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 649e8f73a15..ed5770310de 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -50,7 +50,7 @@ quickcheck = "0.9.0" wasm-timer = "0.2" [build-dependencies] -prost-build = "0.8" +prost-build = "0.9" [features] default = ["secp256k1"] diff --git a/protocols/floodsub/Cargo.toml b/protocols/floodsub/Cargo.toml index d494b1f2724..a08fcc49bcf 100644 --- a/protocols/floodsub/Cargo.toml +++ b/protocols/floodsub/Cargo.toml @@ -21,4 +21,4 @@ rand = "0.7" smallvec = "1.6.1" [build-dependencies] -prost-build = "0.8" +prost-build = "0.9" diff --git a/protocols/gossipsub/Cargo.toml b/protocols/gossipsub/Cargo.toml index a0ecbd5d2fe..7f91804a9aa 100644 --- a/protocols/gossipsub/Cargo.toml +++ b/protocols/gossipsub/Cargo.toml @@ -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/identify/Cargo.toml b/protocols/identify/Cargo.toml index 7a7333739b1..b6bd5e4ee92 100644 --- a/protocols/identify/Cargo.toml +++ b/protocols/identify/Cargo.toml @@ -27,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/kad/Cargo.toml b/protocols/kad/Cargo.toml index 8e150779d46..5a9440cf234 100644 --- a/protocols/kad/Cargo.toml +++ b/protocols/kad/Cargo.toml @@ -35,4 +35,4 @@ libp2p-yamux = { path = "../../muxers/yamux" } quickcheck = "0.9.0" [build-dependencies] -prost-build = "0.8" +prost-build = "0.9" diff --git a/protocols/relay/Cargo.toml b/protocols/relay/Cargo.toml index a9e71711a08..e94519182b3 100644 --- a/protocols/relay/Cargo.toml +++ b/protocols/relay/Cargo.toml @@ -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/rendezvous/Cargo.toml b/protocols/rendezvous/Cargo.toml index ac11088d164..9f9d084f780 100644 --- a/protocols/rendezvous/Cargo.toml +++ b/protocols/rendezvous/Cargo.toml @@ -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/transports/noise/Cargo.toml b/transports/noise/Cargo.toml index 66f7e7f5294..680902427d2 100644 --- a/transports/noise/Cargo.toml +++ b/transports/noise/Cargo.toml @@ -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/plaintext/Cargo.toml b/transports/plaintext/Cargo.toml index da7a7ef6774..f09c60b607c 100644 --- a/transports/plaintext/Cargo.toml +++ b/transports/plaintext/Cargo.toml @@ -25,4 +25,4 @@ quickcheck = "0.9.0" rand = "0.7" [build-dependencies] -prost-build = "0.8" +prost-build = "0.9" From 40c5335e3bd6afaea46c9c8116ba8ff16a6bdf91 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 14 Oct 2021 18:05:07 +0200 Subject: [PATCH 15/21] core/: Concurrent dial attempts (#2248) Concurrently dial address candidates within a single dial attempt. Main motivation for this feature is to increase success rate on hole punching (see https://github.com/libp2p/rust-libp2p/issues/1896#issuecomment-885894496 for details). Though, as a nice side effect, as one would expect, it does improve connection establishment time. Cleanups and fixes done along the way: - Merge `pool.rs` and `manager.rs`. - Instead of manually implementing state machines in `task.rs` use `async/await`. - Fix bug where `NetworkBehaviour::inject_connection_closed` is called without a previous `NetworkBehaviour::inject_connection_established` (see https://github.com/libp2p/rust-libp2p/issues/2242). - Return handler to behaviour on incoming connection limit error. Missed in https://github.com/libp2p/rust-libp2p/issues/2242. --- core/CHANGELOG.md | 9 + core/src/connection.rs | 84 +- core/src/connection/error.rs | 43 +- core/src/connection/manager.rs | 488 -------- core/src/connection/manager/task.rs | 395 ------- core/src/connection/pool.rs | 1167 ++++++++++++------- core/src/connection/pool/concurrent_dial.rs | 158 +++ core/src/connection/pool/task.rs | 260 +++++ core/src/network.rs | 468 +++----- core/src/network/event.rs | 57 +- core/src/network/peer.rs | 234 ++-- core/tests/concurrent_dialing.rs | 167 +++ core/tests/connection_limits.rs | 14 +- core/tests/network_dial_error.rs | 58 +- core/tests/util.rs | 7 +- examples/file-sharing.rs | 9 +- misc/metrics/src/swarm.rs | 139 ++- misc/multistream-select/tests/transport.rs | 1 + protocols/gossipsub/src/behaviour.rs | 1 + protocols/gossipsub/src/behaviour/tests.rs | 5 + protocols/identify/src/identify.rs | 45 +- protocols/kad/Cargo.toml | 1 + protocols/kad/src/behaviour.rs | 121 +- protocols/kad/src/behaviour/test.rs | 3 +- protocols/relay/src/behaviour.rs | 66 +- protocols/relay/tests/lib.rs | 43 +- protocols/rendezvous/examples/discover.rs | 11 - protocols/rendezvous/tests/harness/mod.rs | 39 +- protocols/rendezvous/tests/rendezvous.rs | 27 +- protocols/request-response/src/lib.rs | 42 +- swarm-derive/src/lib.rs | 28 +- swarm/CHANGELOG.md | 18 + swarm/src/behaviour.rs | 45 +- swarm/src/lib.rs | 239 ++-- swarm/src/test.rs | 31 +- swarm/src/toggle.rs | 20 +- 36 files changed, 2233 insertions(+), 2310 deletions(-) delete mode 100644 core/src/connection/manager.rs delete mode 100644 core/src/connection/manager/task.rs create mode 100644 core/src/connection/pool/concurrent_dial.rs create mode 100644 core/src/connection/pool/task.rs create mode 100644 core/tests/concurrent_dialing.rs diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index d013c7d67f5..4d9f4bda667 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -39,6 +39,14 @@ - 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 @@ -47,6 +55,7 @@ [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/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/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/network.rs b/core/src/network.rs index 8c68a55ea38..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), } } @@ -172,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(); @@ -205,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 @@ -219,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`. @@ -260,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 { @@ -296,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. @@ -311,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< @@ -343,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) { @@ -406,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, @@ -467,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()`]. @@ -677,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, } @@ -688,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 } @@ -698,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 } @@ -710,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 } @@ -721,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 } @@ -732,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 { @@ -761,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 }, } @@ -778,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() } @@ -795,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..1948b201509 --- /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 rand::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..5070e90ad99 100644 --- a/core/tests/connection_limits.rs +++ b/core/tests/connection_limits.rs @@ -23,7 +23,7 @@ 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, }; @@ -39,18 +39,20 @@ fn max_outgoing() { 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: _ } => { @@ -96,7 +98,7 @@ fn max_established_incoming() { 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 listen_addr = multiaddr![Memory(0u64)]; let _ = network1.listen_on(listen_addr.clone()).unwrap(); let (addr_sender, addr_receiver) = futures::channel::oneshot::channel(); let mut addr_sender = Some(addr_sender); @@ -111,8 +113,8 @@ fn max_established_incoming() { network1.accept(connection, TestHandler()).unwrap(); } NetworkEvent::ConnectionEstablished { .. } => {} - NetworkEvent::ConnectionClosed { - error: Some(ConnectionError::ConnectionLimit(err)), + NetworkEvent::IncomingConnectionError { + error: PendingConnectionError::ConnectionLimit(err), .. } => { assert_eq!(err.limit, limit); 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/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/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/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/identify/src/identify.rs b/protocols/identify/src/identify.rs index cc157216b30..8a5bfc7613e 100644 --- a/protocols/identify/src/identify.rs +++ b/protocols/identify/src/identify.rs @@ -220,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(), @@ -230,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( @@ -244,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); + } + } } } @@ -421,19 +447,6 @@ impl NetworkBehaviour for Identify { .map(|addr| Vec::from_iter(addr)) .unwrap_or_default() } - - fn inject_addr_reach_failure( - &mut self, - peer_id: Option<&PeerId>, - addr: &Multiaddr, - _: &dyn std::error::Error, - ) { - if let Some(peer) = peer_id { - if let Some(entry) = self.discovered_peers.get_mut(peer) { - entry.remove(addr); - } - } - } } /// Event emitted by the `Identify` behaviour. diff --git a/protocols/kad/Cargo.toml b/protocols/kad/Cargo.toml index 5a9440cf234..12a84ca980e 100644 --- a/protocols/kad/Cargo.toml +++ b/protocols/kad/Cargo.toml @@ -29,6 +29,7 @@ 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" } diff --git a/protocols/kad/src/behaviour.rs b/protocols/kad/src/behaviour.rs index 7721d5a9a83..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( 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/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/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/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/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/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 8bc85e4bb6d..51264235026 100644 --- a/swarm/CHANGELOG.md +++ b/swarm/CHANGELOG.md @@ -45,11 +45,29 @@ - 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/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 2a2dca6675d..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), } @@ -364,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) } @@ -553,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(); @@ -565,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); @@ -582,6 +577,7 @@ where peer_id, num_established, endpoint, + concurrent_dial_errors, }); } } @@ -625,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, @@ -711,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, }); } } @@ -827,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), ); } } @@ -962,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< @@ -1138,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); @@ -1201,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 @@ -1219,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 { @@ -1227,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!( @@ -1251,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), } } } @@ -1259,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 { From a905665b8bc11711ee285afd61629abf6a7f6192 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 15 Oct 2021 11:15:05 +0200 Subject: [PATCH 16/21] *: Prepare v0.40.0-rc.1 release (#2290) --- CHANGELOG.md | 2 +- Cargo.toml | 52 ++++++++++++------------- core/CHANGELOG.md | 2 +- core/Cargo.toml | 2 +- misc/metrics/CHANGELOG.md | 2 +- misc/metrics/Cargo.toml | 12 +++--- misc/multistream-select/CHANGELOG.md | 2 +- muxers/mplex/CHANGELOG.md | 2 +- muxers/mplex/Cargo.toml | 4 +- muxers/yamux/CHANGELOG.md | 2 +- muxers/yamux/Cargo.toml | 4 +- protocols/floodsub/CHANGELOG.md | 2 +- protocols/floodsub/Cargo.toml | 6 +-- protocols/gossipsub/CHANGELOG.md | 2 +- protocols/gossipsub/Cargo.toml | 6 +-- protocols/identify/CHANGELOG.md | 2 +- protocols/identify/Cargo.toml | 6 +-- protocols/kad/CHANGELOG.md | 2 +- protocols/kad/Cargo.toml | 6 +-- protocols/mdns/CHANGELOG.md | 2 +- protocols/mdns/Cargo.toml | 6 +-- protocols/ping/CHANGELOG.md | 2 +- protocols/ping/Cargo.toml | 6 +-- protocols/relay/CHANGELOG.md | 2 +- protocols/relay/Cargo.toml | 6 +-- protocols/rendezvous/CHANGELOG.md | 2 +- protocols/rendezvous/Cargo.toml | 6 +-- protocols/request-response/CHANGELOG.md | 2 +- protocols/request-response/Cargo.toml | 6 +-- swarm-derive/CHANGELOG.md | 2 +- swarm-derive/Cargo.toml | 2 +- swarm/CHANGELOG.md | 2 +- swarm/Cargo.toml | 4 +- transports/deflate/CHANGELOG.md | 2 +- transports/deflate/Cargo.toml | 4 +- transports/dns/CHANGELOG.md | 2 +- transports/dns/Cargo.toml | 4 +- transports/noise/CHANGELOG.md | 2 +- transports/noise/Cargo.toml | 4 +- transports/plaintext/CHANGELOG.md | 2 +- transports/plaintext/Cargo.toml | 4 +- transports/pnet/CHANGELOG.md | 2 +- transports/pnet/Cargo.toml | 2 +- transports/tcp/CHANGELOG.md | 2 +- transports/tcp/Cargo.toml | 4 +- transports/uds/CHANGELOG.md | 2 +- transports/uds/Cargo.toml | 4 +- transports/wasm-ext/CHANGELOG.md | 2 +- transports/wasm-ext/Cargo.toml | 4 +- transports/websocket/CHANGELOG.md | 2 +- transports/websocket/Cargo.toml | 4 +- 51 files changed, 110 insertions(+), 110 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c28e4afe1e8..4a64d537890 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,7 +42,7 @@ # `libp2p` facade crate -## Version 0.40.0 [unreleased] +## Version 0.40.0-rc.1 [2021-10-15] - Update individual crates. - `libp2p-core` diff --git a/Cargo.toml b/Cargo.toml index 05b5ccc8860..93d025d1ef7 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.1-rc.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -68,37 +68,37 @@ atomic = "0.5.0" bytes = "1" futures = "0.3.1" lazy_static = "1.2" -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.1", 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/core/CHANGELOG.md b/core/CHANGELOG.md index 4d9f4bda667..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]). diff --git a/core/Cargo.toml b/core/Cargo.toml index ed5770310de..98b5077c989 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" 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/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/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/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 a08fcc49bcf..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,8 +13,8 @@ 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.9" rand = "0.7" diff --git a/protocols/gossipsub/CHANGELOG.md b/protocols/gossipsub/CHANGELOG.md index ac66461ceed..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) diff --git a/protocols/gossipsub/Cargo.toml b/protocols/gossipsub/Cargo.toml index 7f91804a9aa..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" diff --git a/protocols/identify/CHANGELOG.md b/protocols/identify/CHANGELOG.md index 1d7378d0771..9611597c067 100644 --- a/protocols/identify/CHANGELOG.md +++ b/protocols/identify/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/identify/Cargo.toml b/protocols/identify/Cargo.toml index b6bd5e4ee92..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,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" lru = "0.6" prost = "0.9" diff --git a/protocols/kad/CHANGELOG.md b/protocols/kad/CHANGELOG.md index d1557c3401b..d746459a297 100644 --- a/protocols/kad/CHANGELOG.md +++ b/protocols/kad/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/kad/Cargo.toml b/protocols/kad/Cargo.toml index 12a84ca980e..f67c615167d 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.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -17,8 +17,8 @@ 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" } +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" 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 e94519182b3..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,8 +14,8 @@ 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.9" 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 9f9d084f780..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,8 +11,8 @@ 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" } +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" 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 db6d2162490..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,8 +13,8 @@ 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.7" rand = "0.7" 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/CHANGELOG.md b/swarm/CHANGELOG.md index 51264235026..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) 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/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 680902427d2..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,7 +12,7 @@ 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.9" rand = "0.8.3" 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 f09c60b607c..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,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.8" prost = "0.9" unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] } 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 d0663746d08..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,7 +13,7 @@ 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" From 2d13f99d303e1347b921613fd4da0a2327575d6a Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 15 Oct 2021 11:55:44 +0200 Subject: [PATCH 17/21] Cargo.toml: Fix version typo (#2292) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 93d025d1ef7..5152e0128ee 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-rc.1-rc.1" +version = "0.40.0-rc.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" From a430e6bffc9d1d25c3f6dbace1b8f73e8bcd5225 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 15 Oct 2021 15:30:16 +0200 Subject: [PATCH 18/21] protocols/kad: Export KademliaBucketInserts (#2294) --- CHANGELOG.md | 6 ++++++ Cargo.toml | 4 ++-- protocols/kad/CHANGELOG.md | 6 ++++++ protocols/kad/Cargo.toml | 2 +- protocols/kad/src/lib.rs | 3 ++- 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a64d537890..db0ca108d3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,12 @@ # `libp2p` facade crate + +## 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. diff --git a/Cargo.toml b/Cargo.toml index 5152e0128ee..4ab8a15dc8f 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-rc.1" +version = "0.40.0-rc.2" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -72,7 +72,7 @@ libp2p-core = { version = "0.30.0-rc.1", path = "core", default-features = fals 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.1", path = "protocols/kad", 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 } diff --git a/protocols/kad/CHANGELOG.md b/protocols/kad/CHANGELOG.md index d746459a297..b4ebf2f68b9 100644 --- a/protocols/kad/CHANGELOG.md +++ b/protocols/kad/CHANGELOG.md @@ -1,3 +1,9 @@ +# 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. diff --git a/protocols/kad/Cargo.toml b/protocols/kad/Cargo.toml index f67c615167d..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-rc.1" +version = "0.32.0-rc.2" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" 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; From ca1b7cf043b4264c69b19fe75de488330a7a1f2f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Oct 2021 18:12:30 +0200 Subject: [PATCH 19/21] build(deps): Bump actions/checkout from 2.3.4 to 2.3.5 (#2300) Bumps [actions/checkout](https://github.com/actions/checkout) from 2.3.4 to 2.3.5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2.3.4...v2.3.5) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 05391381678..b7b26694ac5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,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 with: @@ -52,7 +52,7 @@ jobs: with: access_token: ${{ github.token }} - - uses: actions/checkout@v2.3.4 + - uses: actions/checkout@v2.3.5 - name: Install Rust ${{ matrix.toolchain }} uses: actions-rs/toolchain@v1.0.7 @@ -90,7 +90,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 @@ -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: From cd2588e18263778ee3568b5ccd70c71dc9868337 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Wed, 20 Oct 2021 20:22:53 +0200 Subject: [PATCH 20/21] core/tests/connection_limit: Fix flake in max_established_incoming (#2295) The test `max_established_incoming` starts two networks with a configured connection limit, spawns up to `limit + 1` connections and expects the last connection to be closed due to being over the limit. The previous test implementation depended on both networks to handle each connection in sequence, which is not always the case. E.g. while network 2 might expect the last connection to close, network 1 might finish upgrading the last connection before the second to last connection, thus expecting the second to last connection to close. This commit drives network 1 and 2 in sequence and ensures both networks are finished upgrading a connection before starting a new connection. In addition it upgrade the test to use `quickcheck`. --- core/tests/connection_limits.rs | 165 +++++++++++++++++++------------- 1 file changed, 99 insertions(+), 66 deletions(-) diff --git a/core/tests/connection_limits.rs b/core/tests/connection_limits.rs index 5070e90ad99..74388d83f86 100644 --- a/core/tests/connection_limits.rs +++ b/core/tests/connection_limits.rs @@ -27,6 +27,7 @@ use libp2p_core::{ network::{ConnectionLimits, DialError, NetworkConfig, NetworkEvent}, PeerId, }; +use quickcheck::*; use rand::Rng; use std::task::Poll; use util::{test_network, TestHandler}; @@ -88,89 +89,121 @@ fn max_outgoing() { #[test] fn max_established_incoming() { - let limit = rand::thread_rng().gen_range(1, 10); + #[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![Memory(0u64)]; - 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::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); - 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(_)); } From 97509519c522d5c0e92d59a7f8092e92a10dc7a0 Mon Sep 17 00:00:00 2001 From: Qinxuan Chen Date: Fri, 22 Oct 2021 18:09:02 +0800 Subject: [PATCH 21/21] core/: Update libsecp256k1 from 0.6 to 0.7 (#2306) Signed-off-by: koushiro --- core/Cargo.toml | 5 +++-- core/src/identity/rsa.rs | 2 +- core/tests/concurrent_dialing.rs | 2 +- core/tests/connection_limits.rs | 7 +++++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 98b5077c989..18a0a90b17e 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -18,7 +18,7 @@ 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"] } @@ -26,7 +26,7 @@ multistream-select = { version = "0.10", path = "../misc/multistream-select" } parking_lot = "0.11.0" pin-project = "1.0.0" prost = "0.9" -rand = "0.7" +rand = "0.8" rw-stream-sink = "0.2.0" sha2 = "0.9.1" smallvec = "1.6.1" @@ -47,6 +47,7 @@ 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] 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/tests/concurrent_dialing.rs b/core/tests/concurrent_dialing.rs index 1948b201509..08b1b5782e0 100644 --- a/core/tests/concurrent_dialing.rs +++ b/core/tests/concurrent_dialing.rs @@ -29,7 +29,7 @@ use libp2p_core::{ ConnectedPoint, }; use quickcheck::*; -use rand::Rng; +use rand07::Rng; use std::num::NonZeroU8; use std::task::Poll; use util::{test_network, TestHandler}; diff --git a/core/tests/connection_limits.rs b/core/tests/connection_limits.rs index 74388d83f86..bee42b53055 100644 --- a/core/tests/connection_limits.rs +++ b/core/tests/connection_limits.rs @@ -28,13 +28,14 @@ use libp2p_core::{ PeerId, }; use quickcheck::*; -use rand::Rng; 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); @@ -89,6 +90,8 @@ fn max_outgoing() { #[test] fn max_established_incoming() { + use rand07::Rng; + #[derive(Debug, Clone)] struct Limit(u32);