@libp2p/kad-dht 13.1.1 → 13.1.2-27b2fa6b6

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 (63) hide show
  1. package/dist/index.min.js +2 -2
  2. package/dist/src/index.d.ts +28 -6
  3. package/dist/src/index.d.ts.map +1 -1
  4. package/dist/src/index.js.map +1 -1
  5. package/dist/src/kad-dht.d.ts.map +1 -1
  6. package/dist/src/kad-dht.js +25 -11
  7. package/dist/src/kad-dht.js.map +1 -1
  8. package/dist/src/network.d.ts +2 -1
  9. package/dist/src/network.d.ts.map +1 -1
  10. package/dist/src/network.js +10 -3
  11. package/dist/src/network.js.map +1 -1
  12. package/dist/src/peer-distance-list.d.ts.map +1 -0
  13. package/dist/src/{peer-list/peer-distance-list.js → peer-distance-list.js} +1 -1
  14. package/dist/src/peer-distance-list.js.map +1 -0
  15. package/dist/src/peer-routing/index.js +1 -1
  16. package/dist/src/peer-routing/index.js.map +1 -1
  17. package/dist/src/query/manager.d.ts +1 -2
  18. package/dist/src/query/manager.d.ts.map +1 -1
  19. package/dist/src/query/manager.js +11 -16
  20. package/dist/src/query/manager.js.map +1 -1
  21. package/dist/src/query-self.d.ts.map +1 -1
  22. package/dist/src/query-self.js +13 -6
  23. package/dist/src/query-self.js.map +1 -1
  24. package/dist/src/routing-table/closest-peers.d.ts +43 -0
  25. package/dist/src/routing-table/closest-peers.d.ts.map +1 -0
  26. package/dist/src/routing-table/closest-peers.js +86 -0
  27. package/dist/src/routing-table/closest-peers.js.map +1 -0
  28. package/dist/src/routing-table/index.d.ts +55 -32
  29. package/dist/src/routing-table/index.d.ts.map +1 -1
  30. package/dist/src/routing-table/index.js +261 -158
  31. package/dist/src/routing-table/index.js.map +1 -1
  32. package/dist/src/routing-table/k-bucket.d.ts +65 -21
  33. package/dist/src/routing-table/k-bucket.d.ts.map +1 -1
  34. package/dist/src/routing-table/k-bucket.js +122 -66
  35. package/dist/src/routing-table/k-bucket.js.map +1 -1
  36. package/dist/src/routing-table/refresh.d.ts.map +1 -1
  37. package/dist/src/routing-table/refresh.js +4 -1
  38. package/dist/src/routing-table/refresh.js.map +1 -1
  39. package/dist/src/rpc/index.d.ts.map +1 -1
  40. package/dist/src/rpc/index.js +3 -7
  41. package/dist/src/rpc/index.js.map +1 -1
  42. package/package.json +11 -12
  43. package/src/index.ts +32 -6
  44. package/src/kad-dht.ts +29 -13
  45. package/src/network.ts +18 -5
  46. package/src/{peer-list/peer-distance-list.ts → peer-distance-list.ts} +1 -1
  47. package/src/peer-routing/index.ts +1 -1
  48. package/src/query/manager.ts +16 -21
  49. package/src/query-self.ts +14 -6
  50. package/src/routing-table/closest-peers.ts +113 -0
  51. package/src/routing-table/index.ts +302 -189
  52. package/src/routing-table/k-bucket.ts +194 -81
  53. package/src/routing-table/refresh.ts +5 -1
  54. package/src/rpc/index.ts +4 -7
  55. package/dist/src/peer-list/index.d.ts +0 -29
  56. package/dist/src/peer-list/index.d.ts.map +0 -1
  57. package/dist/src/peer-list/index.js +0 -45
  58. package/dist/src/peer-list/index.js.map +0 -1
  59. package/dist/src/peer-list/peer-distance-list.d.ts.map +0 -1
  60. package/dist/src/peer-list/peer-distance-list.js.map +0 -1
  61. package/dist/typedoc-urls.json +0 -56
  62. package/src/peer-list/index.ts +0 -54
  63. /package/dist/src/{peer-list/peer-distance-list.d.ts → peer-distance-list.d.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@libp2p/kad-dht",
3
- "version": "13.1.1",
3
+ "version": "13.1.2-27b2fa6b6",
4
4
  "description": "JavaScript implementation of the Kad-DHT for libp2p",
5
5
  "license": "Apache-2.0 OR MIT",
6
6
  "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/kad-dht#readme",
@@ -57,13 +57,13 @@
57
57
  "doc-check": "aegir doc-check"
58
58
  },
