Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion packages/bitswap/src/network.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { InvalidParametersError, NotStartedError, TimeoutError, TypedEventEmitter, UnsupportedProtocolError, setMaxListeners } from '@libp2p/interface'
import { InvalidParametersError, NotStartedError, TimeoutError, TypedEventEmitter, UnsupportedProtocolError, isPeerId, setMaxListeners } from '@libp2p/interface'
import { PeerQueue } from '@libp2p/utils'
import drain from 'it-drain'
import * as lp from 'it-length-prefixed'
Expand Down Expand Up @@ -406,6 +406,21 @@ export class Network extends TypedEventEmitter<NetworkEvents> {

options?.onProgress?.(new CustomProgressEvent<PeerId | Multiaddr | Multiaddr[]>('bitswap:dial', peer))

// Fast path: peer:identify is single-shot per peer, so if the peer was
// already identified (e.g. dialed by another subsystem) the raceEvent
// below would wait for an event that has already fired. When the peerStore
// already lists BITSWAP_120 for this peer we can skip the race entirely.
if (isPeerId(peer)) {
try {
const peerData = await this.libp2p.peerStore.get(peer)
if (peerData.protocols.includes(BITSWAP_120)) {
return await this.libp2p.dial(peer, options)
}
} catch {
// peer not in peerStore yet — fall through to the identify race
}
}

// dial and wait for identify - this is to avoid opening a protocol stream
// that we are not going to use but depends on the remote node running the
// identify protocol
Expand Down
67 changes: 67 additions & 0 deletions packages/bitswap/test/network.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,73 @@ describe('network', () => {
.with.property('name', 'NotStartedError')
})

it('should not wait for peer:identify when the peer is already known to speak bitswap', async () => {
// peer:identify is single-shot per peer. If the peer was already
// identified by another subsystem (e.g. pubsub mesh warmup) before
// bitswap calls connectTo, awaiting peer:identify hangs forever. When
// the peerStore already lists BITSWAP_120 for the peer the race is
// unnecessary and connectTo must resolve from dial() alone.
const peerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519'))
const connection = stubInterface<Connection>()

components.libp2p.peerStore = stubInterface({
get: Sinon.stub().withArgs(peerId).resolves({
id: peerId,
addresses: [],
protocols: [BITSWAP_120],
metadata: new Map(),
tags: new Map()
})
}) as any

components.libp2p.dial.withArgs(peerId).resolves(connection)

const result = await network.connectTo(peerId)
expect(result).to.equal(connection)
expect(components.libp2p.dial.calledWith(peerId)).to.be.true()
// raceEvent listens via addEventListener; the fast path skips it entirely
const identifyListens = components.libp2p.addEventListener.getCalls()
.filter(call => call.args[0] === 'peer:identify')
expect(identifyListens, 'should not subscribe to peer:identify on the fast path').to.have.lengthOf(0)
})

it('should fall through to the identify race when the peer is not yet in peerStore', async () => {
const peerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519'))
const connection = stubInterface<Connection>()
const peerStoreError = new Error('NotFoundError')
peerStoreError.name = 'NotFoundError'

components.libp2p.peerStore = stubInterface({
get: Sinon.stub().rejects(peerStoreError)
}) as any

components.libp2p.dial.callsFake(async () => {
// simulate identify firing after dial
setTimeout(() => {
const call = components.libp2p.addEventListener.getCalls()
.find(c => c.args[0] === 'peer:identify')
const callback = call?.args[1]
if (typeof callback === 'function') {
callback(new CustomEvent<IdentifyResult>('peer:identify', {
detail: {
peerId,
protocols: [BITSWAP_120],
listenAddrs: [],
connection
}
}))
}
}, 10)
return connection
})

await network.connectTo(peerId)

const identifyListens = components.libp2p.addEventListener.getCalls()
.filter(call => call.args[0] === 'peer:identify')
expect(identifyListens, 'should subscribe to peer:identify on the slow path').to.have.lengthOf.at.least(1)
})

it('should register protocol handlers', () => {
expect(components.libp2p.handle.called).to.be.true()
expect(components.libp2p.register.calledWith(BITSWAP_120)).to.be.true()
Expand Down