Skip to content

feat(kad): re-seed a routing table that fell below a minimum size - #2937

Open
gmelodie wants to merge 4 commits into
masterfrom
feat/kad/fixlowpeers-loop
Open

feat(kad): re-seed a routing table that fell below a minimum size#2937
gmelodie wants to merge 4 commits into
masterfrom
feat/kad/fixlowpeers-loop

Conversation

@gmelodie

@gmelodie gmelodie commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Bootstrap runs one time, in KadDHT.start. After that only the 10-minute bucket refresh (maintainBuckets) runs, and it can only refresh peers that the table already holds. A node that loses its peers to an eviction storm, or that comes back from a network partition, has no path back into the DHT: an empty or near-empty table gives the refresh nothing to walk from.

This PR adds fixLowPeers, the equivalent of the go-libp2p runFixLowPeersLoop. A background loop compares the routing table against config.minRoutingTableSize every config.fixLowPeersInterval. When the table is short, the loop re-seeds it from two sources and then forces a refresh:

  • the currently connected peers, through the normal admission probe (admitPeers), because a live connection does not prove that the peer speaks the DHT protocol.
  • the configured bootstrap nodes, through the trusted path (updatePeers), because the operator already trusted them at construction time, and because the admission probes only land after the current pass.

The forced refresh (refreshTable(forceRefresh = true)) then walks every bucket, and not only the stale ones, so the re-seeded peers give closer peers immediately.

To make the bootstrap nodes available after construction, KadDHT now keeps them in a bootstrapNodes field. initKadBase takes them and does the initial updatePeers, so KadDHT.new and ServiceDiscovery.new share one seeding path instead of two copies.

Affected Areas

  • Peer Management / Discovery
  • Protocol Logic

Kademlia only. libp2p/protocols/kademlia.nim gets connectedPeerInfos, fixLowPeers and the maintainMinPeers loop, started in start and cancelled in stop. routing_table.nim gets peerCount, which updateRoutingTableMetrics now reuses. find.nim gets toPeerInfos, so the tuple to PeerInfo conversion lives in one place. service_discovery.nim passes its bootstrap nodes through initKadBase.

Compatibility & Downstream Validation

No API break, so no downstream branch is needed.

  • Nimbus: N/A
  • Waku: N/A
  • Codex: N/A

Impact on Library Users

No API change. KadDHTConfig.new gets two new optional parameters, fixLowPeersInterval (default 1 minute) and minRoutingTableSize (default 10 peers). Existing call sites keep compiling and get the loop with the defaults.

Behaviour change: a node with fewer than 10 peers in its routing table now re-dials its bootstrap nodes and its connected peers one time per minute until it recovers. Set minRoutingTableSize = 0 to turn the re-seed off.

New metric: kad_routing_table_reseeds, a counter of the re-seed attempts.

Risk Assessment

  • Backward compatibility: the defaults preserve the current behaviour for a healthy table, because the loop returns early while peerCount() >= minRoutingTableSize.
  • Network behaviour: a node that stays below the minimum, for example a fresh node on a small network, re-seeds every minute. The dial set is bounded by the number of configured bootstrap nodes plus the connected peers, and the forced refresh is the same work that the bucket refresh already does.
  • Trust: the bootstrap nodes take the unprobed path on every re-seed, and not only at construction. A dead or repointed seed can therefore re-enter the table until the liveness loop evicts it again. The peers are operator-configured, so this re-applies a trust that the operator already granted, but it is a real change from a one-time decision to a recurring one.
  • Attack surface: the connected peers come from switch.connectedPeers(), so an inbound connection can offer itself. It still has to answer a FIND_NODE probe and pass the per-IP and per-subnet caps in admissibleAddrs, and the probes run behind admissionSem.
  • Performance: the size check is a bucket walk, and it runs one time per interval.

Tests

tests/libp2p/kademlia/test_fix_low_peers.nim covers five cases: a healthy table triggers no lookup, a short table forces a refresh of every non-empty bucket, an evicted bootstrap node returns to the table, a connected peer is admitted into an empty table, and the loop itself re-seeds with no explicit call.

References

Closes #2859

