@libp2p/kad-dht 12.0.1 → 12.0.2

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.
package/src/kad-dht.ts CHANGED
@@ -1,7 +1,5 @@
1
1
  import { CodeError, CustomEvent, TypedEventEmitter, contentRoutingSymbol, peerDiscoverySymbol, peerRoutingSymbol } from '@libp2p/interface'
2
2
  import drain from 'it-drain'
3
- import map from 'it-map'
4
- import parallel from 'it-parallel'
5
3
  import pDefer from 'p-defer'
6
4
  import { PROTOCOL } from './constants.js'
7
5
  import { ContentFetching } from './content-fetching/index.js'
@@ -22,42 +20,17 @@ import {
22
20
  removePrivateAddressesMapper
23
21
  } from './utils.js'
24
22
  import type { KadDHTComponents, KadDHTInit, Validators, Selectors, KadDHT as KadDHTInterface, QueryEvent, PeerInfoMapper } from './index.js'
25
- import type { AbortOptions, ContentRouting, Logger, PeerDiscovery, PeerDiscoveryEvents, PeerId, PeerInfo, PeerRouting, RoutingOptions, Startable } from '@libp2p/interface'
23
+ import type { ContentRouting, Logger, PeerDiscovery, PeerDiscoveryEvents, PeerId, PeerInfo, PeerRouting, RoutingOptions, Startable } from '@libp2p/interface'
26
24
  import type { CID } from 'multiformats/cid'
27
25
 
28
- async function * ensurePeerInfoHasMultiaddrs (source: AsyncGenerator<PeerInfo>, peerRouting: PeerRouting, log: Logger, options: AbortOptions = {}): AsyncGenerator<() => Promise<PeerInfo | undefined>, void, undefined> {
29
- yield * map(source, prov => {
30
- return async () => {
31
- if (prov.multiaddrs.length > 0) {
32
- return prov
33
- }
34
-
35
- try {
36
- return await peerRouting.findPeer(prov.id, {
37
- ...options,
38
- useCache: false
39
- })
40
- } catch (err) {
41
- log.error('could not find peer', err)
42
- }
43
- }
44
- })
45
- }
46
-
47
26
  /**
48
27
  * Wrapper class to convert events into returned values
49
28
  */
