Skip to content

Commit bd45dfc

Browse files
feat(kad): make the bound of num closest peers returned configurable
1 parent 4682e73 commit bd45dfc

File tree

4 files changed

+89
-9
lines changed

4 files changed

+89
-9
lines changed

protocols/kad/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## Unreleased
2+
3+
- Make the number of closest peers returned in response to FIND_NODE requests configurable via `Config::set_num_closest_peers()`.
4+
The default remains `K_VALUE` (20) for backward compatibility.
5+
See [PR XXXX](https://github.com/libp2p/rust-libp2p/pull/XXXX)
6+
17
## 0.49.0
28

39
- Remove no longer constructed GetRecordError::QuorumFailed.

protocols/kad/src/behaviour.rs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,14 @@ impl Config {
437437
self
438438
}
439439

440+
/// Sets the number of closest peers to return in response to a request.
441+
///
442+
/// The default is `K_VALUE`.
443+
pub fn set_num_closest_peers(&mut self, num_closest_peers: NonZeroUsize) -> &mut Self {
444+
self.query_config.num_closest_peers = num_closest_peers;
445+
self
446+
}
447+
440448
/// Sets the time to wait before calling [`Behaviour::bootstrap`] after a new peer is inserted
441449
/// in the routing table. This prevent cascading bootstrap requests when multiple peers are
442450
/// inserted into the routing table "at the same time". This also allows to wait a little
@@ -729,24 +737,27 @@ where
729737
where
730738
K: Into<kbucket::Key<K>> + Into<Vec<u8>> + Clone,
731739
{
732-
self.get_closest_peers_inner(key, K_VALUE)
740+
self.get_closest_peers_inner(key, self.queries.config().num_closest_peers)
733741
}
734742

735743
/// Initiates an iterative query for the closest peers to the given key.
736744
/// The expected responding peers is specified by `num_results`
737-
/// Note that the result is capped after exceeds K_VALUE
745+
/// Note that the result is capped to the configured `num_closest_peers` (defaults to
746+
/// `K_VALUE`).
738747
///
739748
/// The result of the query is delivered in a
740749
/// [`Event::OutboundQueryProgressed`] with `result` [`QueryResult::GetClosestPeers`].
741750
pub fn get_n_closest_peers<K>(&mut self, key: K, num_results: NonZeroUsize) -> QueryId
742751
where
743752
K: Into<kbucket::Key<K>> + Into<Vec<u8>> + Clone,
744753
{
745-
// The inner code never expect higher than K_VALUE results to be returned.
746-
// And removing such cap will be tricky,
754+
// The inner code never expect higher than the configured num_closest_peers results to be
755+
// returned. And removing such cap will be tricky,
747756
// since it would involve forging a new key and additional requests.
748-
// Hence bound to K_VALUE here to set clear expectation and prevent unexpected behaviour.
749-
let capped_num_results = std::cmp::min(num_results, K_VALUE);
757+
// Hence bound to num_closest_peers here to set clear expectation and prevent unexpected
758+
// behaviour.
759+
let capped_num_results =
760+
std::cmp::min(num_results, self.queries.config().num_closest_peers);
750761
self.get_closest_peers_inner(key, capped_num_results)
751762
}
752763

@@ -784,10 +795,11 @@ where
784795
key: &'a kbucket::Key<K>,
785796
source: &'a PeerId,
786797
) -> impl Iterator<Item = KadPeer> + 'a {
798+
let num_closest_peers = self.queries.config().num_closest_peers;
787799
self.kbuckets
788800
.closest(key)
789801
.filter(move |e| e.node.key.preimage() != source)
790-
.take(K_VALUE.get())
802+
.take(num_closest_peers.get())
791803
.map(KadPeer::from)
792804
}
793805

protocols/kad/src/behaviour/test.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ fn unresponsive_not_returned_indirect() {
434434
}
435435

