@libp2p/kad-dht 15.0.2 → 15.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/index.min.js +2 -15
  2. package/dist/index.min.js.map +7 -0
  3. package/dist/src/constants.d.ts +1 -1
  4. package/dist/src/constants.d.ts.map +1 -1
  5. package/dist/src/constants.js +1 -1
  6. package/dist/src/constants.js.map +1 -1
  7. package/dist/src/content-fetching/index.d.ts +4 -4
  8. package/dist/src/content-fetching/index.d.ts.map +1 -1
  9. package/dist/src/content-fetching/index.js +39 -12
  10. package/dist/src/content-fetching/index.js.map +1 -1
  11. package/dist/src/content-routing/index.d.ts.map +1 -1
  12. package/dist/src/content-routing/index.js +87 -37
  13. package/dist/src/content-routing/index.js.map +1 -1
  14. package/dist/src/index.d.ts +57 -5
  15. package/dist/src/index.d.ts.map +1 -1
  16. package/dist/src/index.js +1 -0
  17. package/dist/src/index.js.map +1 -1
  18. package/dist/src/kad-dht.d.ts +3 -1
  19. package/dist/src/kad-dht.d.ts.map +1 -1
  20. package/dist/src/kad-dht.js +14 -10
  21. package/dist/src/kad-dht.js.map +1 -1
  22. package/dist/src/network.d.ts +12 -6
  23. package/dist/src/network.d.ts.map +1 -1
  24. package/dist/src/network.js +17 -16
  25. package/dist/src/network.js.map +1 -1
  26. package/dist/src/peer-distance-list.d.ts +10 -3
  27. package/dist/src/peer-distance-list.d.ts.map +1 -1
  28. package/dist/src/peer-distance-list.js +13 -5
  29. package/dist/src/peer-distance-list.js.map +1 -1
  30. package/dist/src/peer-routing/index.d.ts +10 -8
  31. package/dist/src/peer-routing/index.d.ts.map +1 -1
  32. package/dist/src/peer-routing/index.js +78 -48
  33. package/dist/src/peer-routing/index.js.map +1 -1
  34. package/dist/src/providers.d.ts +4 -4
  35. package/dist/src/providers.d.ts.map +1 -1
  36. package/dist/src/providers.js +12 -12
  37. package/dist/src/providers.js.map +1 -1
  38. package/dist/src/query/events.d.ts +15 -3
  39. package/dist/src/query/events.d.ts.map +1 -1
  40. package/dist/src/query/events.js +9 -0
  41. package/dist/src/query/events.js.map +1 -1
  42. package/dist/src/query/manager.d.ts +2 -4
  43. package/dist/src/query/manager.d.ts.map +1 -1
  44. package/dist/src/query/manager.js +42 -16
  45. package/dist/src/query/manager.js.map +1 -1
  46. package/dist/src/query/query-path.d.ts +10 -14
  47. package/dist/src/query/query-path.d.ts.map +1 -1
  48. package/dist/src/query/query-path.js +67 -53
  49. package/dist/src/query/query-path.js.map +1 -1
  50. package/dist/src/query/types.d.ts +6 -5
  51. package/dist/src/query/types.d.ts.map +1 -1
  52. package/dist/src/query-self.d.ts +2 -3
  53. package/dist/src/query-self.d.ts.map +1 -1
  54. package/dist/src/query-self.js +11 -14
  55. package/dist/src/query-self.js.map +1 -1
  56. package/dist/src/routing-table/closest-peers.js +1 -1
  57. package/dist/src/routing-table/closest-peers.js.map +1 -1
  58. package/dist/src/routing-table/k-bucket.js +1 -1
  59. package/dist/src/routing-table/k-bucket.js.map +1 -1
  60. package/dist/src/rpc/handlers/get-providers.js +1 -1
  61. package/dist/src/rpc/handlers/get-providers.js.map +1 -1
  62. package/dist/src/utils.d.ts +1 -0
  63. package/dist/src/utils.d.ts.map +1 -1
  64. package/dist/src/utils.js +4 -0
  65. package/dist/src/utils.js.map +1 -1
  66. package/dist/typedoc-urls.json +4 -0
  67. package/package.json +12 -11
  68. package/src/constants.ts +1 -1
  69. package/src/content-fetching/index.ts +40 -13
  70. package/src/content-routing/index.ts +92 -44
  71. package/src/index.ts +64 -5
  72. package/src/kad-dht.ts +14 -10
  73. package/src/network.ts +33 -20
  74. package/src/peer-distance-list.ts +19 -7
  75. package/src/peer-routing/index.ts +83 -52
  76. package/src/providers.ts +13 -13
  77. package/src/query/events.ts +27 -3
  78. package/src/query/manager.ts +48 -21
  79. package/src/query/query-path.ts +86 -76
  80. package/src/query/types.ts +7 -5
  81. package/src/query-self.ts +15 -17
  82. package/src/routing-table/closest-peers.ts +1 -1
  83. package/src/routing-table/k-bucket.ts +1 -1
  84. package/src/rpc/handlers/get-providers.ts +1 -1
  85. package/src/utils.ts +9 -0