@gmelodie
gmelodie force-pushed the feat/kad/fixlowpeers-loop branch from e311e51 to 5d888fa Compare August 14, 2026 12:54
@codecov-commenter

codecov-commenter commented Aug 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.21053% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.30%. Comparing base (0e87563) to head (336689f).

Files with missing lines Patch % Lines
libp2p/protocols/kademlia.nim 88.88% 4 Missing ⚠️
libp2p/protocols/service_discovery.nim 57.14% 3 Missing ⚠️
libp2p/protocols/kademlia/types.nim 60.00% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2937      +/-   ##
==========================================
+ Coverage   82.25%   82.30%   +0.04%     
==========================================
  Files         178      178              
  Lines       32466    32511      +45     
  Branches       12       11       -1     
==========================================
+ Hits        26704    26757      +53     
+ Misses       5762     5754       -8     
Files with missing lines Coverage Δ
libp2p/protocols/kademlia/find.nim 78.45% <100.00%> (+0.23%) ⬆️
libp2p/protocols/kademlia/kademlia_metrics.nim 100.00% <100.00%> (ø)
libp2p/protocols/kademlia/routing_table.nim 91.78% <100.00%> (+0.11%) ⬆️
libp2p/protocols/kademlia/types.nim 83.78% <60.00%> (+0.16%) ⬆️
libp2p/protocols/service_discovery.nim 86.08% <57.14%> (-1.82%) ⬇️
libp2p/protocols/kademlia.nim 78.43% <88.88%> (+4.41%) ⬆️

... and 6 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@gmelodie
gmelodie marked this pull request as ready for review August 14, 2026 13:32
@gmelodie
gmelodie requested review from a team, richard-ramos and vladopajic August 14, 2026 13:32

await kad.refreshTable(kad.rtable, forceRefresh = true)

proc maintainMinPeers(kad: KadDHT) {.async: (raises: [CancelledError]).} =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

it's unnecessary to raise here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You mean the {.async: (raises: [CancelledError]).} part? Or the doAssert below it? I removed the doAssert

Copilot AI 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.

Pull request overview

This PR improves Kademlia DHT self-healing by introducing a periodic “fix low peers” mechanism that re-seeds and refreshes the routing table when it drops below a configured minimum, using connected peers and configured bootstrap nodes.

Changes:

  • Added fixLowPeers and a background maintainMinPeers loop, started/stopped with the KadDHT lifecycle.
  • Extended KadDHTConfig with fixLowPeersInterval and minRoutingTableSize, plus a new kad_routing_table_reseeds metric.
  • Refactored bootstrap seeding to flow through initKadBase, and centralized tuple→PeerInfo conversion via toPeerInfos.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/libp2p/kademlia/utils.nim Extends test config helper to include new fix-low-peers config parameters.
tests/libp2p/kademlia/test_fix_low_peers.nim Adds unit tests covering reseed/refresh behavior and the background loop.
libp2p/protocols/service_discovery.nim Routes bootstrap nodes through initKadBase for consistent seeding.
libp2p/protocols/kademlia/types.nim Adds new config defaults/fields and a new KadDHT loop field + stored bootstrap nodes.
libp2p/protocols/kademlia/routing_table.nim Introduces peerCount() and reuses it in routing table metrics.
libp2p/protocols/kademlia/kademlia_metrics.nim Adds a counter for routing table reseed attempts.
libp2p/protocols/kademlia/find.nim Adds toPeerInfos helper and updates updatePeers overload to use it.
libp2p/protocols/kademlia.nim Implements connectedPeerInfos, fixLowPeers, and the periodic reseed loop; wires loop into start/stop.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread libp2p/protocols/kademlia.nim
Comment thread libp2p/protocols/kademlia/routing_table.nim Outdated
@gmelodie
gmelodie enabled auto-merge August 14, 2026 16:02
@gmelodie
gmelodie disabled auto-merge August 14, 2026 16:02
@gmelodie
gmelodie enabled auto-merge August 14, 2026 16:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: new

Development

Successfully merging this pull request may close these issues.

kad: add a fixLowPeers loop to auto-heal a shrinking/partitioned routing table

5 participants