@libp2p/kad-dht 13.1.2 → 14.0.0-0d326d102

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/constants.d.ts +1 -0
  3. package/dist/src/constants.d.ts.map +1 -1
  4. package/dist/src/constants.js +3 -0
  5. package/dist/src/constants.js.map +1 -1
  6. package/dist/src/index.d.ts +28 -6
  7. package/dist/src/index.d.ts.map +1 -1
  8. package/dist/src/index.js.map +1 -1
  9. package/dist/src/kad-dht.d.ts.map +1 -1
  10. package/dist/src/kad-dht.js +25 -11
  11. package/dist/src/kad-dht.js.map +1 -1
  12. package/dist/src/network.d.ts +1 -1
  13. package/dist/src/network.d.ts.map +1 -1
  14. package/dist/src/network.js +1 -3
  15. package/dist/src/network.js.map +1 -1
  16. package/dist/src/peer-distance-list.d.ts.map +1 -0
  17. package/dist/src/{peer-list/peer-distance-list.js → peer-distance-list.js} +1 -1
  18. package/dist/src/peer-distance-list.js.map +1 -0
  19. package/dist/src/peer-routing/index.js +1 -1
  20. package/dist/src/peer-routing/index.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 +259 -161
  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/constants.ts +5 -0
  44. package/src/index.ts +32 -6
  45. package/src/kad-dht.ts +29 -13
  46. package/src/network.ts +1 -4
  47. package/src/{peer-list/peer-distance-list.ts → peer-distance-list.ts} +1 -1
  48. package/src/peer-routing/index.ts +1 -1
  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 +300 -194
  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.2",
3
+ "version": "14.0.0-0d326d102",
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-0d326d102",
61
+ "@libp2p/interface": "2.1.2-0d326d102",
62
+ "@libp2p/interface-internal": "2.0.7-0d326d102",
63
+ "@libp2p/peer-collections": "6.0.7-0d326d102",
64
+ "@libp2p/peer-id": "5.0.4-0d326d102",
65
+ "@libp2p/record": "4.0.4-0d326d102",
66
+ "@libp2p/utils": "6.1.0-0d326d102",
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.5-0d326d102",
93
+ "@libp2p/logger": "5.1.0-0d326d102",
94
+ "@libp2p/peer-store": "11.0.7-0d326d102",
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/constants.ts CHANGED
@@ -5,6 +5,8 @@
5
5
  // until the year 2020 (a great time in the future). For that record to stick around
6
6
  // it must be rebroadcasted more frequently than once every 'MaxRecordAge'
7
7
 
8
+ import { KEEP_ALIVE } from '@libp2p/interface'
9
+
8
10
  export const second = 1000
9
11
  export const minute = 60 * second
10
12
  export const hour = 60 * minute
@@ -51,3 +53,6 @@ export const TABLE_REFRESH_QUERY_TIMEOUT = 30 * second
51
53
 
52
54
  // When a timeout is not specified, run a query for this long
53
55
  export const DEFAULT_QUERY_TIMEOUT = 180 * second
56
+
57
+ // used to ensure connections to our closest peers remain open
58
+ export const KEEP_ALIVE_TAG = `${KEEP_ALIVE}-kad-dht`
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
@@ -85,7 +85,7 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
85
85
  }
86
86
 
87
87
  /**
88
- * Send a request and record RTT for latency measurements
88
+ * Send a request and read a response
89
89
  */
90
90
  async * sendRequest (to: PeerId, msg: Partial<Message>, options: RoutingOptions = {}): AsyncGenerator<QueryEvent> {
91
91
  if (!this.running) {
@@ -204,7 +204,6 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
204
204
  async _writeMessage (stream: Stream, msg: Partial<Message>, options: AbortOptions): Promise<void> {
205
205
  const pb = pbStream(stream)
206
206
  await pb.write(msg, Message, options)
207
- await pb.unwrap().close(options)
208
207
  }
209
208
 
210
209
  /**
@@ -219,8 +218,6 @@ export class Network extends TypedEventEmitter<NetworkEvents> implements Startab
219
218
 
220
219
  const message = await pb.read(Message, options)
221
220
 
222
- await pb.unwrap().close(options)
223
-
224
221
  // tell any listeners about new peers we've seen
225
222
  message.closer.forEach(peerData => {
226
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,
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 { PeerSet } from '@libp2p/peer-collections'
2
+ import { KEEP_ALIVE_TAG } from '../constants.js'
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_TAG]: {
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_TAG]: undefined
108
+ }
109
+ })
110
+ })
111
+ ])
112
+ }
113
+ }