@libp2p/kad-dht 15.0.2 → 15.1.0-aa25d38ab

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 (101) 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 +40 -13
  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 +88 -38
  13. package/dist/src/content-routing/index.js.map +1 -1
  14. package/dist/src/index.d.ts +58 -6
  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/message/dht.d.ts +1 -1
  23. package/dist/src/message/dht.d.ts.map +1 -1
  24. package/dist/src/message/dht.js +0 -5
  25. package/dist/src/message/dht.js.map +1 -1
  26. package/dist/src/network.d.ts +14 -8
  27. package/dist/src/network.d.ts.map +1 -1
  28. package/dist/src/network.js +17 -16
  29. package/dist/src/network.js.map +1 -1
  30. package/dist/src/peer-distance-list.d.ts +10 -3
  31. package/dist/src/peer-distance-list.d.ts.map +1 -1
  32. package/dist/src/peer-distance-list.js +13 -5
  33. package/dist/src/peer-distance-list.js.map +1 -1
  34. package/dist/src/peer-routing/index.d.ts +10 -8
  35. package/dist/src/peer-routing/index.d.ts.map +1 -1
  36. package/dist/src/peer-routing/index.js +79 -49
  37. package/dist/src/peer-routing/index.js.map +1 -1
  38. package/dist/src/providers.d.ts +4 -4
  39. package/dist/src/providers.d.ts.map +1 -1
  40. package/dist/src/providers.js +12 -12
  41. package/dist/src/providers.js.map +1 -1
  42. package/dist/src/query/events.d.ts +15 -3
  43. package/dist/src/query/events.d.ts.map +1 -1
  44. package/dist/src/query/events.js +9 -0
  45. package/dist/src/query/events.js.map +1 -1
  46. package/dist/src/query/manager.d.ts +3 -5
  47. package/dist/src/query/manager.d.ts.map +1 -1
  48. package/dist/src/query/manager.js +41 -16
  49. package/dist/src/query/manager.js.map +1 -1
  50. package/dist/src/query/query-path.d.ts +10 -14
  51. package/dist/src/query/query-path.d.ts.map +1 -1
  52. package/dist/src/query/query-path.js +67 -53
  53. package/dist/src/query/query-path.js.map +1 -1
  54. package/dist/src/query/types.d.ts +6 -5
  55. package/dist/src/query/types.d.ts.map +1 -1
  56. package/dist/src/query-self.d.ts +2 -3
  57. package/dist/src/query-self.d.ts.map +1 -1
  58. package/dist/src/query-self.js +11 -14
  59. package/dist/src/query-self.js.map +1 -1
  60. package/dist/src/routing-table/closest-peers.js +1 -1
  61. package/dist/src/routing-table/closest-peers.js.map +1 -1
  62. package/dist/src/routing-table/k-bucket.js +1 -1
  63. package/dist/src/routing-table/k-bucket.js.map +1 -1
  64. package/dist/src/rpc/handlers/add-provider.d.ts +1 -1
  65. package/dist/src/rpc/handlers/add-provider.d.ts.map +1 -1
  66. package/dist/src/rpc/handlers/get-providers.js +1 -1
  67. package/dist/src/rpc/handlers/get-providers.js.map +1 -1
  68. package/dist/src/rpc/index.d.ts +7 -7
  69. package/dist/src/rpc/index.d.ts.map +1 -1
  70. package/dist/src/rpc/index.js +1 -1
  71. package/dist/src/rpc/index.js.map +1 -1
  72. package/dist/src/topology-listener.d.ts +2 -2
  73. package/dist/src/topology-listener.d.ts.map +1 -1
  74. package/dist/src/utils.d.ts +1 -0
  75. package/dist/src/utils.d.ts.map +1 -1
  76. package/dist/src/utils.js +4 -0
  77. package/dist/src/utils.js.map +1 -1
  78. package/package.json +30 -39
  79. package/src/constants.ts +1 -1
  80. package/src/content-fetching/index.ts +41 -14
  81. package/src/content-routing/index.ts +93 -45
  82. package/src/index.ts +65 -6
  83. package/src/kad-dht.ts +14 -10
  84. package/src/message/dht.ts +2 -7
  85. package/src/network.ts +36 -22
  86. package/src/peer-distance-list.ts +19 -7
  87. package/src/peer-routing/index.ts +84 -53
  88. package/src/providers.ts +13 -13
  89. package/src/query/events.ts +27 -3
  90. package/src/query/manager.ts +48 -22
  91. package/src/query/query-path.ts +86 -76
  92. package/src/query/types.ts +7 -5
  93. package/src/query-self.ts +15 -17
  94. package/src/routing-table/closest-peers.ts +1 -1
  95. package/src/routing-table/k-bucket.ts +1 -1
  96. package/src/rpc/handlers/add-provider.ts +1 -1
  97. package/src/rpc/handlers/get-providers.ts +1 -1
  98. package/src/rpc/index.ts +12 -8
  99. package/src/topology-listener.ts +2 -2
  100. package/src/utils.ts +9 -0
  101. package/dist/typedoc-urls.json +0 -59
