fix(dht): make bittorrent-dht bootstrap reliably (1 → 50+ nodes) - #232
Open
bluejorts wants to merge 1 commit into
Open
fix(dht): make bittorrent-dht bootstrap reliably (1 → 50+ nodes)#232bluejorts wants to merge 1 commit into
bluejorts wants to merge 1 commit into
Conversation
The DHT routing table was stuck at a single node (itself) on cold start,
while libtorrent reached 67 nodes on the same network — so this was a
usage problem, not the network. Five compounding issues, all fixed:
- dht.listen() was never called, so the socket's 'listening' event never
fired and bittorrent-dht's internal bucket-refresh / re-bootstrap-when-
isolated maintenance loop never started. Now bind explicitly on a free
ephemeral port (node.port belongs to the UTP transport).
- The 5s query timeout was passed to k-rpc, which ignores it; the socket
used its 2s default and dropped slow-but-live routers. Moved to
krpcSocket({ timeout: 5000 }).
- Only a single shallow bootstrap lookup ran, then the DHT idled until the
15-minute reannounce. Added a cold-bootstrap loop that re-seeds routers
and re-runs lookup(ownId) every 10s until the table reaches 20 nodes.
- Raised k-rpc concurrency (16→32) and backgroundConcurrency (4→16) for
faster traversal; added backgroundConcurrency to the KRPCOptions type.
- Cache persistence only fired at >50 nodes (never reached), so restarts
were always cold. Now persists whenever the table reaches a new high of
>=8 nodes, seeding cacheSize from the loaded cache.
Bootstrap list: replaced the mostly-dead/mis-configured set (wrong
dht.libtorrent.org port :6881, defunct utorrent/bitcomet/aelitis routers,
and Hydrabase peer hostnames that don't speak the DHT) with the routers
empirically verified live: dht.transmissionbt.com:6881,
dht.libtorrent.org:25401 (correct port), the canonical bittorrent/utorrent
routers, and a transmissionbt IP fallback.
Verified: real app now reaches "Ready with 53 nodes" on startup.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VjgRYtdZZk6LuvTmsmpSh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Under Bun (the Docker image runtime), the DHT routing table never grows past a single node (itself), so Hydrabase peer discovery over the DHT is effectively non-functional. This reproduces on a bare host and in a bridge-networked container.
Root cause: usage, not the network
On the same machine and network, libtorrent (qBittorrent's engine) reaches 60–70 DHT nodes in ~20s, and a raw
bittorrent-dhtinstance behaves identically under Node and Bun — so this is not a Bun/UDP problem and not a network/NAT problem. It's how the DHT is driven. Five compounding issues:dht.listen()is never called.bittorrent-dht's internal bucket-refresh / re-bootstrap-when-isolated maintenance loop only starts on the socketlisteningevent, which requires an explicitlisten(). Without it, the library's own self-healing never runs and a cold start stalls after one shallow bootstrap lookup.krpc(), which ignores it; the socket used its 2s default and dropped slow-but-live routers.reannounce.concurrency/backgroundConcurrency(16/4).nodes > 50, which never happened, so restarts were always cold.The
bootstrapNodeslist was also mostly dead:dht.libtorrent.orgwas on the wrong port (:6881instead of:25401), several routers are defunct, and the list included Hydrabase peer hostnames that don't speak the BitTorrent DHT.Fix (
src/backend/networking/dht.ts,config.ts,krpc.d.ts)dht.listen()on a free ephemeral port (notnode.port— the UTP transport owns that) to start the maintenance loop.krpcSocket({ timeout: 5000 })so it actually applies.lookup(ownId)every 10s until the table reaches 20 nodes.concurrency16→32 andbackgroundConcurrency4→16 (and addbackgroundConcurrencyto theKRPCOptionstype).cacheSizefrom the loaded cache) so restarts are warm.find_node):dht.transmissionbt.com:6881,dht.libtorrent.org:25401(correct port), the canonical bittorrent/utorrent routers, and a transmissionbt IP fallback.Verification
bun run)No dependency changes; the fix is self-contained to the DHT wiring, config, and one type declaration.
🤖 Generated with Claude Code