Skip to content

Commit 39c6934

Browse files
authored
Merge branch 'master' into chore/kad/lookup-peers-before-insert
2 parents be9f840 + a197145 commit 39c6934

4 files changed

Lines changed: 105 additions & 16 deletions

File tree

libp2p/protocols/kademlia/find.nim

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -619,12 +619,30 @@ proc findPeer*(
619619

620620
return ok(PeerInfo(peerId: target, addrs: kad.switch.peerStore[AddressBook][target]))
621621

622-
proc findClosestPeers*(kad: KadDHT, target: Key): seq[Peer] =
623-
let closestPeerKeys = kad.rtable.findClosest(target, kad.config.replication).filterIt(
624-
it != kad.switch.peerInfo.peerId.toKey()
625-
)
622+
proc findClosestPeers*(kad: KadDHT, target: Key, requester: PeerId): seq[Peer] =
623+
## Over-fetches by `excluded.len` so dropping self and `requester` still fills the reply.
624+
let excluded = [kad.switch.peerInfo.peerId.toKey(), requester.toKey()]
625+
let closestPeerKeys = kad.rtable
626+
.findClosest(target, kad.config.replication + excluded.len)
627+
.filterIt(it notin excluded)
628+
629+
return kad.switch.toPeers(
630+
closestPeerKeys[0 ..< min(kad.config.replication, closestPeerKeys.len)]
631+
)
632+
633+
proc findNodeCloserPeers(kad: KadDHT, target: Key, requester: PeerId): seq[Peer] =
634+
## Also returns the target itself, which keeps client-mode peers resolvable.
635+
let closest = kad.findClosestPeers(target, requester)
636+
if target == requester.toKey():
637+
return closest
638+
639+
let targetPeer = target.toPeer(kad.switch).valueOr:
640+
return closest
641+
642+
if closest.len > 0 and closest[0].id == targetPeer.id:
643+
return closest
626644

627-
return kad.switch.toPeers(closestPeerKeys)
645+
return @[targetPeer] & closest
628646

629647
method handleFindNode*(
630648
kad: KadDHT, stream: Stream, msg: Message
@@ -634,7 +652,8 @@ method handleFindNode*(
634652
return
635653

636654
let response = Message(
637-
msgType: Opt.some(MessageType.findNode), closerPeers: kad.findClosestPeers(msgKey)
655+
msgType: Opt.some(MessageType.findNode),
656+
closerPeers: kad.findNodeCloserPeers(msgKey, stream.peerId),
638657
)
639658
let encoded = response.encode(kad.config.hideConnectionStatus)
640659
kad_message_bytes_sent.inc(encoded.len.int64, labelValues = [$MessageType.findNode])

libp2p/protocols/kademlia/get.nim

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ method handleGetValue*(
177177
let response = Message(
178178
msgType: Opt.some(MessageType.getValue),
179179
key: Opt.some(key),
180-
closerPeers: kad.findClosestPeers(key),
180+
closerPeers: kad.findClosestPeers(key, stream.peerId),
181181
)
182182
let encoded = response.encode(kad.config.hideConnectionStatus)
183183
kad_message_bytes_sent.inc(encoded.len.int64, labelValues = [$MessageType.getValue])
@@ -197,7 +197,7 @@ method handleGetValue*(
197197
timeReceived: Opt.some(entryRecord.time),
198198
)
199199
),
200-
closerPeers: kad.findClosestPeers(key),
200+
closerPeers: kad.findClosestPeers(key, stream.peerId),
201201
)
202202
let encoded = response.encode(kad.config.hideConnectionStatus)
203203
kad_message_bytes_sent.inc(encoded.len.int64, labelValues = [$MessageType.getValue])

libp2p/protocols/kademlia/provider.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ proc handleGetProviders*(
391391
let response = Message(
392392
msgType: Opt.some(MessageType.getProviders),
393393
key: msg.key,
394-
closerPeers: kad.findClosestPeers(msgKey),
394+
closerPeers: kad.findClosestPeers(msgKey, stream.peerId),
395395
providerPeers: providers.toSeq(),
396396
)
397397
let encoded = response.encode(kad.config.hideConnectionStatus)

tests/libp2p/kademlia/test_find.nim

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -221,10 +221,11 @@ suite "KadDHT Find":
221221
kads[0].hasKey(kads[2].rtable.selfId)
222222

223223
asyncTest "Find node with empty key returns closest peers":
224-
let kads = setupKadSwitches(2)
224+
let kads = setupKadSwitches(3)
225225
startAndDeferStop(kads)
226226

227-
await connect(kads[0], kads[1])
227+
# Setup: kads[0] <-> kads[1], kads[0] <-> kads[2]
228+
await connectHub(kads[0], kads[1 ..^ 1])
228229

229230
# Send FIND_NODE with empty key directly
230231
let emptyKey: Key = @[]
@@ -235,9 +236,9 @@ suite "KadDHT Find":
235236
check:
236237
response.msgType == MessageType.findNode
237238
response.closerPeers.len == 1
238-
response.closerPeers[0].id.get() == kads[1].rtable.selfId
239+
response.closerPeers[0].id.get() == kads[2].rtable.selfId
239240

240-
asyncTest "Find node for own PeerID returns closest peers":
241+
asyncTest "Find node for own PeerID excludes the requester":
241242
let kads = setupKadSwitches(3)
242243
startAndDeferStop(kads)
243244

@@ -252,9 +253,78 @@ suite "KadDHT Find":
252253
let closerPeersIds = response.closerPeers.mapIt(it.id.get())
253254
check:
254255
response.msgType == MessageType.findNode
255-
# kads[0] knows kads[1] and kads[2], should return both as closest peers
256-
response.closerPeers.len == 2
257-
kads[1].rtable.selfId in closerPeersIds
256+
response.closerPeers.len == 1
257+
kads[1].rtable.selfId notin closerPeersIds
258+
kads[2].rtable.selfId in closerPeersIds
259+
260+
asyncTest "Find node for a known target returns it once":
261+
let kads = setupKadSwitches(3)
262+
startAndDeferStop(kads)
263+
264+
await connectHub(kads[0], kads[1 ..^ 1])
265+
266+
let response = (
267+
await kads[1].dispatchFindNode(
268+
kads[0].switch.peerInfo.peerId, kads[2].rtable.selfId
269+
)
270+
).value()
271+
272+
check:
273+
response.closerPeers.len == 1
274+
response.closerPeers[0].id.get() == kads[2].rtable.selfId
275+
276+
asyncTest "Find node for an unknown target omits it":
277+
let kads = setupKadSwitches(3)
278+
startAndDeferStop(kads)
279+
280+
await connectHub(kads[0], kads[1 ..^ 1])
281+
282+
let unknownTarget = randomPeerId().toKey()
283+
let response = (
284+
await kads[1].dispatchFindNode(kads[0].switch.peerInfo.peerId, unknownTarget)
285+
).value()
286+
287+
let closerPeersIds = response.closerPeers.mapIt(it.id.get())
288+
check:
289+
unknownTarget notin closerPeersIds
290+
kads[2].rtable.selfId in closerPeersIds
291+
292+
asyncTest "Find node returns a target known only from the address book":
293+
let kads = setupKadSwitches(3)
294+
startAndDeferStop(kads)
295+
296+
await connectHub(kads[0], kads[1 ..^ 1])
297+
298+
# A client-mode peer is in nobody's routing table, but its addresses are known.
299+
let client = randomPeerId()
300+
kads[0].switch.peerStore[AddressBook][client] =
301+
@[MultiAddress.init("/ip4/127.0.0.1/tcp/9999").tryGet()]
302+
303+
let response = (
304+
await kads[1].dispatchFindNode(kads[0].switch.peerInfo.peerId, client.toKey())
305+
).value()
306+
307+
check:
308+
not kads[0].hasKey(client.toKey())
309+
response.closerPeers[0].id.get() == client.toKey()
310+
311+
asyncTest "Find node excludes the requester for an unrelated target":
312+
let kads = setupKadSwitches(3)
313+
startAndDeferStop(kads)
314+
315+
await connectHub(kads[0], kads[1 ..^ 1])
316+
317+
# kads[0] knows kads[1] and kads[2], and both fit in a reply, so pre-filter
318+
# kads[1] would get itself back even though the target is unrelated to it.
319+
let response = (
320+
await kads[1].dispatchFindNode(
321+
kads[0].switch.peerInfo.peerId, randomPeerId().toKey()
322+
)
323+
).value()
324+
325+
let closerPeersIds = response.closerPeers.mapIt(it.id.get())
326+
check:
327+
kads[1].rtable.selfId notin closerPeersIds
258328
kads[2].rtable.selfId in closerPeersIds
259329

260330
asyncTest "Find node with empty routing table returns empty result":

0 commit comments

Comments
 (0)