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

Sign up to get free protection for your applications and to get access to all the features.
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
+ }