@@ -3,7 +3,6 @@ import { InvalidPublicKeyError, NotFoundError } from '@libp2p/interface'
3
3
  import { peerIdFromPublicKey, peerIdFromMultihash } from '@libp2p/peer-id'
4
4
  import { Libp2pRecord } from '@libp2p/record'
5
5
  import * as Digest from 'multiformats/hashes/digest'
6
- import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
7
6
  import { xor as uint8ArrayXor } from 'uint8arrays/xor'
8
7
  import { xorCompare as uint8ArrayXorCompare } from 'uint8arrays/xor-compare'
9
8
  import { QueryError, InvalidRecordError } from '../errors.js'
@@ -18,17 +17,19 @@ import { verifyRecord } from '../record/validators.js'
18
17
  import { convertBuffer, convertPeerId, keyForPublicKey } from '../utils.js'
19
18
  import type { DHTRecord, FinalPeerEvent, QueryEvent, Validators } from '../index.js'
20
19
  import type { Message } from '../message/dht.js'
21
- import type { Network } from '../network.js'
20
+ import type { Network, SendMessageOptions } from '../network.js'
22
21
  import type { QueryManager, QueryOptions } from '../query/manager.js'
23
22
  import type { QueryFunc } from '../query/types.js'
24
23
  import type { RoutingTable } from '../routing-table/index.js'
25
24
  import type { ComponentLogger, Logger, Metrics, PeerId, PeerInfo, PeerStore, RoutingOptions } from '@libp2p/interface'
25
+ import type { ConnectionManager } from '@libp2p/interface-internal'
26
26
 
27
27
  export interface PeerRoutingComponents {
28
28
  peerId: PeerId
29
29
  peerStore: PeerStore
30
30
  logger: ComponentLogger
31
31
  metrics?: Metrics
32
+ connectionManager: ConnectionManager
32
33
  }
33
34
 
