Summary
@helia/bitswap documents runOnLimitedConnections, but setting it to true does not make bitswap work over a limited (circuit-relay) connection. Two independent places drop the flag, and each one alone is enough to break it — patching either has no effect, patching both makes it work.
Versions: @helia/bitswap 4.0.11, helia 7.1.7, libp2p 3.3.8, @libp2p/circuit-relay-v2 4.2.11. Two browsers (Chromium), meeting through a circuit relay.
Where the flag is lost
1. The registrar topology never notifies about limited connections.
src/network.js, start() registers a topology that does not set notifyOnLimitedConnection:
const topology = {
onConnect: (peerId) => { this.safeDispatchEvent('peer:connected', { detail: peerId }) },
...
}
libp2p/src/registrar.js skips exactly that case:
if (connection.limits != null && topology.notifyOnLimitedConnection !== true) { /* skipped */ }
So bitswap never learns the peer exists and never asks it for anything.
2. sendMessage() dials without the flag.
src/network.js:
const stream = await this.libp2p.dialProtocol(peerId, BITSWAP_120, options)
options here is the queue job's options; this.runOnLimitedConnections is not merged in. handle() and findProviders()'s isDialable() both honour it — the call that actually asks a peer for a block is the one that does not.
Measured
Two browser nodes, no direct path possible (no DCUtR service and no /webrtc listen address), so the circuit is the only path they can ever have. The connection reports limits != null. One node adds a block; the other reads it by CID.
| patch applied |
runOnLimitedConnections |
result |
| none |
true |
times out |
| topology only |
true |
times out |
sendMessage only |
true |
times out |
| both |
true |
block arrives |
| both |
false (default) |
times out |
The last row matters: with both fixed, the default behaviour is unchanged. Nothing starts crossing circuits unless the option asks for it.
Worth noting separately — the bitswap protocol stream itself opens fine over the circuit. Dialling /ipfs/bitswap/1.2.0 by hand with runOnLimitedConnection: true succeeds while a read is still timing out, which is what pointed at these two sites rather than at libp2p refusing the protocol.
Suggested patch
const topology = {
+ notifyOnLimitedConnection: this.runOnLimitedConnections,
onConnect: (peerId) => {
- const stream = await this.libp2p.dialProtocol(peerId, BITSWAP_120, options);
+ const stream = await this.libp2p.dialProtocol(peerId, BITSWAP_120, {
+ ...options,
+ runOnLimitedConnection: this.runOnLimitedConnections
+ });
Why this matters for browsers
Two browsers behind carrier NAT cannot hole-punch reliably, and the circuit is often the only path they have. Today that means content cannot move between them at all through Helia, while everything else on the same connection — identify, ping, gossipsub, an application protocol declaring runOnLimitedConnection: true — works. The option exists to allow that trade; it just does not reach the code that would honour it.
Happy to open a PR with the two-line change and a test if that is useful.
Possibly related, though not the same thing: #253.
Summary
@helia/bitswapdocumentsrunOnLimitedConnections, but setting it totruedoes not make bitswap work over a limited (circuit-relay) connection. Two independent places drop the flag, and each one alone is enough to break it — patching either has no effect, patching both makes it work.Versions:
@helia/bitswap4.0.11,helia7.1.7,libp2p3.3.8,@libp2p/circuit-relay-v24.2.11. Two browsers (Chromium), meeting through a circuit relay.Where the flag is lost
1. The registrar topology never notifies about limited connections.
src/network.js,start()registers a topology that does not setnotifyOnLimitedConnection:libp2p/src/registrar.jsskips exactly that case:So bitswap never learns the peer exists and never asks it for anything.
2.
sendMessage()dials without the flag.src/network.js:optionshere is the queue job's options;this.runOnLimitedConnectionsis not merged in.handle()andfindProviders()'sisDialable()both honour it — the call that actually asks a peer for a block is the one that does not.Measured
Two browser nodes, no direct path possible (no DCUtR service and no
/webrtclisten address), so the circuit is the only path they can ever have. The connection reportslimits != null. One node adds a block; the other reads it by CID.runOnLimitedConnectionstruetruesendMessageonlytruetruefalse(default)The last row matters: with both fixed, the default behaviour is unchanged. Nothing starts crossing circuits unless the option asks for it.
Worth noting separately — the bitswap protocol stream itself opens fine over the circuit. Dialling
/ipfs/bitswap/1.2.0by hand withrunOnLimitedConnection: truesucceeds while a read is still timing out, which is what pointed at these two sites rather than at libp2p refusing the protocol.Suggested patch
const topology = { + notifyOnLimitedConnection: this.runOnLimitedConnections, onConnect: (peerId) => {Why this matters for browsers
Two browsers behind carrier NAT cannot hole-punch reliably, and the circuit is often the only path they have. Today that means content cannot move between them at all through Helia, while everything else on the same connection — identify, ping, gossipsub, an application protocol declaring
runOnLimitedConnection: true— works. The option exists to allow that trade; it just does not reach the code that would honour it.Happy to open a PR with the two-line change and a test if that is useful.
Possibly related, though not the same thing: #253.