@@ -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
  }
@@ -175,17 +190,18 @@ export class PeerRouting {
175
190
  let foundPeer = false
176
191
 
177
192
  if (options.useNetwork !== false) {
178
- const self = this // eslint-disable-line @typescript-eslint/no-this-alias
193
+ const self = this
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
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
-
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
259
 
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,8 @@
1
1
  import { setMaxListeners } from '@libp2p/interface'
2
- import { PeerSet } from '@libp2p/peer-collections'
2
+ import { createScalableCuckooFilter } from '@libp2p/utils/filters'
3
3
  import { anySignal } from 'any-signal'
4
4
  import merge from 'it-merge'
5
+ import { pEvent } from 'p-event'
5
6
  import { raceSignal } from 'race-signal'
6
7
  import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
7
8
  import {
@@ -17,7 +18,7 @@ import type { ConnectionManager } from '@libp2p/interface-internal'
17
18
  import type { DeferredPromise } from 'p-defer'
18
19
 
19
20
  export interface CleanUpEvents {
20
- 'cleanup': CustomEvent
21
+ cleanup: CustomEvent
21
22
  }
22
23
 
23
24
  export interface QueryManagerInit {
@@ -26,6 +27,7 @@ export interface QueryManagerInit {
26
27
  disjointPaths?: number
27
28
  alpha?: number
28
29
  initialQuerySelfHasRun: DeferredPromise<void>
30
+ allowQueryWithZeroPeers?: boolean
29
31
  routingTable: RoutingTable
30
32
  }
31
33
 
@@ -37,11 +39,6 @@ export interface QueryManagerComponents {
37
39
  }
38
40
 
39
41
  export interface QueryOptions extends RoutingOptions {
40
- /**
41
- * A timeout for subqueries executed as part of the main query
42
- */
43
- queryFuncTimeout?: number
44
-
45
42
  isSelfQuery?: boolean
46
43
  }
47
44
 
@@ -59,6 +56,7 @@ export class QueryManager implements Startable {
59
56
  private readonly routingTable: RoutingTable
60
57
  private initialQuerySelfHasRun?: DeferredPromise<void>
61
58
  private readonly logPrefix: string
59
+ private readonly allowQueryWithZeroPeers: boolean
62
60
 
63
61
  constructor (components: QueryManagerComponents, init: QueryManagerInit) {
64
62
  this.logPrefix = init.logPrefix
@@ -69,6 +67,7 @@ export class QueryManager implements Startable {
69
67
  this.logger = components.logger
70
68
  this.peerId = components.peerId
71
69
  this.connectionManager = components.connectionManager
70
+ this.allowQueryWithZeroPeers = init.allowQueryWithZeroPeers ?? false
72
71
 
73
72
  // allow us to stop queries on shut down
74
73
  this.shutDownController = new AbortController()
@@ -146,8 +145,18 @@ export class QueryManager implements Startable {
146
145
  let queryFinished = false
147
146
 
148
147
  try {
148
+ if (this.routingTable.size === 0 && !this.allowQueryWithZeroPeers) {
149
+ log('routing table was empty, waiting for some peers before running%s query', options.isSelfQuery === true ? ' self' : '')
150
+ // wait to discover at least one DHT peer that isn't us
151
+ await pEvent(this.routingTable, 'peer:add', {
152
+ signal,
153
+ filter: (event) => !this.peerId.equals(event.detail)
154
+ })
155
+ log('routing table has peers, continuing with%s query', options.isSelfQuery === true ? ' self' : '')
156
+ }
157
+
149
158
  if (options.isSelfQuery !== true && this.initialQuerySelfHasRun != null) {
150
- log('waiting for initial query-self query before continuing')
159
+ log('waiting for initial self query before continuing')
151
160
 
152
161
  await raceSignal(this.initialQuerySelfHasRun.promise, signal)
153
162
 
@@ -157,30 +166,43 @@ export class QueryManager implements Startable {
157
166
  log('query:start')
158
167
 
159
168
  const id = await convertBuffer(key)
160
- const peers = this.routingTable.closestPeers(id)
161
- const peersToQuery = peers.slice(0, Math.min(this.disjointPaths, peers.length))
169
+ const peers = this.routingTable.closestPeers(id, this.routingTable.kBucketSize)
170
+
171
+ // split peers into d buckets evenly(ish)
172
+ const peersToQuery = peers.sort(() => {
173
+ if (Math.random() > 0.5) {
174
+ return 1
175
+ }
176
+
177
+ return -1
178
+ })
179
+ .reduce((acc: PeerId[][], curr, index) => {
180
+ acc[index % this.disjointPaths].push(curr)
181
+
182
+ return acc
183
+ }, new Array(this.disjointPaths).fill(0).map(() => []))
184
+ .filter(peers => peers.length > 0)
162
185
 
163
186
  if (peers.length === 0) {
164
- log.error('Running query with no peers')
187
+ log.error('running query with no peers')
165
188
  return
166
189
  }
167
190
 
168
191
  // make sure we don't get trapped in a loop
169
- const peersSeen = new PeerSet()
192
+ const peersSeen = createScalableCuckooFilter(1024)
170
193
 
171
194
  // Create query paths from the starting peers
172
195
  const paths = peersToQuery.map((peer, index) => {
173
196
  return queryPath({
174
197
  ...options,
175
198
  key,
176
- startingPeer: peer,
199
+ startingPeers: peer,
177
200
  ourPeerId: this.peerId,
178
201
  signal,
179
202
  query: queryFunc,
180
- pathIndex: index,
203
+ path: index,
181
204
  numPaths: peersToQuery.length,
182
205
  alpha: this.alpha,
183
- queryFuncTimeout: options.queryFuncTimeout,
184
206
  log,
185
207
  peersSeen,
186
208
  onProgress: options.onProgress,
@@ -197,22 +219,26 @@ export class QueryManager implements Startable {
197
219
  if (event.name === 'PEER_RESPONSE') {
198
220
  for (const peer of [...event.closer, ...event.providers]) {
199
221
  // eslint-disable-next-line max-depth
200
- if (!(await this.connectionManager.isDialable(peer.multiaddrs))) {
222
+ if (!(await this.connectionManager.isDialable(peer.multiaddrs, {
223
+ signal
224
+ }))) {
201
225
  continue
202
226
  }
203
227
 
204
- await this.routingTable.add(peer.id)
228
+ await this.routingTable.add(peer.id, {
229
+ signal
230
+ })
205
231
  }
206
232
  }
207
233
 
234
+ signal.throwIfAborted()
208
235
  yield event
209
236
  }
210
237
 
211
238
  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 {
239
+ } catch (err) {
240
+ if (this.running) {
241
+ // ignore errors thrown during shut down
216
242
  throw err
217
243
  }
218
244
  } finally {
@@ -223,7 +249,7 @@ export class QueryManager implements Startable {
223
249
 
224
250
  signal.clear()
225
251
 
226
- log('query:done')
252
+ log('query finished')
227
253
  }
228
254
  }
229
255
  }