50
29
  class DHTContentRouting implements ContentRouting {
51
30
  private readonly dht: KadDHTInterface
52
- private readonly peerInfoMapper: PeerInfoMapper
53
- private readonly peerRouting: PeerRouting
54
- private readonly log: Logger
55
31
 
56
- constructor (dht: KadDHTInterface, peerInfoMapper: PeerInfoMapper, peerRouting: PeerRouting, log: Logger) {
32
+ constructor (dht: KadDHTInterface) {
57
33
  this.dht = dht
58
- this.peerInfoMapper = peerInfoMapper
59
- this.peerRouting = peerRouting
60
- this.log = log
61
34
  }
62
35
 
63
36
  async provide (cid: CID, options: RoutingOptions = {}): Promise<void> {
@@ -65,27 +38,11 @@ class DHTContentRouting implements ContentRouting {
65
38
  }
66
39
 
67
40
  async * findProviders (cid: CID, options: RoutingOptions = {}): AsyncGenerator<PeerInfo, void, undefined> {
68
- const self = this
69
- const source = async function * (): AsyncGenerator<PeerInfo, void, undefined> {
70
- for await (const event of self.dht.findProviders(cid, options)) {
71
- if (event.name === 'PROVIDER') {
72
- yield * event.providers
73
- }
41
+ for await (const event of this.dht.findProviders(cid, options)) {
42
+ if (event.name === 'PROVIDER') {
43
+ yield * event.providers
74
44
  }
75
45
  }
76
- for await (let peerInfo of parallel(ensurePeerInfoHasMultiaddrs(source(), this.peerRouting, this.log, options))) {
77
- if (peerInfo == null) {
78
- continue
79
- }
80
-
81
- peerInfo = this.peerInfoMapper(peerInfo)
82
-
83
- if (peerInfo.multiaddrs.length === 0) {
84
- continue
85
- }
86
-
87
- yield peerInfo
88
- }
89
46
  }
90
47
 
91
48
  async put (key: Uint8Array, value: Uint8Array, options?: RoutingOptions): Promise<void> {
@@ -108,23 +65,15 @@ class DHTContentRouting implements ContentRouting {
108
65
  */
109
66
  class DHTPeerRouting implements PeerRouting {
110
67
  private readonly dht: KadDHTInterface
111
- private readonly peerInfoMapper: PeerInfoMapper
112
- private readonly log: Logger
113
68
 
114
- constructor (dht: KadDHTInterface, peerInfoMapper: PeerInfoMapper, log: Logger) {
69
+ constructor (dht: KadDHTInterface) {
115
70
  this.dht = dht
116
- this.peerInfoMapper = peerInfoMapper
117
- this.log = log
118
71
  }
119
72
 
120
73
  async findPeer (peerId: PeerId, options: RoutingOptions = {}): Promise<PeerInfo> {
121
74
  for await (const event of this.dht.findPeer(peerId, options)) {
122
75
  if (event.name === 'FINAL_PEER') {
123
- const peer = this.peerInfoMapper(event.peer)
124
-
125
- if (peer.multiaddrs.length > 0) {
126
- return event.peer
127
- }
76
+ return event.peer
128
77
  }
129
78
  }
130
79
 
@@ -132,27 +81,10 @@ class DHTPeerRouting implements PeerRouting {
132
81
  }
133
82
 
134
83
  async * getClosestPeers (key: Uint8Array, options: RoutingOptions = {}): AsyncIterable<PeerInfo> {
135
- const self = this
136
- const source = async function * (): AsyncGenerator<PeerInfo, void, undefined> {
137
- for await (const event of self.dht.getClosestPeers(key, options)) {
138
- if (event.name === 'FINAL_PEER') {
139
- yield event.peer
140
- }
141
- }
142
- }
143
-
144
- for await (let peerInfo of parallel(ensurePeerInfoHasMultiaddrs(source(), this, this.log, options))) {
145
- if (peerInfo == null) {
146
- continue
147
- }
148
-
149
- peerInfo = this.peerInfoMapper(peerInfo)
150
-
151
- if (peerInfo.multiaddrs.length === 0) {
152
- continue
84
+ for await (const event of this.dht.getClosestPeers(key, options)) {
85
+ if (event.name === 'FINAL_PEER') {
86
+ yield event.peer
153
87
  }
154
-
155
- yield peerInfo
156
88
  }
157
89
  }
158
90
  }
@@ -347,8 +279,8 @@ export class KadDHT extends TypedEventEmitter<PeerDiscoveryEvents> implements Ka
347
279
  })
348
280
  })
349
281
 
350
- this.dhtPeerRouting = new DHTPeerRouting(this, this.peerInfoMapper, this.log)
351
- this.dhtContentRouting = new DHTContentRouting(this, this.peerInfoMapper, this.dhtPeerRouting, this.log)
282
+ this.dhtPeerRouting = new DHTPeerRouting(this)
283
+ this.dhtContentRouting = new DHTContentRouting(this)
352
284
 
353
285
  // if client mode has not been explicitly specified, auto-switch to server
354
286
  // mode when the node's peer data is updated with publicly dialable
@@ -1,7 +1,8 @@
1
- import { AbortError, TypedEventEmitter, CustomEvent, setMaxListeners } from '@libp2p/interface'
1
+ import { TypedEventEmitter, CustomEvent, setMaxListeners } from '@libp2p/interface'
2
2
  import { PeerSet } from '@libp2p/peer-collections'
3
3
  import { anySignal } from 'any-signal'
4
4
  import merge from 'it-merge'
5
+ import { raceSignal } from 'race-signal'
5
6
  import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
6
7
  import {
7
8
  ALPHA, K, DEFAULT_QUERY_TIMEOUT
@@ -127,7 +128,16 @@ export class QueryManager implements Startable {
127
128
  }
128
129
  }
129
130
 
130
- const signal = anySignal([this.shutDownController.signal, options.signal])
131
+ // if the user breaks out of a for..await of loop iterating over query
132
+ // results we need to cancel any in-flight network requests
133
+ const queryEarlyExitController = new AbortController()
134
+ setMaxListeners(Infinity, queryEarlyExitController.signal)
135
+
136
+ const signal = anySignal([
137
+ this.shutDownController.signal,
138
+ queryEarlyExitController.signal,
139
+ options.signal
140
+ ])
131
141
 
132
142
  // this signal will get listened to for every invocation of queryFunc
133
143
  // so make sure we don't make a lot of noise in the logs
@@ -138,19 +148,13 @@ export class QueryManager implements Startable {
138
148
  // query a subset of peers up to `kBucketSize / 2` in length
139
149
  const startTime = Date.now()
140
150
  const cleanUp = new TypedEventEmitter<CleanUpEvents>()
151
+ let queryFinished = false
141
152
 
142
153
  try {
143
154
  if (options.isSelfQuery !== true && this.initialQuerySelfHasRun != null) {
144
155
  log('waiting for initial query-self query before continuing')
145
156
 
146
- await Promise.race([
147
- new Promise((resolve, reject) => {
148
- signal.addEventListener('abort', () => {
149
- reject(new AbortError('Query was aborted before self-query ran'))
150
- })
151
- }),
152
- this.initialQuerySelfHasRun.promise
153
- ])
157
+ await raceSignal(this.initialQuerySelfHasRun.promise, signal)
154
158
 
155
159
  this.initialQuerySelfHasRun = undefined
156
160
  }
@@ -192,12 +196,14 @@ export class QueryManager implements Startable {
192
196
 
193
197
  // Execute the query along each disjoint path and yield their results as they become available
194
198
  for await (const event of merge(...paths)) {
195
- yield event
196
-
197
199
  if (event.name === 'QUERY_ERROR') {
198
- log('error', event.error)
200
+ log.error('query error', event.error)
199
201
  }
202
+
203
+ yield event
200
204
  }
205
+
206
+ queryFinished = true
201
207
  } catch (err: any) {
202
208
  if (!this.running && err.code === 'ERR_QUERY_ABORTED') {
203
209
  // ignore query aborted errors that were thrown during query manager shutdown
@@ -205,6 +211,11 @@ export class QueryManager implements Startable {
205
211
  throw err
206
212
  }
207
213
  } finally {
214
+ if (!queryFinished) {
215
+ log('query exited early')
216
+ queryEarlyExitController.abort()
217
+ }
218
+
208
219
  signal.clear()
209
220
 
210
221
  this.queries--
@@ -1,6 +1,6 @@
1
1
  import { CodeError, TypedEventEmitter } from '@libp2p/interface'
2
2
  import { PeerSet } from '@libp2p/peer-collections'
3
- import { PeerJobQueue } from '@libp2p/utils/peer-job-queue'
3
+ import { PeerQueue } from '@libp2p/utils/peer-queue'
4
4
  import { pbStream } from 'it-protobuf-stream'
5
5
  import { Message, MessageType } from '../message/dht.js'
6
6
  import * as utils from '../utils.js'
@@ -44,7 +44,7 @@ export interface RoutingTableEvents {
44
44
  export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implements Startable {
45
45
  public kBucketSize: number
46
46
  public kb?: KBucket
47
- public pingQueue: PeerJobQueue
47
+ public pingQueue: PeerQueue<boolean>
48
48
 
49
49
  private readonly log: Logger
50
50
  private readonly components: RoutingTableComponents
@@ -56,8 +56,6 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
56
56
  private readonly tagValue: number
57
57
  private readonly metrics?: {
58
58
  routingTableSize: Metric
59
- pingQueueSize: Metric
60
- pingRunning: Metric
61
59
  }
62
60
 
63
61
  constructor (components: RoutingTableComponents, init: RoutingTableInit) {
@@ -75,23 +73,18 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
75
73
  this.tagName = tagName ?? KAD_CLOSE_TAG_NAME
76
74
  this.tagValue = tagValue ?? KAD_CLOSE_TAG_VALUE
77
75
 
78
- const updatePingQueueSizeMetric = (): void => {
79
- this.metrics?.pingQueueSize.update(this.pingQueue.size)
80
- this.metrics?.pingRunning.update(this.pingQueue.pending)
81
- }
82
-
83
- this.pingQueue = new PeerJobQueue({ concurrency: this.pingConcurrency })
84
- this.pingQueue.addListener('add', updatePingQueueSizeMetric)
85
- this.pingQueue.addListener('next', updatePingQueueSizeMetric)
86
- this.pingQueue.addListener('error', err => {
87
- this.log.error('error pinging peer', err)
76
+ this.pingQueue = new PeerQueue({
77
+ concurrency: this.pingConcurrency,
78
+ metricName: `${logPrefix.replaceAll(':', '_')}_ping_queue`,
79
+ metrics: this.components.metrics
80
+ })
81
+ this.pingQueue.addEventListener('error', evt => {
82
+ this.log.error('error pinging peer', evt.detail)
88
83
  })
89
84
 
90
85
  if (this.components.metrics != null) {
91
86
  this.metrics = {
92
- routingTableSize: this.components.metrics.registerMetric(`${logPrefix.replaceAll(':', '_')}_routing_table_size`),
93
- pingQueueSize: this.components.metrics.registerMetric(`${logPrefix.replaceAll(':', '_')}_ping_queue_size`),
94
- pingRunning: this.components.metrics.registerMetric(`${logPrefix.replaceAll(':', '_')}_ping_running`)
87
+ routingTableSize: this.components.metrics.registerMetric(`${logPrefix.replaceAll(':', '_')}_routing_table_size`)
95
88
  }
96
89
  }
97
90
  }
@@ -204,8 +197,10 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
204
197
  const results = await Promise.all(
205
198
  oldContacts.map(async oldContact => {
206
199
  // if a previous ping wants us to ping this contact, re-use the result
207
- if (this.pingQueue.hasJob(oldContact.peer)) {
208
- return this.pingQueue.joinJob(oldContact.peer)
200
+ const pingJob = this.pingQueue.find(oldContact.peer)
201
+
202
+ if (pingJob != null) {
203
+ return pingJob.join()
209
204
  }
210
205
 
211
206
  return this.pingQueue.add(async () => {