diff --git a/protocols/kad/CHANGELOG.md b/protocols/kad/CHANGELOG.md index b31797b196d..f88484bafa1 100644 --- a/protocols/kad/CHANGELOG.md +++ b/protocols/kad/CHANGELOG.md @@ -23,6 +23,10 @@ See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). - Correctly handle the `NoKnownPeers` error on automatic bootstrap. See [PR 5349](https://github.com/libp2p/rust-libp2p/pull/5349). +- Improve automatic bootstrap triggering conditions: + trigger when the routing table is updated and we have less that `K_VALUE` peers in it, + trigger when a new listen address is discovered and we have no connected peers. + See [PR 5474](https://github.com/libp2p/rust-libp2p/pull/5474). ## 0.45.3 diff --git a/protocols/kad/src/behaviour.rs b/protocols/kad/src/behaviour.rs index 09f4e93fe4e..069eec1a5b4 100644 --- a/protocols/kad/src/behaviour.rs +++ b/protocols/kad/src/behaviour.rs @@ -23,7 +23,6 @@ mod test; use crate::addresses::Addresses; -use crate::bootstrap; use crate::handler::{Handler, HandlerEvent, HandlerIn, RequestId}; use crate::kbucket::{self, Distance, KBucketsTable, NodeStatus}; use crate::protocol::{ConnectionType, KadPeer, ProtocolConfig}; @@ -33,6 +32,7 @@ use crate::record::{ store::{self, RecordStore}, ProviderRecord, Record, }; +use crate::{bootstrap, K_VALUE}; use crate::{jobs::*, protocol}; use fnv::FnvHashSet; use libp2p_core::{ConnectedPoint, Endpoint, Multiaddr}; @@ -604,7 +604,8 @@ where }; match entry.insert(addresses.clone(), status) { kbucket::InsertResult::Inserted => { - self.bootstrap_status.on_new_peer_in_routing_table(); + self.bootstrap_on_low_peers(); + self.queued_events.push_back(ToSwarm::GenerateEvent( Event::RoutingUpdated { peer: *peer, @@ -1324,7 +1325,8 @@ where let addresses = Addresses::new(a); match entry.insert(addresses.clone(), new_status) { kbucket::InsertResult::Inserted => { - self.bootstrap_status.on_new_peer_in_routing_table(); + self.bootstrap_on_low_peers(); + let event = Event::RoutingUpdated { peer, is_new_peer: true, @@ -1375,6 +1377,20 @@ where } } + /// A new peer has been inserted in the routing table but we check if the routing + /// table is currently small (less that `K_VALUE` peers are present) and only + /// trigger a bootstrap in that case + fn bootstrap_on_low_peers(&mut self) { + if self + .kbuckets() + .map(|kbucket| kbucket.num_entries()) + .sum::() + < K_VALUE.get() + { + self.bootstrap_status.trigger(); + } + } + /// Handles a finished (i.e. successful) query. fn query_finished(&mut self, q: Query) -> Option { let query_id = q.id(); @@ -2613,6 +2629,12 @@ where } FromSwarm::DialFailure(dial_failure) => self.on_dial_failure(dial_failure), FromSwarm::AddressChange(address_change) => self.on_address_change(address_change), + FromSwarm::NewListenAddr(_) if self.connected_peers.is_empty() => { + // A new listen addr was just discovered and we have no connected peers, + // it can mean that our network interfaces were not up but they are now + // so it might be a good idea to trigger a bootstrap. + self.bootstrap_status.trigger(); + } _ => {} } } diff --git a/protocols/kad/src/bootstrap.rs b/protocols/kad/src/bootstrap.rs index fd9e3d41be6..40acdfd88ee 100644 --- a/protocols/kad/src/bootstrap.rs +++ b/protocols/kad/src/bootstrap.rs @@ -44,7 +44,8 @@ impl Status { } } - pub(crate) fn on_new_peer_in_routing_table(&mut self) { + /// Trigger a bootstrap now or after the configured `automatic_throttle` if configured. + pub(crate) fn trigger(&mut self) { // Registering `self.throttle_timer` means scheduling a bootstrap. // A bootstrap will be triggered when `self.throttle_timer` finishes. // A `throttle_timer` is useful to not trigger a batch of bootstraps when a @@ -201,7 +202,7 @@ mod tests { "bootstrap to not be triggered immediately because periodic bootstrap is in ~1s" ); - status.on_new_peer_in_routing_table(); // Connected to a new peer though! + status.trigger(); // Connected to a new peer though! assert!( status.next().now_or_never().is_some(), "bootstrap to be triggered immediately because we connected to a new peer" @@ -226,7 +227,7 @@ mod tests { "bootstrap to not be triggered immediately because periodic bootstrap is in ~1s" ); - status.on_new_peer_in_routing_table(); // Connected to a new peer though! + status.trigger(); // Connected to a new peer though! assert!( status.next().now_or_never().is_none(), "bootstrap to not be triggered immediately because throttle is 5ms" @@ -247,7 +248,7 @@ mod tests { // User manually triggered a bootstrap do_bootstrap(&mut status); - status.on_new_peer_in_routing_table(); // Connected to a new peer though! + status.trigger(); // Connected to a new peer though! assert!( status.next().now_or_never().is_some(), @@ -260,7 +261,7 @@ mod tests { ) { let mut status = Status::new(Some(MS_100), Some(MS_5)); - status.on_new_peer_in_routing_table(); + status.trigger(); let start = Instant::now(); await_and_do_bootstrap(&mut status).await; @@ -280,7 +281,7 @@ mod tests { ) { let mut status = Status::new(None, Some(Duration::ZERO)); - status.on_new_peer_in_routing_table(); + status.trigger(); status.next().await; } @@ -304,10 +305,10 @@ mod tests { ) { let mut status = Status::new(None, Some(MS_100)); - status.on_new_peer_in_routing_table(); + status.trigger(); for _ in 0..10 { Delay::new(MS_100 / 2).await; - status.on_new_peer_in_routing_table(); // should reset throttle_timer + status.trigger(); // should reset throttle_timer } assert!( status.next().now_or_never().is_none(),