@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
@@ -1,7 +1,6 @@
1
1
  import { PeerSet } from '@libp2p/peer-collections'
2
- import map from 'it-map'
3
- import parallel from 'it-parallel'
4
- import { pipe } from 'it-pipe'
2
+ import { Queue } from '@libp2p/utils/queue'
3
+ import { pushable } from 'it-pushable'
5
4
  import { ALPHA } from '../constants.js'
6
5
  import { MessageType } from '../message/dht.js'
7
6
  import { toPbPeerInfo } from '../message/utils.js'
@@ -10,7 +9,7 @@ import {
10
9
  peerResponseEvent,
11
10
  providerEvent
12
11
  } from '../query/events.js'
13
- import type { KadDHTComponents, PeerResponseEvent, ProviderEvent, QueryEvent } from '../index.js'
12
+ import type { FinalPeerEvent, KadDHTComponents, PeerResponseEvent, ProviderEvent, QueryEvent } from '../index.js'
14
13
  import type { Message } from '../message/dht.js'
15
14
  import type { Network } from '../network.js'
16
15
  import type { PeerRouting } from '../peer-routing/index.js'
@@ -98,51 +97,76 @@ export class ContentRouting {
98
97
  }
99
98
 
100
99
  let sent = 0
100
+ const self = this
101
+
102
+ async function * publishProviderRecord (event: FinalPeerEvent): AsyncGenerator<QueryEvent, void, undefined> {
103
+ try {
104
+ self.log('sending provider record for %s to %p', key, event.peer.id)
105
+
106
+ for await (const addProviderEvent of self.network.sendMessage(event.peer.id, msg, {
107
+ ...options,
108
+ path: event.path
109
+ })) {
110
+ if (addProviderEvent.name === 'PEER_RESPONSE') {
111
+ self.log('sent provider record for %s to %p', key, event.peer.id)
112
+ sent++
113
+ }
101
114
 
102
- const maybeNotifyPeer = (event: QueryEvent) => {
103
- return async () => {
104
- if (event.name !== 'FINAL_PEER') {
105
- return [event]
115
+ yield addProviderEvent
106
116
  }
117
+ } catch (err: any) {
118
+ self.log.error('error sending provide record to peer %p', event.peer.id, err)
119
+ yield queryErrorEvent({
120
+ from: event.peer.id,
121
+ error: err,
122
+ path: event.path
123
+ }, options)
124
+ }
125
+ }
107
126
 
108
- const events = []
127
+ const events = pushable<QueryEvent>({
128
+ objectMode: true
129
+ })
109
130
 
110
- this.log('putProvider %s to %p', key, event.peer.id)
131
+ const queue = new Queue({
132
+ concurrency: ALPHA
133
+ })
134
+ queue.addEventListener('idle', () => {
135
+ events.end()
136
+ })
137
+ queue.addEventListener('error', (err) => {
138
+ this.log.error('error publishing provider record to peer - %e', err)
139
+ })
111
140
 
112
- try {
113
- this.log('sending provider record for %s to %p', key, event.peer.id)
141
+ queue.add(async () => {
142
+ const finalPeerEvents: FinalPeerEvent[] = []
114
143
 
115
- for await (const sendEvent of this.network.sendMessage(event.peer.id, msg, options)) {
116
- if (sendEvent.name === 'PEER_RESPONSE') {
117
- this.log('sent provider record for %s to %p', key, event.peer.id)
118
- sent++
119
- }
144
+ for await (const event of this.peerRouting.getClosestPeers(target, options)) {
145
+ events.push(event)
120
146
 
121
- events.push(sendEvent)
122
- }
123
- } catch (err: any) {
124
- this.log.error('error sending provide record to peer %p', event.peer.id, err)
125
- events.push(queryErrorEvent({ from: event.peer.id, error: err }, options))
147
+ if (event.name !== 'FINAL_PEER') {
148
+ continue
126
149
  }
127
150
 
128
- return events
151
+ finalPeerEvents.push(event)
129
152
  }
130
- }
131
153
 
132
- // Notify closest peers
133
- yield * pipe(
134
- this.peerRouting.getClosestPeers(target, options),
135
- (source) => map(source, (event) => maybeNotifyPeer(event)),
136
- (source) => parallel(source, {
137
- ordered: false,
138
- concurrency: ALPHA
139
- }),
140
- async function * (source) {
141
- for await (const events of source) {
142
- yield * events
143
- }
144
- }
145
- )
154
+ finalPeerEvents.forEach(event => {
155
+ queue.add(async () => {
156
+ for await (const notifyEvent of publishProviderRecord(event)) {
157
+ events.push(notifyEvent)
158
+ }
159
+ })
160
+ .catch(err => {
161
+ this.log.error('error publishing provider record to peer - %e', err)
162
+ })
163
+ })
164
+ })
165
+ .catch(err => {
166
+ events.end(err)
167
+ })
168
+
169
+ yield * events
146
170
 
147
171
  this.log('sent provider records to %d peers', sent)
148
172
  }
@@ -154,7 +178,7 @@ export class ContentRouting {
154
178
  const toFind = this.routingTable.kBucketSize
155
179
  let found = 0
156
180
  const target = key.multihash.bytes
157
- const self = this // eslint-disable-line @typescript-eslint/no-this-alias
181
+ const self = this
158
182
 
159
183
  this.log('findProviders %c', key)
160
184
 
@@ -181,8 +205,27 @@ export class ContentRouting {
181
205
  }
182
206
  }
183
207
 
184
- yield peerResponseEvent({ from: this.components.peerId, messageType: MessageType.GET_PROVIDERS, providers }, options)
185
- yield providerEvent({ from: this.components.peerId, providers }, options)
208
+ yield peerResponseEvent({
209
+ from: this.components.peerId,
210
+ messageType: MessageType.GET_PROVIDERS,
211
+ providers,
212
+ path: {
213
+ index: -1,
214
+ queued: 0,
215
+ running: 0,
216
+ total: 0
217
+ }
218
+ }, options)
219
+ yield providerEvent({
220
+ from: this.components.peerId,
221
+ providers,
222
+ path: {
223
+ index: -1,
224
+ queued: 0,
225
+ running: 0,
226
+ total: 0
227
+ }
228
+ }, options)
186
229
 
187
230
  found += providers.length
188
231
 
@@ -194,15 +237,16 @@ export class ContentRouting {
194
237
  /**
195
238
  * The query function to use on this particular disjoint path
196
239
  */
197
- const findProvidersQuery: QueryFunc = async function * ({ peer, signal }) {
240
+ const findProvidersQuery: QueryFunc = async function * ({ peer, signal, path }) {
198
241
  const request = {
199
242
  type: MessageType.GET_PROVIDERS,
200
243
  key: target
201
244
  }
202
245
 
203
- yield * self.network.sendRequest(peer, request, {
246
+ yield * self.network.sendRequest(peer.id, request, {
204
247
  ...options,
205
- signal
248
+ signal,
249
+ path
206
250
  })
207
251
  }
208
252
 
@@ -226,7 +270,11 @@ export class ContentRouting {
226
270
  }
227
271
 
228
272
  if (newProviders.length > 0) {
229
- yield providerEvent({ from: event.from, providers: newProviders }, options)
273
+ yield providerEvent({
274
+ from: event.from,
275
+ providers: newProviders,
276
+ path: event.path
277
+ }, options)
230
278
 
231
279
  found += newProviders.length
232
280
 
package/src/index.ts CHANGED
@@ -138,7 +138,7 @@ import { removePrivateAddressesMapper, removePublicAddressesMapper, passthroughM
138
138
  import type { Libp2pEvents, ComponentLogger, TypedEventTarget, Metrics, PeerId, PeerInfo, PeerStore, RoutingOptions, PrivateKey } from '@libp2p/interface'
139
139
  import type { AddressManager, ConnectionManager, Registrar } from '@libp2p/interface-internal'
140
140
  import type { Ping } from '@libp2p/ping'
141
- import type { AdaptiveTimeoutInit } from '@libp2p/utils/src/adaptive-timeout.js'
141
+ import type { AdaptiveTimeoutInit } from '@libp2p/utils/adaptive-timeout'
142
142
  import type { Datastore } from 'interface-datastore'
143
143
  import type { CID } from 'multiformats/cid'
144
144
  import type { ProgressEvent } from 'progress-events'
@@ -157,7 +157,8 @@ export enum EventTypes {
157
157
  PROVIDER,
158
158
  VALUE,
159
159
  ADD_PEER,
160
- DIAL_PEER
160
+ DIAL_PEER,
161
+ PATH_ENDED
161
162
  }
162
163
 
163
164
  /**
@@ -183,6 +184,13 @@ export type DHTProgressEvents =
183
184
  ProgressEvent<'kad-dht:query:add-peer', AddPeerEvent> |
184
185
  ProgressEvent<'kad-dht:query:dial-peer', DialPeerEvent>
185
186
 
187
+ export interface DisjointPath {
188
+ index: number
189
+ queued: number
190
+ running: number
191
+ total: number
192
+ }
193
+
186
194
  /**
187
195
  * Emitted when sending queries to remote peers
188
196
  */
@@ -192,6 +200,7 @@ export interface SendQueryEvent {
192
200
  name: 'SEND_QUERY'
193
201
  messageName: keyof typeof MessageType
194
202
  messageType: MessageType
203
+ path: DisjointPath
195
204
  }
196
205
 
197
206
  /**
@@ -207,6 +216,7 @@ export interface PeerResponseEvent {
207
216
  closer: PeerInfo[]
208
217
  providers: PeerInfo[]
209
218
  record?: DHTRecord
219
+ path: DisjointPath
210
220
  }
211
221
 
212
222
  /**
@@ -217,6 +227,7 @@ export interface FinalPeerEvent {
217
227
  peer: PeerInfo
218
228
  type: EventTypes.FINAL_PEER
219
229
  name: 'FINAL_PEER'
230
+ path: DisjointPath
220
231
  }
221
232
 
222
233
  /**
@@ -227,6 +238,7 @@ export interface QueryErrorEvent {
227
238
  type: EventTypes.QUERY_ERROR
228
239
  name: 'QUERY_ERROR'
229
240
  error: Error
241
+ path: DisjointPath
230
242
  }
231
243
 
232
244
  /**
@@ -237,6 +249,7 @@ export interface ProviderEvent {
237
249
  type: EventTypes.PROVIDER
238
250
  name: 'PROVIDER'
239
251
  providers: PeerInfo[]
252
+ path: DisjointPath
240
253
  }
241
254
 
242
255
  /**
@@ -247,6 +260,7 @@ export interface ValueEvent {
247
260
  type: EventTypes.VALUE
248
261
  name: 'VALUE'
249
262
  value: Uint8Array
263
+ path: DisjointPath
250
264
  }
251
265
 
252
266
  /**
@@ -256,18 +270,29 @@ export interface AddPeerEvent {
256
270
  type: EventTypes.ADD_PEER
257
271
  name: 'ADD_PEER'
258
272
  peer: PeerId
273
+ path: DisjointPath
259
274
  }
260
275
 
261
276
  /**
262
- * Emitted when peers are dialled as part of a query
277
+ * Emitted when peers are dialled and a new stream is opened as part of a query
263
278
  */
264
279
  export interface DialPeerEvent {
265
280
  peer: PeerId
266
281
  type: EventTypes.DIAL_PEER
267
282
  name: 'DIAL_PEER'
283
+ path: DisjointPath
284
+ }
285
+
286
+ /**
287
+ * Emitted when sending queries to remote peers
288
+ */
289
+ export interface PathEndedEvent {
290
+ type: EventTypes.PATH_ENDED
291
+ name: 'PATH_ENDED'
292
+ path: DisjointPath
268
293
  }
269
294
 
270
- export type QueryEvent = SendQueryEvent | PeerResponseEvent | FinalPeerEvent | QueryErrorEvent | ProviderEvent | ValueEvent | AddPeerEvent | DialPeerEvent
295
+ export type QueryEvent = SendQueryEvent | PeerResponseEvent | FinalPeerEvent | QueryErrorEvent | ProviderEvent | ValueEvent | AddPeerEvent | DialPeerEvent | PathEndedEvent
271
296
 
272
297
  export interface RoutingTable {
273
298
  size: number
@@ -278,6 +303,24 @@ export interface PeerInfoMapper {
278
303
  }
279
304
 
280
305
  export interface KadDHT {
306
+ /**
307
+ * This is the maximum size of the k-buckets and how many peers are looked up
308
+ * when searching for peers close to a key.
309
+ */
310
+ readonly k: number
311
+
312
+ /**
313
+ * Query concurrency factor - this controls how many peers we contact in
314
+ * parallel during a query.
315
+ */
316
+ readonly a: number
317
+
318
+ /**
319
+ * From section 4.4 of the S/Kademlia paper - this is how many disjoint paths
320
+ * are used during a query.
321
+ */
322
+ readonly d: number
323
+
281
324
  /**
282
325
  * Get a value from the DHT, the final ValueEvent will be the best value
283
326
  */
@@ -436,6 +479,22 @@ export interface KadDHTInit {
436
479
  */
437
480
  kBucketSplitThreshold?: number
438
481
 
482
+ /**
483
+ * How many peers are queried in parallel during a query.
484
+ *
485
+ * @default 3
486
+ */
487
+ alpha?: number
488
+
489
+ /**
490
+ * How many disjoint paths are used during a query
491
+ *
492
+ * @see https://telematics.tm.kit.edu/publications/Files/267/SKademlia_2007.pdf - section 4.4
493
+ *
494
+ * @default alpha
495
+ */
496
+ disjointPaths?: number
497
+
439
498
  /**
440
499
  * How many bits of the KAD-ID of peers to use when creating the routing
441
500
  * table.
@@ -506,8 +565,8 @@ export interface KadDHTInit {
506
565
  initialQuerySelfInterval?: number
507
566
 
508
567
  /**
509
- * After startup by default all queries will be paused until the initial
510
- * self-query has run and there are some peers in the routing table.
568
+ * After startup by default all queries will be paused until there is at least
569
+ * one peer in the routing table.
511
570
  *
512
571
  * Pass true here to disable this behavior.
513
572
  *
package/src/kad-dht.ts CHANGED
@@ -2,7 +2,7 @@ import { NotFoundError, TypedEventEmitter, contentRoutingSymbol, peerDiscoverySy
2
2
  import drain from 'it-drain'
3
3
  import createMortice from 'mortice'
4
4
  import pDefer from 'p-defer'
5
- import { PROTOCOL } from './constants.js'
5
+ import { ALPHA, PROTOCOL } from './constants.js'
6
6
  import { ContentFetching } from './content-fetching/index.js'
7
7
  import { ContentRouting as KADDHTContentRouting } from './content-routing/index.js'
8
8
  import { Network } from './network.js'
@@ -13,7 +13,7 @@ import { QuerySelf } from './query-self.js'
13
13
  import { selectors as recordSelectors } from './record/selectors.js'
14
14
  import { validators as recordValidators } from './record/validators.js'
15
15
  import { Reprovider } from './reprovider.js'
16
- import { RoutingTable } from './routing-table/index.js'
16
+ import { KBUCKET_SIZE, RoutingTable } from './routing-table/index.js'
17
17
  import { RoutingTableRefresh } from './routing-table/refresh.js'
18
18
  import { RPC } from './rpc/index.js'
19
19
  import { TopologyListener } from './topology-listener.js'
@@ -113,6 +113,9 @@ export interface OperationMetrics {
113
113
  * Original implementation in go: https://github.com/libp2p/go-libp2p-kad-dht.
114
114
  */
115
115
  export class KadDHT extends TypedEventEmitter<PeerDiscoveryEvents> implements KadDHTInterface, Startable {
116
+ public readonly k: number
117
+ public readonly a: number
118
+ public readonly d: number
116
119
  public protocol: string
117
120
  public routingTable: RoutingTable
118
121
  public providers: Providers
@@ -122,7 +125,6 @@ export class KadDHT extends TypedEventEmitter<PeerDiscoveryEvents> implements Ka
122
125
  public readonly components: KadDHTComponents
123
126
  private readonly log: Logger
124
127
  private running: boolean
125
- private readonly kBucketSize: number
126
128
  private clientMode: boolean
127
129
  private readonly validators: Validators
128
130
  private readonly selectors: Selectors
@@ -161,8 +163,10 @@ export class KadDHT extends TypedEventEmitter<PeerDiscoveryEvents> implements Ka
161
163
  this.running = false
162
164
  this.components = components
163
165
  this.log = components.logger.forComponent(logPrefix)
166
+ this.k = init.kBucketSize ?? KBUCKET_SIZE
167
+ this.a = init.alpha ?? ALPHA
168
+ this.d = init.disjointPaths ?? this.a
164
169
  this.protocol = init.protocol ?? PROTOCOL
165
- this.kBucketSize = init.kBucketSize ?? 20
166
170
  this.clientMode = init.clientMode ?? true
167
171
  this.maxInboundStreams = init.maxInboundStreams ?? DEFAULT_MAX_INBOUND_STREAMS
168
172
  this.maxOutboundStreams = init.maxOutboundStreams ?? DEFAULT_MAX_OUTBOUND_STREAMS
@@ -192,7 +196,7 @@ export class KadDHT extends TypedEventEmitter<PeerDiscoveryEvents> implements Ka
192
196
  })
193
197
 
194
198
  this.routingTable = new RoutingTable(components, {
195
- kBucketSize: init.kBucketSize,
199
+ kBucketSize: this.k,
196
200
  pingOldContactTimeout: init.pingOldContactTimeout,
197
201
  pingOldContactConcurrency: init.pingOldContactConcurrency,
198
202
  pingOldContactMaxQueueSize: init.pingOldContactMaxQueueSize,
@@ -218,12 +222,13 @@ export class KadDHT extends TypedEventEmitter<PeerDiscoveryEvents> implements Ka
218
222
  }
219
223
 
220
224
  this.queryManager = new QueryManager(components, {
221
- // Number of disjoint query paths to use - This is set to `kBucketSize/2` per the S/Kademlia paper
222
- disjointPaths: Math.ceil(this.kBucketSize / 2),
225
+ disjointPaths: this.d,
226
+ alpha: this.a,
223
227
  logPrefix,
224
228
  metricsPrefix,
225
229
  initialQuerySelfHasRun,
226
- routingTable: this.routingTable
230
+ routingTable: this.routingTable,
231
+ allowQueryWithZeroPeers: init.allowQueryWithZeroPeers
227
232
  })
228
233
 
229
234
  // DHT components
@@ -276,7 +281,6 @@ export class KadDHT extends TypedEventEmitter<PeerDiscoveryEvents> implements Ka
276
281
  initialInterval: init.initialQuerySelfInterval,
277
282
  logPrefix,
278
283
  initialQuerySelfHasRun,
279
- routingTable: this.routingTable,
280
284
  operationMetrics
281
285
  })
282
286
  this.reprovider = new Reprovider(components, {
@@ -317,7 +321,7 @@ export class KadDHT extends TypedEventEmitter<PeerDiscoveryEvents> implements Ka
317
321
 
318
322
  await this.onPeerConnect(peerData)
319
323
  }).catch(err => {
320
- this.log.error('could not add %p to routing table - %e', peerId, err)
324
+ this.log.error('could not add %p to routing table - %e - %e', peerId, err)
321
325
  })
322
326
  })
323
327
 
@@ -1,11 +1,6 @@
1
- /* eslint-disable import/export */
2
- /* eslint-disable complexity */
3
- /* eslint-disable @typescript-eslint/no-namespace */
4
- /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
5
- /* eslint-disable @typescript-eslint/no-empty-interface */
6
-
7
- import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, enumeration, MaxLengthError, message } from 'protons-runtime'
1
+ import { decodeMessage, encodeMessage, enumeration, MaxLengthError, message } from 'protons-runtime'
8
2
  import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc'
3
+ import type { Codec, DecodeOptions } from 'protons-runtime'
9
4
  import type { Uint8ArrayList } from 'uint8arraylist'
10
5
 
11
6
  export interface Record {
package/src/network.ts CHANGED
@@ -1,17 +1,18 @@
1
1
  import { InvalidParametersError, TypedEventEmitter } from '@libp2p/interface'
2
2
  import { Libp2pRecord } from '@libp2p/record'
3
- import { AdaptiveTimeout, type AdaptiveTimeoutInit } from '@libp2p/utils/adaptive-timeout'
3
+ import { AdaptiveTimeout } from '@libp2p/utils/adaptive-timeout'
4
4
  import { pbStream } from 'it-protobuf-stream'
5
5
  import { Message } from './message/dht.js'
6
6
  import { fromPbPeerInfo } from './message/utils.js'
7
7
  import {
8
- dialPeerEvent,
9
8
  sendQueryEvent,
10
9
  peerResponseEvent,
11
- queryErrorEvent
10
+ queryErrorEvent,
11
+ dialPeerEvent
12
12
  } from './query/events.js'
13
- import type { KadDHTComponents, QueryEvent } from './index.js'
13
+ import type { DisjointPath, KadDHTComponents, QueryEvent } from './index.js'
14
14
  import type { AbortOptions, Logger, Stream, PeerId, PeerInfo, Startable, RoutingOptions, CounterGroup } from '@libp2p/interface'
15
+ import type { AdaptiveTimeoutInit } from '@libp2p/utils/adaptive-timeout'
15
16
 
16
17
  export interface NetworkInit {
17
18
  protocol: string
@@ -21,7 +22,16 @@ export interface NetworkInit {
21
22
  }
22
23
 
23
24
  interface NetworkEvents {
24
- 'peer': CustomEvent<PeerInfo>
25
+ peer: CustomEvent<PeerInfo>
26
+ }
27
+
28
+ export interface SendMessageOptions extends RoutingOptions {
29
+ /**
30
+ * Queries involve following up to `k` disjoint paths through the network -
31
+ * this option is which index within `k` this message is for, and it
32
+ * allows observers to collate events together on a per-path basis
33
+ */
34
+ path: DisjointPath
25
35
  }
26
36
 
27
37
  /**
@@ -142,7 +152,7 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
142
152
  /**
143
153
  * Send a request and read a response
144
154
  */
145
- async * sendRequest (to: PeerId, msg: Partial<Message>, options: RoutingOptions = {}): AsyncGenerator<QueryEvent> {
155
+ async * sendRequest (to: PeerId, msg: Partial<Message>, options: SendMessageOptions): AsyncGenerator<QueryEvent> {
146
156
  if (!this.running) {
147
157
  return
148
158
  }
@@ -153,10 +163,6 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
153
163
  throw new InvalidParametersError('Message type was missing')
154
164
  }
155
165
 
156
- this.log('sending %s to %p', msg.type, to)
157
- yield dialPeerEvent({ peer: to }, options)
158
- yield sendQueryEvent({ to, type }, options)
159
-
160
166
  let stream: Stream | undefined
161
167
  const signal = this.timeout.getTimeoutSignal(options)
162
168
 
@@ -168,8 +174,15 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
168
174
  try {
169
175
  this.metrics.operations?.increment({ [type]: true })
170
176
 
177
+ this.log('dialling %p', to)
178
+ yield dialPeerEvent({ peer: to, path: options.path }, options)
179
+
171
180
  const connection = await this.components.connectionManager.openConnection(to, options)
172
181
  stream = await connection.newStream(this.protocol, options)
182
+
183
+ this.log('sending %s to %p', msg.type, to)
184
+ yield sendQueryEvent({ to, type, path: options.path }, options)
185
+
173
186
  const response = await this._writeReadMessage(stream, msg, options)
174
187
 
175
188
  stream.close(options)
@@ -183,7 +196,8 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
183
196
  messageType: response.type,
184
197
  closer: response.closer.map(fromPbPeerInfo),
185
198
  providers: response.providers.map(fromPbPeerInfo),
186
- record: response.record == null ? undefined : Libp2pRecord.deserialize(response.record)
199
+ record: response.record == null ? undefined : Libp2pRecord.deserialize(response.record),
200
+ path: options.path
187
201
  }, options)
188
202
  } catch (err: any) {
189
203
  this.metrics.errors?.increment({ [type]: true })
@@ -196,7 +210,7 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
196
210
  this.log.error('could not send %s to %p - %e', msg.type, to, err)
197
211
  }
198
212
 
199
- yield queryErrorEvent({ from: to, error: err }, options)
213
+ yield queryErrorEvent({ from: to, error: err, path: options.path }, options)
200
214
  } finally {
201
215
  this.timeout.cleanUp(signal)
202
216
  }
@@ -205,7 +219,7 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
205
219
  /**
206
220
  * Sends a message without expecting an answer
207
221
  */
208
- async * sendMessage (to: PeerId, msg: Partial<Message>, options: RoutingOptions = {}): AsyncGenerator<QueryEvent> {
222
+ async * sendMessage (to: PeerId, msg: Partial<Message>, options: SendMessageOptions): AsyncGenerator<QueryEvent> {
209
223
  if (!this.running) {
210
224
  return
211
225
  }
@@ -216,10 +230,6 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
216
230
  throw new InvalidParametersError('Message type was missing')
217
231
  }
218
232
 
219
- this.log('sending %s to %p', msg.type, to)
220
- yield dialPeerEvent({ peer: to }, options)
221
- yield sendQueryEvent({ to, type }, options)
222
-
223
233
  let stream: Stream | undefined
224
234
  const signal = this.timeout.getTimeoutSignal(options)
225
235
 
@@ -231,9 +241,15 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
231
241
  try {
232
242
  this.metrics.operations?.increment({ [type]: true })
233
243
 
244
+ this.log('dialling %p', to)
245
+ yield dialPeerEvent({ peer: to, path: options.path }, options)
246
+
234
247
  const connection = await this.components.connectionManager.openConnection(to, options)
235
248
  stream = await connection.newStream(this.protocol, options)
236
249
 
250
+ this.log('sending %s to %p', msg.type, to)
251
+ yield sendQueryEvent({ to, type, path: options.path }, options)
252
+
237
253
  await this._writeMessage(stream, msg, options)
238
254
 
239
255
  stream.close(options)
@@ -242,12 +258,12 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
242
258
  stream?.abort(err)
243
259
  })
244
260
 
245
- yield peerResponseEvent({ from: to, messageType: type }, options)
261
+ yield peerResponseEvent({ from: to, messageType: type, path: options.path }, options)
246
262
  } catch (err: any) {
247
263
  this.metrics.errors?.increment({ [type]: true })
248
264
 
249
265
  stream?.abort(err)
250
- yield queryErrorEvent({ from: to, error: err }, options)
266
+ yield queryErrorEvent({ from: to, error: err, path: options.path }, options)
251
267
  } finally {
252
268
  this.timeout.cleanUp(signal)
253
269
  }
@@ -262,9 +278,7 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
262
278
  }
263
279
 
264
280
  /**
265
- * Write a message and read its response.
266
- * If no response is received after the specified timeout
267
- * this will error out.
281
+ * Write a message and read a response
268
282
  */
269
283
  async _writeReadMessage (stream: Stream, msg: Partial<Message>, options: AbortOptions): Promise<Message> {
270
284
  const pb = pbStream(stream)
@@ -1,11 +1,13 @@
1
1
  import { xor as uint8ArrayXor } from 'uint8arrays/xor'
2
2
  import { xorCompare as uint8ArrayXorCompare } from 'uint8arrays/xor-compare'
3
3
  import { convertPeerId } from './utils.js'
4
+ import type { DisjointPath } from './index.js'
4
5
  import type { PeerId, PeerInfo } from '@libp2p/interface'
5
6
 
6
7
  interface PeerDistance {
7
8
  peer: PeerInfo
8
9
  distance: Uint8Array
10
+ path: DisjointPath
9
11
  }
10
12
 
11
13
  /**
@@ -40,30 +42,40 @@ export class PeerDistanceList {
40
42
  /**
41
43
  * The peers in the list, in order of distance from the origin key
42
44
  */
43
- get peers (): PeerInfo[] {
44
- return this.peerDistances.map(pd => pd.peer)
45
+ get peers (): PeerDistance[] {
46
+ return [...this.peerDistances]
45
47
  }
46
48
 
47
49
  /**
48
50
  * Add a peerId to the list.
49
51
  */
50
- async add (peer: PeerInfo): Promise<void> {
52
+ async add (peer: PeerInfo, path: DisjointPath = { index: -1, queued: 0, running: 0, total: 0 }): Promise<void> {
51
53
  const dhtKey = await convertPeerId(peer.id)
52
54
 
53
- this.addWithKadId(peer, dhtKey)
55
+ this.addWithKadId(peer, dhtKey, path)
54
56
  }
55
57
 
56
58
  /**
57
59
  * Add a peerId to the list.
58
60
  */
59
- addWithKadId (peer: PeerInfo, kadId: Uint8Array): void {
61
+ addWithKadId (peer: PeerInfo, kadId: Uint8Array, path: DisjointPath = { index: -1, queued: 0, running: 0, total: 0 }): void {
60
62
  if (this.peerDistances.find(pd => pd.peer.id.equals(peer.id)) != null) {
61
63
  return
62
64
  }
63
65
 
64
- const el = {
66
+ const el: PeerDistance = {
65
67
  peer,
66
- distance: uint8ArrayXor(this.originDhtKey, kadId)
68
+ distance: uint8ArrayXor(this.originDhtKey, kadId),
69
+ path
70
+ }
71
+
72
+ if (this.peerDistances.length === this.capacity) {
73
+ const lastPeer = this.peerDistances[this.peerDistances.length - 1]
74
+
75
+ // only add if it's closer than our furthest peer
76
+ if (lastPeer != null && uint8ArrayXorCompare(el.distance, lastPeer.distance) !== -1) {
77
+ return
78
+ }
67
79
  }
68
80
 
69
81
  let added = false