59
59
  "dependencies": {
60
- "@libp2p/crypto": "^5.0.4",
61
- "@libp2p/interface": "^2.1.2",
62
- "@libp2p/interface-internal": "^2.0.6",
63
- "@libp2p/peer-collections": "^6.0.6",
64
- "@libp2p/peer-id": "^5.0.4",
65
- "@libp2p/record": "^4.0.4",
66
- "@libp2p/utils": "^6.0.6",
60
+ "@libp2p/crypto": "5.0.4-27b2fa6b6",
61
+ "@libp2p/interface": "2.1.2-27b2fa6b6",
62
+ "@libp2p/interface-internal": "2.0.6-27b2fa6b6",
63
+ "@libp2p/peer-collections": "6.0.6-27b2fa6b6",
64
+ "@libp2p/peer-id": "5.0.4-27b2fa6b6",
65
+ "@libp2p/record": "4.0.4-27b2fa6b6",
66
+ "@libp2p/utils": "6.0.6-27b2fa6b6",
67
67
  "@multiformats/multiaddr": "^12.2.3",
68
68
  "any-signal": "^4.1.1",
69
69
  "hashlru": "^2.3.0",
@@ -89,9 +89,9 @@
89
89
  "uint8arrays": "^5.1.0"
90
90
  },
91
91
  "devDependencies": {
92
- "@libp2p/interface-compliance-tests": "^6.1.4",
93
- "@libp2p/logger": "^5.1.0",
94
- "@libp2p/peer-store": "^11.0.6",
92
+ "@libp2p/interface-compliance-tests": "6.1.4-27b2fa6b6",
93
+ "@libp2p/logger": "5.1.0-27b2fa6b6",
94
+ "@libp2p/peer-store": "11.0.6-27b2fa6b6",
95
95
  "@types/lodash.random": "^3.2.9",
96
96
  "@types/lodash.range": "^3.2.9",
97
97
  "@types/sinon": "^17.0.3",
@@ -109,7 +109,6 @@
109
109
  "lodash.random": "^3.2.0",
110
110
  "lodash.range": "^3.2.0",
111
111
  "p-retry": "^6.2.0",
112
- "p-wait-for": "^5.0.2",
113
112
  "protons": "^7.5.0",
114
113
  "sinon": "^18.0.0",
115
114
  "sinon-ts": "^2.0.0",
package/src/index.ts CHANGED
@@ -397,12 +397,10 @@ export interface KadDHTInit {
397
397
  logPrefix?: string
398
398
 
399
399
  /**
400
- * How long to wait in ms when pinging DHT peers to decide if they
401
- * should be evicted from the routing table or not.
402
- *
403
- * @default 10000
400
+ * Settings for how long to wait in ms when pinging DHT peers to decide if
401
+ * they should be evicted from the routing table or not.
404
402
  */
405
- pingTimeout?: number
403
+ pingOldContactTimeout?: Omit<AdaptiveTimeoutInit, 'metricsName' | 'metrics'>
406
404
 
407
405
  /**
408
406
  * How many peers to ping in parallel when deciding if they should
@@ -410,7 +408,35 @@ export interface KadDHTInit {
410
408
  *
411
409
  * @default 10
412
410
  */
413
- pingConcurrency?: number
411
+ pingOldContactConcurrency?: number
412
+
413
+ /**
414
+ * How long the queue to ping peers is allowed to grow
415
+ *
416
+ * @default 100
417
+ */
418
+ pingOldContactMaxQueueSize?: number
419
+
420
+ /**
421
+ * Settings for how long to wait in ms when pinging DHT peers to decide if
422
+ * they should be added to the routing table or not.
423
+ */
424
+ pingNewContactTimeout?: Omit<AdaptiveTimeoutInit, 'metricsName' | 'metrics'>
425
+
426
+ /**
427
+ * How many peers to ping in parallel when deciding if they should be added to
428
+ * the routing table or not
429
+ *
430
+ * @default 10
431
+ */
432
+ pingNewContactConcurrency?: number
433
+
434
+ /**
435
+ * How long the queue to ping peers is allowed to grow
436
+ *
437
+ * @default 100
438
+ */
439
+ pingNewContactMaxQueueSize?: number
414
440
 
415
441
  /**
416
442
  * How many parallel incoming streams to allow on the DHT protocol per
package/src/kad-dht.ts CHANGED
@@ -138,8 +138,6 @@ export class KadDHT extends TypedEventEmitter<PeerDiscoveryEvents> implements Ka
138
138
  querySelfInterval,
139
139
  protocol,
140
140
  logPrefix,
141
- pingTimeout,
142
- pingConcurrency,
143
141
  maxInboundStreams,
144
142
  maxOutboundStreams,
145
143
  providers: providersInit
@@ -156,13 +154,6 @@ export class KadDHT extends TypedEventEmitter<PeerDiscoveryEvents> implements Ka
156
154
  this.maxInboundStreams = maxInboundStreams ?? DEFAULT_MAX_INBOUND_STREAMS
157
155
  this.maxOutboundStreams = maxOutboundStreams ?? DEFAULT_MAX_OUTBOUND_STREAMS
158
156
  this.peerInfoMapper = init.peerInfoMapper ?? removePrivateAddressesMapper
159
- this.routingTable = new RoutingTable(components, {
160
- kBucketSize,
161
- pingTimeout,
162
- pingConcurrency,
163
- protocol: this.protocol,
164
- logPrefix: loggingPrefix
165
- })
166
157
 
167
158
  this.providers = new Providers(components, providersInit ?? {})
168
159
 
@@ -179,6 +170,21 @@ export class KadDHT extends TypedEventEmitter<PeerDiscoveryEvents> implements Ka
179
170
  logPrefix: loggingPrefix
180
171
  })
181
172
 
173
+ this.routingTable = new RoutingTable(components, {
174
+ kBucketSize,
175
+ pingOldContactTimeout: init.pingOldContactTimeout,
176
+ pingOldContactConcurrency: init.pingOldContactConcurrency,
177
+ pingOldContactMaxQueueSize: init.pingOldContactMaxQueueSize,
178
+ pingNewContactTimeout: init.pingNewContactTimeout,
179
+ pingNewContactConcurrency: init.pingNewContactConcurrency,
180
+ pingNewContactMaxQueueSize: init.pingNewContactMaxQueueSize,
181
+ protocol: this.protocol,
182
+ logPrefix: loggingPrefix,
183
+ prefixLength: init.prefixLength,
184
+ splitThreshold: init.kBucketSplitThreshold,
185
+ network: this.network
186
+ })
187
+
182
188
  // all queries should wait for the initial query-self query to run so we have
183
189
  // some peers and don't force consumers to use arbitrary timeouts
184
190
  const initialQuerySelfHasRun = pDefer<any>()
@@ -374,11 +380,17 @@ export class KadDHT extends TypedEventEmitter<PeerDiscoveryEvents> implements Ka
374
380
 
375
381
  await this.components.registrar.unhandle(this.protocol)
376
382
 
383
+ // check again after async work
384
+ if (mode === this.getMode() && !force) {
385
+ this.log('already in %s mode', mode)
386
+ return
387
+ }
388
+
377
389
  if (mode === 'client') {
378
- this.log('enabling client mode')
390
+ this.log('enabling client mode while in %s mode', this.getMode())
379
391
  this.clientMode = true
380
392
  } else {
381
- this.log('enabling server mode')
393
+ this.log('enabling server mode while in %s mode', this.getMode())
382
394
  this.clientMode = false
383
395
  await this.components.registrar.handle(this.protocol, this.rpc.onIncomingStream.bind(this.rpc), {
384
396
  maxInboundStreams: this.maxInboundStreams,
@@ -397,14 +409,18 @@ export class KadDHT extends TypedEventEmitter<PeerDiscoveryEvents> implements Ka
397
409
  await this.setMode(this.clientMode ? 'client' : 'server', true)
398
410
 
399
411
  await start(
400
- this.querySelf,
412
+ this.routingTable,
401
413
  this.providers,
402
414
  this.queryManager,
403
415
  this.network,
404
- this.routingTable,
405
416
  this.topologyListener,
406
417
  this.routingTableRefresh
407
418
  )
419
+
420
+ // Query self after other components are configured
421
+ await start(
422
+ this.querySelf
423
+ )
408
424
  }
409
425
 
410
426
  /**
package/src/network.ts CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  queryErrorEvent
12
12
  } from './query/events.js'
13
13
  import type { KadDHTComponents, QueryEvent } from './index.js'
14
- import type { AbortOptions, Logger, Stream, PeerId, PeerInfo, Startable, RoutingOptions } from '@libp2p/interface'
14
+ import type { AbortOptions, Logger, Stream, PeerId, PeerInfo, Startable, RoutingOptions, CounterGroup } from '@libp2p/interface'
15
15
 
16
16
  export interface NetworkInit {
17
17
  protocol: string
@@ -32,6 +32,10 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
32
32
  private running: boolean
33
33
  private readonly components: KadDHTComponents
34
34
  private readonly timeout: AdaptiveTimeout
35
+ private readonly metrics: {
36
+ operations?: CounterGroup
37
+ errors?: CounterGroup
38
+ }
35
39
 
36
40
  /**
37
41
  * Create a new network
@@ -49,6 +53,10 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
49
53
  metrics: components.metrics,
50
54
  metricName: `${init.logPrefix.replaceAll(':', '_')}_network_message_send_times_milliseconds`
51
55
  })
56
+ this.metrics = {
57
+ operations: components.metrics?.registerCounterGroup(`${init.logPrefix.replaceAll(':', '_')}_outbound_rpc_requests_total`),
58
+ errors: components.metrics?.registerCounterGroup(`${init.logPrefix.replaceAll(':', '_')}_outbound_rpc_errors_total`)
59
+ }
52
60
  }
53
61
 
54
62
  /**
@@ -77,7 +85,7 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
77
85
  }
78
86
 
79
87
  /**
80
- * Send a request and record RTT for latency measurements
88
+ * Send a request and read a response
81
89
  */
82
90
  async * sendRequest (to: PeerId, msg: Partial<Message>, options: RoutingOptions = {}): AsyncGenerator<QueryEvent> {
83
91
  if (!this.running) {
@@ -103,6 +111,8 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
103
111
  }
104
112
 
105
113
  try {
114
+ this.metrics.operations?.increment({ [type]: true })
115
+
106
116
  const connection = await this.components.connectionManager.openConnection(to, options)
107
117
  stream = await connection.newStream(this.protocol, options)
108
118
  const response = await this._writeReadMessage(stream, msg, options)
@@ -121,6 +131,8 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
121
131
  record: response.record == null ? undefined : Libp2pRecord.deserialize(response.record)
122
132
  }, options)
123
133
  } catch (err: any) {
134
+ this.metrics.errors?.increment({ [type]: true })
135
+
124
136
  stream?.abort(err)
125
137
 
126
138
  // only log if the incoming signal was not aborted - this means we were
@@ -162,6 +174,8 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
162
174
  }
163
175
 
164
176
  try {
177
+ this.metrics.operations?.increment({ [type]: true })
178
+
165
179
  const connection = await this.components.connectionManager.openConnection(to, options)
166
180
  stream = await connection.newStream(this.protocol, options)
167
181
 
@@ -175,6 +189,8 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
175
189
 
176
190
  yield peerResponseEvent({ from: to, messageType: type }, options)
177
191
  } catch (err: any) {
192
+ this.metrics.errors?.increment({ [type]: true })
193
+
178
194
  stream?.abort(err)
179
195
  yield queryErrorEvent({ from: to, error: err }, options)
180
196
  } finally {
@@ -188,7 +204,6 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
188
204
  async _writeMessage (stream: Stream, msg: Partial<Message>, options: AbortOptions): Promise<void> {
189
205
  const pb = pbStream(stream)
190
206
  await pb.write(msg, Message, options)
191
- await pb.unwrap().close(options)
192
207
  }
193
208
 
194
209
  /**
@@ -203,8 +218,6 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
203
218
 
204
219
  const message = await pb.read(Message, options)
205
220
 
206
- await pb.unwrap().close(options)
207
-
208
221
  // tell any listeners about new peers we've seen
209
222
  message.closer.forEach(peerData => {
210
223
  this.safeDispatchEvent<PeerInfo>('peer', {
@@ -1,6 +1,6 @@
1
1
  import { xor as uint8ArrayXor } from 'uint8arrays/xor'
2
2
  import { xorCompare as uint8ArrayXorCompare } from 'uint8arrays/xor-compare'
3
- import { convertPeerId } from '../utils.js'
3
+ import { convertPeerId } from './utils.js'
4
4
  import type { PeerId, PeerInfo } from '@libp2p/interface'
5
5
 
6
6
  interface PeerDistance {
@@ -5,7 +5,7 @@ import { Libp2pRecord } from '@libp2p/record'
5
5
  import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
6
6
  import { QueryError, InvalidRecordError } from '../errors.js'
7
7
  import { MessageType } from '../message/dht.js'
8
- import { PeerDistanceList } from '../peer-list/peer-distance-list.js'
8
+ import { PeerDistanceList } from '../peer-distance-list.js'
9
9
  import {
10
10
  queryErrorEvent,
11
11
  finalPeerEvent,
@@ -12,7 +12,7 @@ import { queryPath } from './query-path.js'
12
12
  import type { QueryFunc } from './types.js'
13
13
  import type { QueryEvent } from '../index.js'
14
14
  import type { RoutingTable } from '../routing-table/index.js'
15
- import type { ComponentLogger, Metric, Metrics, PeerId, RoutingOptions, Startable } from '@libp2p/interface'
15
+ import type { ComponentLogger, Counter, Metric, Metrics, PeerId, RoutingOptions, Startable } from '@libp2p/interface'
16
16
  import type { ConnectionManager } from '@libp2p/interface-internal'
17
17
  import type { DeferredPromise } from 'p-defer'
18
18
 
@@ -52,16 +52,16 @@ export class QueryManager implements Startable {
52
52
  private readonly alpha: number
53
53
  private shutDownController: AbortController
54
54
  private running: boolean
55
- private queries: number
56
55
  private readonly logger: ComponentLogger
57
56
  private readonly peerId: PeerId
58
57
  private readonly connectionManager: ConnectionManager
59
58
  private readonly routingTable: RoutingTable
60
59
  private initialQuerySelfHasRun?: DeferredPromise<void>
61
60
  private readonly logPrefix: string
62
- private readonly metrics?: {
63
- runningQueries: Metric
64
- queryTime: Metric
61
+ private readonly metrics: {
62
+ queries?: Counter
63
+ errors?: Counter
64
+ queryTime?: Metric
65
65
  }
66
66
 
67
67
  constructor (components: QueryManagerComponents, init: QueryManagerInit) {
@@ -71,18 +71,16 @@ export class QueryManager implements Startable {
71
71
  this.disjointPaths = disjointPaths ?? K
72
72
  this.running = false
73
73
  this.alpha = alpha ?? ALPHA
74
- this.queries = 0
75
74
  this.initialQuerySelfHasRun = init.initialQuerySelfHasRun
76
75
  this.routingTable = init.routingTable
77
76
  this.logger = components.logger
78
77
  this.peerId = components.peerId
79
78
  this.connectionManager = components.connectionManager
80
79
 
81
- if (components.metrics != null) {
82
- this.metrics = {
83
- runningQueries: components.metrics.registerMetric(`${logPrefix.replaceAll(':', '_')}_running_queries`),
84
- queryTime: components.metrics.registerMetric(`${logPrefix.replaceAll(':', '_')}_query_time_seconds`)
85
- }
80
+ this.metrics = {
81
+ queries: components.metrics?.registerCounter(`${logPrefix.replaceAll(':', '_')}_queries_total`),
82
+ errors: components.metrics?.registerCounter(`${logPrefix.replaceAll(':', '_')}_query_errors_total`),
83
+ queryTime: components.metrics?.registerMetric(`${logPrefix.replaceAll(':', '_')}_query_time_seconds`)
86
84
  }
87
85
 
88
86
  // allow us to stop queries on shut down
@@ -121,7 +119,7 @@ export class QueryManager implements Startable {
121
119
  throw new Error('QueryManager not started')
122
120
  }
123
121
 
124
- const stopQueryTimer = this.metrics?.queryTime.timer()
122
+ const stopQueryTimer = this.metrics.queryTime?.timer()
125
123
 
126
124
  if (options.signal == null) {
127
125
  // don't let queries run forever
@@ -167,8 +165,7 @@ export class QueryManager implements Startable {
167
165
  }
168
166
 
169
167
  log('query:start')
170
- this.queries++
171
- this.metrics?.runningQueries.update(this.queries)
168
+ this.metrics?.queries?.increment()
172
169
 
173
170
  const id = await convertBuffer(key)
174
171
  const peers = this.routingTable.closestPeers(id)
@@ -223,6 +220,10 @@ export class QueryManager implements Startable {
223
220
 
224
221
  queryFinished = true
225
222
  } catch (err: any) {
223
+ if (!queryFinished) {
224
+ this.metrics?.errors?.increment()
225
+ }
226
+
226
227
  if (!this.running && err.name === 'QueryAbortedError') {
227
228
  // ignore query aborted errors that were thrown during query manager shutdown
228
229
  } else {
@@ -236,13 +237,7 @@ export class QueryManager implements Startable {
236
237
 
237
238
  signal.clear()
238
239
 
239
- this.queries--
240
- this.metrics?.runningQueries.update(this.queries)
241
-
242
- if (stopQueryTimer != null) {
243
- stopQueryTimer()
244
- }
245
-
240
+ stopQueryTimer?.()
246
241
  log('query:done in %dms', Date.now() - startTime)
247
242
  }
248
243
  }
package/src/query-self.ts CHANGED
@@ -106,19 +106,27 @@ export class QuerySelf implements Startable {
106
106
 
107
107
  if (this.started) {
108
108
  this.controller = new AbortController()
109
- const timeoutSignal = AbortSignal.timeout(this.queryTimeout)
110
- const signal = anySignal([this.controller.signal, timeoutSignal])
109
+ const signals = [this.controller.signal]
111
110
 
112
- // this controller will get used for lots of dial attempts so make sure we don't cause warnings to be logged
113
- setMaxListeners(Infinity, signal, this.controller.signal, timeoutSignal)
111
+ // add a shorter timeout if we've already run our initial self query
112
+ if (this.initialQuerySelfHasRun == null) {
113
+ const timeoutSignal = AbortSignal.timeout(this.queryTimeout)
114
+ setMaxListeners(Infinity, timeoutSignal)
115
+ signals.push(timeoutSignal)
116
+ }
117
+
118
+ const signal = anySignal(signals)
119
+ setMaxListeners(Infinity, signal, this.controller.signal)
114
120
 
115
121
  try {
116
122
  if (this.routingTable.size === 0) {
117
123
  this.log('routing table was empty, waiting for some peers before running query')
118
- // wait to discover at least one DHT peer
124
+ // wait to discover at least one DHT peer that isn't us
119
125
  await pEvent(this.routingTable, 'peer:add', {
120
- signal
126
+ signal,
127
+ filter: (event) => !this.peerId.equals(event.detail)
121
128
  })
129
+ this.log('routing table has peers, continuing with query')
122
130
  }
123
131
 
124
132
  this.log('run self-query, look for %d peers timing out after %dms', this.count, this.queryTimeout)
@@ -0,0 +1,113 @@
1
+ import { KEEP_ALIVE } from '@libp2p/interface'
2
+ import { PeerSet } from '@libp2p/peer-collections'
3
+ import { PeerDistanceList } from '../peer-distance-list.js'
4
+ import { convertPeerId } from '../utils.js'
5
+ import type { RoutingTable } from './index.js'
6
+ import type { ComponentLogger, Logger, Metrics, PeerId, PeerStore, Startable } from '@libp2p/interface'
7
+
8
+ export const PEER_SET_SIZE = 20
9
+ export const REFRESH_INTERVAL = 5000
10
+ export const KAD_CLOSE_TAG_NAME = 'kad-close'
11
+ export const KAD_CLOSE_TAG_VALUE = 50
12
+
13
+ export interface ClosestPeersInit {
14
+ logPrefix: string
15
+ routingTable: RoutingTable
16
+ peerSetSize?: number
17
+ refreshInterval?: number
18
+ closeTagName?: string
19
+ closeTagValue?: number
20
+ }
21
+
22
+ export interface ClosestPeersComponents {
23
+ peerId: PeerId
24
+ peerStore: PeerStore
25
+ metrics?: Metrics
26
+ logger: ComponentLogger
27
+ }
28
+
29
+ /**
30
+ * Contains a list of the kad-closest peers encountered on the network.
31
+ *
32
+ * Once every few seconds, if the list has changed, it tags the closest peers.
33
+ */
34
+ export class ClosestPeers implements Startable {
35
+ private readonly routingTable: RoutingTable
36
+ private readonly components: ClosestPeersComponents
37
+ private closestPeers: PeerSet
38
+ private newPeers?: PeerDistanceList
39
+ private readonly refreshInterval: number
40
+ private readonly peerSetSize: number
41
+ private timeout?: ReturnType<typeof setTimeout>
42
+ private readonly closeTagName: string
43
+ private readonly closeTagValue: number
44
+ private readonly log: Logger
45
+
46
+ constructor (components: ClosestPeersComponents, init: ClosestPeersInit) {
47
+ this.components = components
48
+ this.log = components.logger.forComponent(`${init.logPrefix}:routing-table`)
49
+ this.routingTable = init.routingTable
50
+ this.refreshInterval = init.refreshInterval ?? REFRESH_INTERVAL
51
+ this.peerSetSize = init.peerSetSize ?? PEER_SET_SIZE
52
+ this.closeTagName = init.closeTagName ?? KAD_CLOSE_TAG_NAME
53
+ this.closeTagValue = init.closeTagValue ?? KAD_CLOSE_TAG_VALUE
54
+
55
+ this.closestPeers = new PeerSet()
56
+ this.onPeerPing = this.onPeerPing.bind(this)
57
+ }
58
+
59
+ async start (): Promise<void> {
60
+ const targetKadId = await convertPeerId(this.components.peerId)
61
+ this.newPeers = new PeerDistanceList(targetKadId, this.peerSetSize)
62
+ this.routingTable.addEventListener('peer:ping', this.onPeerPing)
63
+
64
+ this.timeout = setInterval(() => {
65
+ this.updatePeerTags()
66
+ .catch(err => {
67
+ this.log.error('error updating peer tags - %e', err)
68
+ })
69
+ }, this.refreshInterval)
70
+ }
71
+
72
+ stop (): void {
73
+ this.routingTable.removeEventListener('peer:ping', this.onPeerPing)
74
+ clearTimeout(this.timeout)
75
+ }
76
+
77
+ onPeerPing (event: CustomEvent<PeerId>): void {
78
+ this.newPeers?.add({ id: event.detail, multiaddrs: [] })
79
+ .catch(err => {
80
+ this.log.error('error adding peer to distance list - %e', err)
81
+ })
82
+ }
83
+
84
+ async updatePeerTags (): Promise<void> {
85
+ const newClosest = new PeerSet(this.newPeers?.peers.map(peer => peer.id))
86
+ const added = newClosest.difference(this.closestPeers)
87
+ const removed = this.closestPeers.difference(newClosest)
88
+ this.closestPeers = newClosest
89
+
90
+ await Promise.all([
91
+ ...[...added].map(async peerId => {
92
+ await this.components.peerStore.merge(peerId, {
93
+ tags: {
94
+ [this.closeTagName]: {
95
+ value: this.closeTagValue
96
+ },
97
+ [KEEP_ALIVE]: {
98
+ value: 1
99
+ }
100
+ }
101
+ })
102
+ }),
103
+ ...[...removed].map(async peerId => {
104
+ await this.components.peerStore.merge(peerId, {
105
+ tags: {
106
+ [this.closeTagName]: undefined,
107
+ [KEEP_ALIVE]: undefined
108
+ }
109
+ })
110
+ })
111
+ ])
112
+ }
113
+ }