34
35
  export interface PeerRoutingInit {
@@ -45,16 +46,14 @@ export class PeerRouting {
45
46
  private readonly network: Network
46
47
  private readonly validators: Validators
47
48
  private readonly queryManager: QueryManager
48
- private readonly peerStore: PeerStore
49
- private readonly peerId: PeerId
49
+ private readonly components: PeerRoutingComponents
50
50
 
51
51
  constructor (components: PeerRoutingComponents, init: PeerRoutingInit) {
52
52
  this.routingTable = init.routingTable
53
53
  this.network = init.network
54
54
  this.validators = init.validators
55
55
  this.queryManager = init.queryManager
56
- this.peerStore = components.peerStore
57
- this.peerId = components.peerId
56
+ this.components = components
58
57
  this.log = components.logger.forComponent(`${init.logPrefix}:peer-routing`)
59
58
 
60
59
  this.findPeer = components.metrics?.traceFunction('libp2p.kadDHT.findPeer', this.findPeer.bind(this), {
@@ -77,7 +76,7 @@ export class PeerRouting {
77
76
  this.log('findPeerLocal found %p in routing table', peer)
78
77
 
79
78
  try {
80
- peerData = await this.peerStore.get(p)
79
+ peerData = await this.components.peerStore.get(p)
81
80
  } catch (err: any) {
82
81
  if (err.name !== 'NotFoundError') {
83
82
  throw err
@@ -87,7 +86,7 @@ export class PeerRouting {
87
86
 
88
87
  if (peerData == null) {
89
88
  try {
90
- peerData = await this.peerStore.get(peer)
89
+ peerData = await this.components.peerStore.get(peer)
91
90
  } catch (err: any) {
92
91
  if (err.name !== 'NotFoundError') {
93
92
  throw err
@@ -110,7 +109,7 @@ export class PeerRouting {
110
109
  /**
111
110
  * Get a value via rpc call for the given parameters
112
111
  */
113
- async * _getValueSingle (peer: PeerId, key: Uint8Array, options: RoutingOptions = {}): AsyncGenerator<QueryEvent> {
112
+ async * _getValueSingle (peer: PeerId, key: Uint8Array, options: SendMessageOptions): AsyncGenerator<QueryEvent> {
114
113
  const msg: Partial<Message> = {
115
114
  type: MessageType.GET_VALUE,
116
115
  key
@@ -124,8 +123,17 @@ export class PeerRouting {
124
123
  */
125
124
  async * getPublicKeyFromNode (peer: PeerId, options: RoutingOptions = {}): AsyncGenerator<QueryEvent> {
126
125
  const pkKey = keyForPublicKey(peer)
126
+ const path = {
127
+ index: -1,
128
+ queued: 0,
129
+ running: 0,
130
+ total: 0
131
+ }
127
132
 
128
- for await (const event of this._getValueSingle(peer, pkKey, options)) {
133
+ for await (const event of this._getValueSingle(peer, pkKey, {
134
+ ...options,
135
+ path
136
+ })) {
129
137
  yield event
130
138
 
131
139
  if (event.name === 'PEER_RESPONSE' && event.record != null) {
@@ -143,7 +151,8 @@ export class PeerRouting {
143
151
 
144
152
  yield valueEvent({
145
153
  from: peer,
146
- value: event.record.value
154
+ value: event.record.value,
155
+ path
147
156
  }, options)
148
157
  }
149
158
  }
@@ -165,8 +174,14 @@ export class PeerRouting {
165
174
  if (pi != null) {
166
175
  this.log('found local')
167
176
  yield finalPeerEvent({
168
- from: this.peerId,
169
- peer: pi
177
+ from: this.components.peerId,
178
+ peer: pi,
179
+ path: {
180
+ index: -1,
181
+ queued: 0,
182
+ running: 0,
183
+ total: 0
184
+ }
170
185
  }, options)
171
186
  return
172
187
  }
@@ -177,15 +192,16 @@ export class PeerRouting {
177
192
  if (options.useNetwork !== false) {
178
193
  const self = this // eslint-disable-line @typescript-eslint/no-this-alias
179
194
 
180
- const findPeerQuery: QueryFunc = async function * ({ peer, signal }) {
195
+ const findPeerQuery: QueryFunc = async function * ({ peer, signal, path }) {
181
196
  const request: Partial<Message> = {
182
197
  type: MessageType.FIND_NODE,
183
198
  key: id.toMultihash().bytes
184
199
  }
185
200
 
186
- for await (const event of self.network.sendRequest(peer, request, {
201
+ for await (const event of self.network.sendRequest(peer.id, request, {
187
202
  ...options,
188
- signal
203
+ signal,
204
+ path
189
205
  })) {
190
206
  yield event
191
207
 
@@ -194,7 +210,11 @@ export class PeerRouting {
194
210
 
195
211
  // found the peer
196
212
  if (match != null) {
197
- yield finalPeerEvent({ from: event.from, peer: match }, options)
213
+ yield finalPeerEvent({
214
+ from: event.from,
215
+ peer: match,
216
+ path: event.path
217
+ }, options)
198
218
  }
199
219
  }
200
220
  }
@@ -210,53 +230,64 @@ export class PeerRouting {
210
230
  }
211
231
 
212
232
  if (!foundPeer) {
213
- yield queryErrorEvent({ from: this.peerId, error: new NotFoundError('Not found') }, options)
233
+ throw new NotFoundError('Not found')
214
234
  }
215
235
  }
216
236
 
217
237
  /**
218
- * Kademlia 'FIND_NODE' operation on a key, which could be the bytes from
219
- * a multihash or a peer ID
238
+ * Kademlia 'FIND_NODE' operation on a key, which could be the bytes from a
239
+ * multihash or a peer ID
220
240
  */
221
241
  async * getClosestPeers (key: Uint8Array, options: QueryOptions = {}): AsyncGenerator<QueryEvent> {
222
242
  this.log('getClosestPeers to %b', key)
223
243
  const kadId = await convertBuffer(key)
224
- const tablePeers = this.routingTable.closestPeers(kadId)
225
- const self = this // eslint-disable-line @typescript-eslint/no-this-alias
226
-
227
244
  const peers = new PeerDistanceList(kadId, this.routingTable.kBucketSize)
228
- await Promise.all(tablePeers.map(async peer => { await peers.add({ id: peer, multiaddrs: [] }) }))
245
+ const self = this // eslint-disable-line @typescript-eslint/no-this-alias
229
246
 
230
- const getCloserPeersQuery: QueryFunc = async function * ({ peer, signal }) {
231
- self.log('closerPeersSingle %s from %p', uint8ArrayToString(key, 'base32'), peer)
247
+ const getCloserPeersQuery: QueryFunc = async function * ({ peer, path, peerKadId, signal }) {
248
+ self.log('getClosestPeers asking %p', peer)
232
249
  const request: Partial<Message> = {
233
250
  type: MessageType.FIND_NODE,
234
251
  key
235
252
  }
236
253
 
237
- yield * self.network.sendRequest(peer, request, {
254
+ yield * self.network.sendRequest(peer.id, request, {
238
255
  ...options,
239
- signal
256
+ signal,
257
+ path
240
258
  })
241
- }
242
259
 
243
- for await (const event of this.queryManager.run(key, getCloserPeersQuery, options)) {
244
- if (event.name === 'PEER_RESPONSE') {
245
- await Promise.all(event.closer.map(async peerData => {
246
- await peers.add(peerData)
247
- }))
248
- }
249
-
250
- yield event
260
+ // add the peer to the list if we've managed to contact it successfully
261
+ peers.addWithKadId(peer, peerKadId, path)
251
262
  }
252
263
 
264
+ yield * this.queryManager.run(key, getCloserPeersQuery, options)
265
+
253
266
  this.log('found %d peers close to %b', peers.length, key)
254
267
 
255
- for (const peer of peers.peers) {
256
- yield finalPeerEvent({
257
- from: this.peerId,
258
- peer
259
- }, options)
268
+ for (let { peer, path } of peers.peers) {
269
+ try {
270
+ if (peer.multiaddrs.length === 0) {
271
+ peer = await self.components.peerStore.getInfo(peer.id)
272
+ }
273
+
274
+ if (peer.multiaddrs.length === 0) {
275
+ continue
276
+ }
277
+
278
+ yield finalPeerEvent({
279
+ from: this.components.peerId,
280
+ peer: await self.components.peerStore.getInfo(peer.id),
281
+ path: {
282
+ index: path.index,
283
+ queued: 0,
284
+ running: 0,
285
+ total: 0
286
+ }
287
+ }, options)
288
+ } catch {
289
+ continue
290
+ }
260
291
  }
261
292
  }
262
293
 
@@ -266,7 +297,7 @@ export class PeerRouting {
266
297
  *
267
298
  * Note: The peerStore is updated with new addresses found for the given peer.
268
299
  */
269
- async * getValueOrPeers (peer: PeerId, key: Uint8Array, options: RoutingOptions = {}): AsyncGenerator<QueryEvent> {
300
+ async * getValueOrPeers (peer: PeerId, key: Uint8Array, options: SendMessageOptions): AsyncGenerator<QueryEvent> {
270
301
  for await (const event of this._getValueSingle(peer, key, options)) {
271
302
  if (event.name === 'PEER_RESPONSE') {
272
303
  if (event.record != null) {
@@ -277,7 +308,11 @@ export class PeerRouting {
277
308
  const errMsg = 'invalid record received, discarded'
278
309
  this.log(errMsg)
279
310
 
280
- yield queryErrorEvent({ from: event.from, error: new QueryError(errMsg) }, options)
311
+ yield queryErrorEvent({
312
+ from: event.from,
313
+ error: new QueryError(errMsg),
314
+ path: options.path
315
+ }, options)
281
316
  continue
282
317
  }
283
318
  }
@@ -300,7 +335,8 @@ export class PeerRouting {
300
335
  }
301
336
 
302
337
  /**
303
- * Get the nearest peers to the given query, but if closer than self
338
+ * Get the peers in our routing table that are closer than the passed PeerId
339
+ * to the passed key
304
340
  */
305
341
  async getCloserPeersOffline (key: Uint8Array, closerThan: PeerId): Promise<PeerInfo[]> {
306
342
  const output: PeerInfo[] = []
@@ -310,7 +346,7 @@ export class PeerRouting {
310
346
  const multihash = Digest.decode(key)
311
347
  const targetPeerId = peerIdFromMultihash(multihash)
312
348
 
313
- const peer = await this.peerStore.get(targetPeerId)
349
+ const peer = await this.components.peerStore.get(targetPeerId)
314
350
 
315
351
  output.push({
316
352
  id: peer.id,
@@ -333,12 +369,7 @@ export class PeerRouting {
333
369
  }
334
370
 
335
371
  try {
336
- const peer = await this.peerStore.get(peerId)
337
-
338
- output.push({
339
- id: peerId,
340
- multiaddrs: peer.addresses.map(({ multiaddr }) => multiaddr)
341
- })
372
+ output.push(await this.components.peerStore.getInfo(peerId))
342
373
  } catch (err: any) {
343
374
  if (err.name !== 'NotFoundError') {
344
375
  throw err
package/src/providers.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { PeerMap } from '@libp2p/peer-collections'
2
2
  import * as varint from 'uint8-varint'
3
3
  import { parseProviderKey, readProviderTime, toProviderKey } from './utils.js'
4
- import type { ComponentLogger, Logger, Metrics, PeerId } from '@libp2p/interface'
4
+ import type { AbortOptions, ComponentLogger, Logger, Metrics, PeerId } from '@libp2p/interface'
5
5
  import type { Datastore } from 'interface-datastore'
6
6
  import type { Mortice } from 'mortice'
7
7
  import type { CID } from 'multiformats'
@@ -37,11 +37,11 @@ export class Providers {
37
37
  /**
38
38
  * Add a new provider for the given CID
39
39
  */
40
- async addProvider (cid: CID, provider: PeerId): Promise<void> {
40
+ async addProvider (cid: CID, provider: PeerId, options?: AbortOptions): Promise<void> {
41
41
  const release = await this.lock.readLock()
42
42
 
43
43
  try {
44
- this.log('%p provides %s', provider, cid)
44
+ this.log.trace('%p provides %s', provider, cid)
45
45
  await this.writeProviderEntry(cid, provider)
46
46
  } finally {
47
47
  release()
@@ -51,12 +51,12 @@ export class Providers {
51
51
  /**
52
52
  * Remove a provider for the given CID
53
53
  */
54
- async removeProvider (cid: CID, provider: PeerId): Promise<void> {
54
+ async removeProvider (cid: CID, provider: PeerId, options?: AbortOptions): Promise<void> {
55
55
  const release = await this.lock.writeLock()
56
56
 
57
57
  try {
58
58
  const key = toProviderKey(this.datastorePrefix, cid, provider)
59
- this.log('%p no longer provides %s', provider, cid)
59
+ this.log.trace('%p no longer provides %s', provider, cid)
60
60
  await this.datastore.delete(key)
61
61
  } finally {
62
62
  release()
@@ -66,13 +66,13 @@ export class Providers {
66
66
  /**
67
67
  * Get a list of providers for the given CID
68
68
  */
69
- async getProviders (cid: CID): Promise<PeerId[]> {
69
+ async getProviders (cid: CID, options?: AbortOptions): Promise<PeerId[]> {
70
70
  const release = await this.lock.readLock()
71
71
 
72
72
  try {
73
- this.log('get providers for %c', cid)
74
- const provs = await this.loadProviders(cid)
75
- this.log('got %d providers for %c', provs.size, cid)
73
+ this.log.trace('get providers for %c', cid)
74
+ const provs = await this.loadProviders(cid, options)
75
+ this.log.trace('got %d providers for %c', provs.size, cid)
76
76
 
77
77
  return [...provs.keys()]
78
78
  } finally {
@@ -83,21 +83,21 @@ export class Providers {
83
83
  /**
84
84
  * Write a provider into the given store
85
85
  */
86
- private async writeProviderEntry (cid: CID, peerId: PeerId, time: Date = new Date()): Promise<void> {
86
+ private async writeProviderEntry (cid: CID, peerId: PeerId, time: Date = new Date(), options?: AbortOptions): Promise<void> {
87
87
  const key = toProviderKey(this.datastorePrefix, cid, peerId)
88
88
  const buffer = varint.encode(time.getTime())
89
89
 
90
- await this.datastore.put(key, buffer)
90
+ await this.datastore.put(key, buffer, options)
91
91
  }
92
92
 
93
93
  /**
94
94
  * Load providers for the given CID from the store
95
95
  */
96
- private async loadProviders (cid: CID): Promise<PeerMap<Date>> {
96
+ private async loadProviders (cid: CID, options?: AbortOptions): Promise<PeerMap<Date>> {
97
97
  const providers = new PeerMap<Date>()
98
98
  const key = toProviderKey(this.datastorePrefix, cid)
99
99
 
100
- for await (const entry of this.datastore.query({ prefix: key.toString() })) {
100
+ for await (const entry of this.datastore.query({ prefix: key.toString() }, options)) {
101
101
  const { peerId } = parseProviderKey(entry.key)
102
102
  providers.set(peerId, readProviderTime(entry.value))
103
103
  }
@@ -1,4 +1,4 @@
1
- import type { MessageType, SendQueryEvent, PeerResponseEvent, DialPeerEvent, AddPeerEvent, ValueEvent, ProviderEvent, QueryErrorEvent, FinalPeerEvent } from '../index.js'
1
+ import type { MessageType, SendQueryEvent, PeerResponseEvent, AddPeerEvent, ValueEvent, ProviderEvent, QueryErrorEvent, FinalPeerEvent, DisjointPath, PathEndedEvent, DialPeerEvent } from '../index.js'
2
2
  import type { PeerId, PeerInfo } from '@libp2p/interface'
3
3
  import type { Libp2pRecord } from '@libp2p/record'
4
4
  import type { ProgressOptions } from 'progress-events'
@@ -6,6 +6,7 @@ import type { ProgressOptions } from 'progress-events'
6
6
  export interface QueryEventFields {
7
7
  to: PeerId
8
8
  type: MessageType
9
+ path: DisjointPath
9
10
  }
10
11
 
11
12
  export function sendQueryEvent (fields: QueryEventFields, options: ProgressOptions = {}): SendQueryEvent {
@@ -25,6 +26,7 @@ export function sendQueryEvent (fields: QueryEventFields, options: ProgressOptio
25
26
  export interface PeerResponseEventFields {
26
27
  from: PeerId
27
28
  messageType: MessageType
29
+ path: DisjointPath
28
30
  closer?: PeerInfo[]
29
31
  providers?: PeerInfo[]
30
32
  record?: Libp2pRecord
@@ -48,6 +50,7 @@ export function peerResponseEvent (fields: PeerResponseEventFields, options: Pro
48
50
  export interface FinalPeerEventFields {
49
51
  from: PeerId
50
52
  peer: PeerInfo
53
+ path: DisjointPath
51
54
  }
52
55
 
53
56
  export function finalPeerEvent (fields: FinalPeerEventFields, options: ProgressOptions = {}): FinalPeerEvent {
@@ -65,6 +68,7 @@ export function finalPeerEvent (fields: FinalPeerEventFields, options: ProgressO
65
68
  export interface ErrorEventFields {
66
69
  from: PeerId
67
70
  error: Error
71
+ path: DisjointPath
68
72
  }
69
73
 
70
74
  export function queryErrorEvent (fields: ErrorEventFields, options: ProgressOptions = {}): QueryErrorEvent {
@@ -82,6 +86,7 @@ export function queryErrorEvent (fields: ErrorEventFields, options: ProgressOpti
82
86
  export interface ProviderEventFields {
83
87
  from: PeerId
84
88
  providers: PeerInfo[]
89
+ path: DisjointPath
85
90
  }
86
91
 
87
92
  export function providerEvent (fields: ProviderEventFields, options: ProgressOptions = {}): ProviderEvent {
@@ -99,6 +104,7 @@ export function providerEvent (fields: ProviderEventFields, options: ProgressOpt
99
104
  export interface ValueEventFields {
100
105
  from: PeerId
101
106
  value: Uint8Array
107
+ path: DisjointPath
102
108
  }
103
109
 
104
110
  export function valueEvent (fields: ValueEventFields, options: ProgressOptions = {}): ValueEvent {
@@ -113,11 +119,12 @@ export function valueEvent (fields: ValueEventFields, options: ProgressOptions =
113
119
  return event
114
120
  }
115
121
 
116
- export interface PeerEventFields {
122
+ export interface AddPeerEventFields {
117
123
  peer: PeerId
124
+ path: DisjointPath
118
125
  }
119
126
 
120
- export function addPeerEvent (fields: PeerEventFields, options: ProgressOptions = {}): AddPeerEvent {
127
+ export function addPeerEvent (fields: AddPeerEventFields, options: ProgressOptions = {}): AddPeerEvent {
121
128
  const event: AddPeerEvent = {
122
129
  ...fields,
123
130
  name: 'ADD_PEER',
@@ -131,6 +138,7 @@ export function addPeerEvent (fields: PeerEventFields, options: ProgressOptions
131
138
 
132
139
  export interface DialPeerEventFields {
133
140
  peer: PeerId
141
+ path: DisjointPath
134
142
  }
135
143
 
136
144
  export function dialPeerEvent (fields: DialPeerEventFields, options: ProgressOptions = {}): DialPeerEvent {
@@ -144,3 +152,19 @@ export function dialPeerEvent (fields: DialPeerEventFields, options: ProgressOpt
144
152
 
145
153
  return event
146
154
  }
155
+
156
+ export interface PathEndedEventFields {
157
+ path: DisjointPath
158
+ }
159
+
160
+ export function pathEndedEvent (fields: PathEndedEventFields, options: ProgressOptions = {}): PathEndedEvent {
161
+ const event: PathEndedEvent = {
162
+ ...fields,
163
+ name: 'PATH_ENDED',
164
+ type: 8
165
+ }
166
+
167
+ options.onProgress?.(new CustomEvent('kad-dht:query:path-ended', { detail: event }))
168
+
169
+ return event
170
+ }
@@ -1,7 +1,9 @@
1
+ /* eslint-disable complexity */
1
2
  import { setMaxListeners } from '@libp2p/interface'
2
- import { PeerSet } from '@libp2p/peer-collections'
3
+ import { createScalableCuckooFilter } from '@libp2p/utils/filters'
3
4
  import { anySignal } from 'any-signal'
4
5
  import merge from 'it-merge'
6
+ import { pEvent } from 'p-event'
5
7
  import { raceSignal } from 'race-signal'
6
8
  import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
7
9
  import {
@@ -26,6 +28,7 @@ export interface QueryManagerInit {
26
28
  disjointPaths?: number
27
29
  alpha?: number
28
30
  initialQuerySelfHasRun: DeferredPromise<void>
31
+ allowQueryWithZeroPeers?: boolean
29
32
  routingTable: RoutingTable
30
33
  }
31
34
 
@@ -37,11 +40,6 @@ export interface QueryManagerComponents {
37
40
  }
38
41
 
39
42
  export interface QueryOptions extends RoutingOptions {
40
- /**
41
- * A timeout for subqueries executed as part of the main query
42
- */
43
- queryFuncTimeout?: number
44
-
45
43
  isSelfQuery?: boolean
46
44
  }
47
45
 
@@ -59,6 +57,7 @@ export class QueryManager implements Startable {
59
57
  private readonly routingTable: RoutingTable
60
58
  private initialQuerySelfHasRun?: DeferredPromise<void>
61
59
  private readonly logPrefix: string
60
+ private readonly allowQueryWithZeroPeers: boolean
62
61
 
63
62
  constructor (components: QueryManagerComponents, init: QueryManagerInit) {
64
63
  this.logPrefix = init.logPrefix
@@ -69,6 +68,7 @@ export class QueryManager implements Startable {
69
68
  this.logger = components.logger
70
69
  this.peerId = components.peerId
71
70
  this.connectionManager = components.connectionManager
71
+ this.allowQueryWithZeroPeers = init.allowQueryWithZeroPeers ?? false
72
72
 
73
73
  // allow us to stop queries on shut down
74
74
  this.shutDownController = new AbortController()
@@ -146,8 +146,18 @@ export class QueryManager implements Startable {
146
146
  let queryFinished = false
147
147
 
148
148
  try {
149
+ if (this.routingTable.size === 0 && !this.allowQueryWithZeroPeers) {
150
+ log('routing table was empty, waiting for some peers before running%s query', options.isSelfQuery === true ? ' self' : '')
151
+ // wait to discover at least one DHT peer that isn't us
152
+ await pEvent(this.routingTable, 'peer:add', {
153
+ signal,
154
+ filter: (event) => !this.peerId.equals(event.detail)
155
+ })
156
+ log('routing table has peers, continuing with%s query', options.isSelfQuery === true ? ' self' : '')
157
+ }
158
+
149
159
  if (options.isSelfQuery !== true && this.initialQuerySelfHasRun != null) {
150
- log('waiting for initial query-self query before continuing')
160
+ log('waiting for initial self query before continuing')
151
161
 
152
162
  await raceSignal(this.initialQuerySelfHasRun.promise, signal)
153
163
 
@@ -157,30 +167,43 @@ export class QueryManager implements Startable {
157
167
  log('query:start')
158
168
 
159
169
  const id = await convertBuffer(key)
160
- const peers = this.routingTable.closestPeers(id)
161
- const peersToQuery = peers.slice(0, Math.min(this.disjointPaths, peers.length))
170
+ const peers = this.routingTable.closestPeers(id, this.routingTable.kBucketSize)
171
+
172
+ // split peers into d buckets evenly(ish)
173
+ const peersToQuery = peers.sort(() => {
174
+ if (Math.random() > 0.5) {
175
+ return 1
176
+ }
177
+
178
+ return -1
179
+ })
180
+ .reduce((acc: PeerId[][], curr, index) => {
181
+ acc[index % this.disjointPaths].push(curr)
182
+
183
+ return acc
184
+ }, new Array(this.disjointPaths).fill(0).map(() => []))
185
+ .filter(peers => peers.length > 0)
162
186
 
163
187
  if (peers.length === 0) {
164
- log.error('Running query with no peers')
188
+ log.error('running query with no peers')
165
189
  return
166
190
  }
167
191
 
168
192
  // make sure we don't get trapped in a loop
169
- const peersSeen = new PeerSet()
193
+ const peersSeen = createScalableCuckooFilter(1024)
170
194
 
171
195
  // Create query paths from the starting peers
172
196
  const paths = peersToQuery.map((peer, index) => {
173
197
  return queryPath({
174
198
  ...options,
175
199
  key,
176
- startingPeer: peer,
200
+ startingPeers: peer,
177
201
  ourPeerId: this.peerId,
178
202
  signal,
179
203
  query: queryFunc,
180
- pathIndex: index,
204
+ path: index,
181
205
  numPaths: peersToQuery.length,
182
206
  alpha: this.alpha,
183
- queryFuncTimeout: options.queryFuncTimeout,
184
207
  log,
185
208
  peersSeen,
186
209
  onProgress: options.onProgress,
@@ -197,22 +220,26 @@ export class QueryManager implements Startable {
197
220
  if (event.name === 'PEER_RESPONSE') {
198
221
  for (const peer of [...event.closer, ...event.providers]) {
199
222
  // eslint-disable-next-line max-depth
200
- if (!(await this.connectionManager.isDialable(peer.multiaddrs))) {
223
+ if (!(await this.connectionManager.isDialable(peer.multiaddrs, {
224
+ signal
225
+ }))) {
201
226
  continue
202
227
  }
203
228
 
204
- await this.routingTable.add(peer.id)
229
+ await this.routingTable.add(peer.id, {
230
+ signal
231
+ })
205
232
  }
206
233
  }
207
234
 
235
+ signal.throwIfAborted()
208
236
  yield event
209
237
  }
210
238
 
211
239
  queryFinished = true
212
- } catch (err: any) {
213
- if (!this.running && err.name === 'QueryAbortedError') {
214
- // ignore query aborted errors that were thrown during query manager shutdown
215
- } else {
240
+ } catch (err) {
241
+ if (this.running) {
242
+ // ignore errors thrown during shut down
216
243
  throw err
217
244
  }
218
245
  } finally {
@@ -223,7 +250,7 @@ export class QueryManager implements Startable {
223
250
 
224
251
  signal.clear()
225
252
 
226
- log('query:done')
253
+ log('query finished')
227
254
  }
228
255
  }
229
256
  }