Skip to content

kad: Implement client-server mode for Kademlia#611

Open
lexnv wants to merge 9 commits into
masterfrom
lexnv/kad-client-server
Open

kad: Implement client-server mode for Kademlia#611
lexnv wants to merge 9 commits into
masterfrom
lexnv/kad-client-server

Conversation

@lexnv

@lexnv lexnv commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

This PR implements the client-server mode of operation for Kademlia.

Full nodes and substrate-nodes utilize the server mode by default:

  • nodes respond to queries
  • They are added to the routing table in automatic mode

Light clients or DHT crawlers are encouraged to use the client mode:

  • To preserve resources, they are not responding to queries
  • Client nodes are not added to the routing table, therefore not polluting the DHT (or even worse, intertwining multiple DHTs)

To prepare for supporting the dynamic addition and removal of multiple DHTs at runtime, this PR introduces dynamic protocol configuration for the identify protocol.
By default, Kademlia implementations running in client mode are not advertised via the identify protocol.

cc @dmitry-markin @skunert

lexnv added 8 commits June 8, 2026 09:19
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
@lexnv lexnv self-assigned this Jun 8, 2026
@lexnv lexnv added the enhancement New feature or request label Jun 8, 2026
@skunert skunert self-requested a review June 9, 2026 08:20
@skunert

skunert commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

In general looking good. From looking into this I was wondering about the impact on the routing table. So basically after this change the routing tables will be filles more conservatively which peers which have answered to some of our queries. Peers we discovered on the way but did not query will not be added, even though they might be in server mode (we just don't know). It sounds good to me, I assume we expect no impact? Or even positive impact, because these peers have already answered us.

@gab8i gab8i left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! So few lines of code for quite a feature!

@dmitry-markin dmitry-markin self-requested a review June 10, 2026 08:51

@dmitry-markin dmitry-markin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic seems good with some minor nits. I am going to analyze some general client-server operation consequences before the final approval, but should be good to go.


/// Kademlia operating mode.
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
pub enum KademliaMode {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: move to config, not handle?

Comment thread src/lib.rs
// if identify was enabled, give it the advertised protocols and listen addresses and
// start it
if let Some((service, identify_config)) = identify_info.take() {
*identify_config.protocols.write() = transport_manager

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is currently no need to share RwLock, because the list of identify protocols only gets updated on the litep2p startup.

In general, sharing access to a private struct should be avoided and replaced by higher-level API for either adding/removing identify protocols, or even better enabling/disabling the advertisement of individual installed protocols.

Comment on lines +253 to +256
pub fn with_mode(mut self, mode: KademliaMode) -> Self {
self.mode = mode;
self
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also make it possible to switch from client to server mode at runtime — e.g., when we discover a reachable external address. Can be a follow-up to make the PR smaller.

Comment thread src/lib.rs
// start kademlia protocol event loops
//
// protocol names of client-mode instances are not advertised via identify
let mut unadvertised_protocols = Vec::new();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping a separate list of ignored protocols seems to be error-prone. Can we update a transport_manager.register_protocol() to accept a mandatory parameter of whether we advertise the protocol instead?


if let KademliaMode::Client = self.mode {
tracing::trace!(target: LOG_TARGET, ?peer, "ignoring inbound substream in client mode");
let _ = substream.close().await;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to make sure this won't cause the nodes without client-server understanding to busy-loop on trying to open a substream. Reading and silently discarding all the input might be better if it is the case.

// mode is
let _ = self
.event_tx
.send(KademliaEvent::RoutingTableUpdate {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not anymore the RoutingTableUpdate event, because we don't update the routing table. What about moving to insert_proven_server to not break the API by renaming the event?

);

self.engine.register_response(
let _ = self.engine.register_response(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also learn the node is reachable through PUT_VALUE/ADD_PROVIDER/etc. success, so makes sense updating the routing table here as well.

ADD_PROVIDER is trickier, because the spec doesn't define the response message, so we should track the substream opening / message sending success instead (already done for ADD_PROVIDER queries), but at least worth adding a TODO comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants