Skip to content

Commit 47a51e3

Browse files
authored
fix(kad-dht): only keep peers that responded in getClosestPeers (libp2p#3537)
* fix(kad-dht): only keep peers that responded in getClosestPeers Adds a peer to the closest set only if it sent a PEER_RESPONSE during the lookup, so dead and stale routing-table entries no longer pad the closest-K or receive wasted PUT_VALUEs. * docs(kad-dht): restore the original comment on the closest-set add
1 parent d4dad9b commit 47a51e3

2 files changed

Lines changed: 63 additions & 6 deletions

File tree

packages/kad-dht/src/peer-routing/index.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -249,14 +249,24 @@ export class PeerRouting {
249249
key
250250
}
251251

252-
yield * self.network.sendRequest(peer.id, request, {
252+
let responded = false
253+
254+
for await (const event of self.network.sendRequest(peer.id, request, {
253255
...options,
254256
signal,
255257
path
256-
})
258+
})) {
259+
if (event.name === 'PEER_RESPONSE') {
260+
responded = true
261+
}
257262

258-
// add the peer to the list if we've managed to contact it successfully
259-
peers.addWithKadId(peer, peerKadId, path)
263+
yield event
264+
}
265+
266+
if (responded) {
267+
// add the peer to the list if we've managed to contact it successfully
268+
peers.addWithKadId(peer, peerKadId, path)
269+
}
260270
}
261271

262272
yield * this.queryManager.run(key, getCloserPeersQuery, options)

packages/kad-dht/test/peer-routing.spec.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@ import { multiaddr } from '@multiformats/multiaddr'
55
import { expect } from 'aegir/chai'
66
import { stubInterface } from 'sinon-ts'
77
import { K } from '../src/constants.ts'
8+
import { MessageType } from '../src/message/dht.ts'
89
import { PeerRouting } from '../src/peer-routing/index.ts'
9-
import { convertBuffer } from '../src/utils.ts'
10+
import { peerResponseEvent, queryErrorEvent } from '../src/query/events.ts'
11+
import { convertBuffer, convertPeerId } from '../src/utils.ts'
1012
import { createPeerIdsWithPrivateKey } from './utils/create-peer-id.ts'
1113
import { sortClosestPeers } from './utils/sort-closest-peers.ts'
1214
import type { PeerAndKey } from './utils/create-peer-id.ts'
13-
import type { Validators } from '../src/index.ts'
15+
import type { QueryEvent, Validators } from '../src/index.ts'
1416
import type { Network } from '../src/network.ts'
1517
import type { QueryManager } from '../src/query/manager.ts'
18+
import type { QueryFunc } from '../src/query/types.ts'
1619
import type { RoutingTable } from '../src/routing-table/index.ts'
1720
import type { Peer, ComponentLogger, PeerId, PeerStore } from '@libp2p/interface'
1821
import type { ConnectionManager } from '@libp2p/interface-internal'
@@ -135,6 +138,50 @@ describe('peer-routing', () => {
135138
expect(closer[1].id).to.equal(serverPeer.id)
136139
})
137140
})
141+
142+
describe('getClosestPeers', () => {
143+
it('only adds peers to the closest set if they responded to the query', async () => {
144+
const key = Uint8Array.from([0, 1, 2, 3, 4])
145+
const [livePeer, deadPeer] = await getSortedPeers(key, 2)
146+
const path = { index: 0, queued: 0, running: 0, total: 1 }
147+
148+
// the QueryManager is stubbed, so drive the query function ourselves for
149+
// both peers and re-yield whatever the network produces for each
150+
const run = async function * (_key: Uint8Array, query: QueryFunc): AsyncGenerator<QueryEvent> {
151+
for (const { peerId } of [livePeer, deadPeer]) {
152+
yield * query({
153+
key,
154+
peer: { id: peerId, multiaddrs: [] },
155+
peerKadId: await convertPeerId(peerId),
156+
path,
157+
numPaths: 1
158+
})
159+
}
160+
}
161+
init.queryManager.run.callsFake(run)
162+
163+
// the live peer answers; the dead peer only errors, never sending a
164+
// PEER_RESPONSE
165+
const sendRequest = async function * (to: PeerId): AsyncGenerator<QueryEvent> {
166+
if (to.equals(livePeer.peerId)) {
167+
yield peerResponseEvent({ from: to, messageType: MessageType.FIND_NODE, path })
168+
} else {
169+
yield queryErrorEvent({ from: to, error: new Error('could not dial peer'), path })
170+
}
171+
}
172+
init.network.sendRequest.callsFake(sendRequest)
173+
174+
const finalPeerIds: PeerId[] = []
175+
for await (const event of peerRouting.getClosestPeers(key)) {
176+
if (event.name === 'FINAL_PEER') {
177+
finalPeerIds.push(event.peer.id)
178+
}
179+
}
180+
181+
expect(finalPeerIds).to.have.lengthOf(1)
182+
expect(finalPeerIds[0].equals(livePeer.peerId)).to.be.true()
183+
})
184+
})
138185
})
139186

140187
async function getSortedPeers (key: Uint8Array, count = 3): Promise<PeerAndKey[]> {

0 commit comments

Comments
 (0)