436436
// Test the result of get_closest_peers with different num_results
437-
// Note that the result is capped after exceeds K_VALUE
437+
// Note that the result is capped to the configured num_closest_peers (defaults to K_VALUE)
438438
#[test]
439439
fn get_closest_with_different_num_results() {
440440
let k_value = K_VALUE.get();
@@ -1680,3 +1680,59 @@ fn get_closest_peers_should_return_up_to_k_peers() {
16801680
}))
16811681
}
16821682
}
1683+
1684+
// Test that nodes respond with the configured num_closest_peers amount.
1685+
#[test]
1686+
fn get_closest_peers_should_return_configured_num_closest_peers() {
1687+
let k_value = K_VALUE.get();
1688+
// Test with values less than, equal to, and greater than K_VALUE
1689+
for num_closest_peers in [5, k_value, k_value * 2] {
1690+
// Should be enough nodes for every node to have >= num_closest_peers nodes in their RT.
1691+
let num_of_nodes = 3 * num_closest_peers;
1692+
1693+
let mut cfg = Config::new(PROTOCOL_NAME);
1694+
cfg.set_num_closest_peers(NonZeroUsize::new(num_closest_peers).unwrap());
1695+
1696+
let swarms = build_connected_nodes_with_config(num_of_nodes, k_value, cfg);
1697+
let mut swarms = swarms
1698+
.into_iter()
1699+
.map(|(_addr, swarm)| swarm)
1700+
.collect::<Vec<_>>();
1701+
1702+
// Ask first node to search for a random peer.
1703+
let search_target = PeerId::random();
1704+
swarms[0].behaviour_mut().get_closest_peers(search_target);
1705+
1706+
let rt = Runtime::new().unwrap();
1707+
rt.block_on(poll_fn(move |ctx| {
1708+
for swarm in &mut swarms {
1709+
loop {
1710+
match swarm.poll_next_unpin(ctx) {
1711+
Poll::Ready(Some(SwarmEvent::Behaviour(
1712+
Event::OutboundQueryProgressed {
1713+
result: QueryResult::GetClosestPeers(Ok(ok)),
1714+
..
1715+
},
1716+
))) => {
1717+
assert_eq!(&ok.key[..], search_target.to_bytes().as_slice());
1718+
// Verify that we get the configured num_closest_peers amount.
1719+
assert_eq!(
1720+
ok.peers.len(),
1721+
num_closest_peers,
1722+
"Expected {} peers but got {}",
1723+
num_closest_peers,
1724+
ok.peers.len()
1725+
);
1726+
return Poll::Ready(());
1727+
}
1728+
// Ignore any other event.
1729+
Poll::Ready(Some(_)) => (),
1730+
e @ Poll::Ready(_) => panic!("Unexpected return value: {e:?}"),
1731+
Poll::Pending => break,
1732+
}
1733+
}
1734+
}
1735+
Poll::Pending
1736+
}))
1737+
}
1738+
}

protocols/kad/src/query.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,15 +265,21 @@ pub(crate) struct QueryConfig {
265265
///
266266
/// See [`crate::behaviour::Config::disjoint_query_paths`] for details.
267267
pub(crate) disjoint_query_paths: bool,
268+
/// The number of closest peers to return in response to a request.
269+
///
270+
/// See [`crate::behaviour::Config::set_num_closest_peers`] for details.
271+
pub(crate) num_closest_peers: NonZeroUsize,
268272
}
269273

270274
impl Default for QueryConfig {
271275
fn default() -> Self {
276+
let k_value = NonZeroUsize::new(K_VALUE.get()).expect("K_VALUE > 0");
272277
QueryConfig {
273278
timeout: Duration::from_secs(60),
274-
replication_factor: NonZeroUsize::new(K_VALUE.get()).expect("K_VALUE > 0"),
279+
replication_factor: k_value,
275280
parallelism: ALPHA_VALUE,
276281
disjoint_query_paths: false,
282+
num_closest_peers: k_value,
277283
}
278284
}
279285
}

0 commit comments

Comments
 (0)