@libp2p/kad-dht 13.1.2 → 14.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) 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 +1 -1
  9. package/dist/src/network.d.ts.map +1 -1
  10. package/dist/src/network.js +1 -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-self.d.ts.map +1 -1
  18. package/dist/src/query-self.js +13 -6
  19. package/dist/src/query-self.js.map +1 -1
  20. package/dist/src/routing-table/closest-peers.d.ts +43 -0
  21. package/dist/src/routing-table/closest-peers.d.ts.map +1 -0
  22. package/dist/src/routing-table/closest-peers.js +86 -0
  23. package/dist/src/routing-table/closest-peers.js.map +1 -0
  24. package/dist/src/routing-table/index.d.ts +55 -32
  25. package/dist/src/routing-table/index.d.ts.map +1 -1
  26. package/dist/src/routing-table/index.js +259 -161
  27. package/dist/src/routing-table/index.js.map +1 -1
  28. package/dist/src/routing-table/k-bucket.d.ts +65 -21
  29. package/dist/src/routing-table/k-bucket.d.ts.map +1 -1
  30. package/dist/src/routing-table/k-bucket.js +122 -66
  31. package/dist/src/routing-table/k-bucket.js.map +1 -1
  32. package/dist/src/routing-table/refresh.d.ts.map +1 -1
  33. package/dist/src/routing-table/refresh.js +4 -1
  34. package/dist/src/routing-table/refresh.js.map +1 -1
  35. package/dist/src/rpc/index.d.ts.map +1 -1
  36. package/dist/src/rpc/index.js +3 -7
  37. package/dist/src/rpc/index.js.map +1 -1
  38. package/package.json +6 -7
  39. package/src/index.ts +32 -6
  40. package/src/kad-dht.ts +29 -13
  41. package/src/network.ts +1 -4
  42. package/src/{peer-list/peer-distance-list.ts → peer-distance-list.ts} +1 -1
  43. package/src/peer-routing/index.ts +1 -1
  44. package/src/query-self.ts +14 -6
  45. package/src/routing-table/closest-peers.ts +113 -0
  46. package/src/routing-table/index.ts +300 -194
  47. package/src/routing-table/k-bucket.ts +194 -81
  48. package/src/routing-table/refresh.ts +5 -1
  49. package/src/rpc/index.ts +4 -7
  50. package/dist/src/peer-list/index.d.ts +0 -29
  51. package/dist/src/peer-list/index.d.ts.map +0 -1
  52. package/dist/src/peer-list/index.js +0 -45
  53. package/dist/src/peer-list/index.js.map +0 -1
  54. package/dist/src/peer-list/peer-distance-list.d.ts.map +0 -1
  55. package/dist/src/peer-list/peer-distance-list.js.map +0 -1
  56. package/src/peer-list/index.ts +0 -54
  57. /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",
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",
@@ -59,11 +59,11 @@
59
59
  "dependencies": {
60
60
  "@libp2p/crypto": "^5.0.4",
61
61
  "@libp2p/interface": "^2.1.2",
62
- "@libp2p/interface-internal": "^2.0.6",
63
- "@libp2p/peer-collections": "^6.0.6",
62
+ "@libp2p/interface-internal": "^2.0.7",
63
+ "@libp2p/peer-collections": "^6.0.7",
64
64
  "@libp2p/peer-id": "^5.0.4",
65
65
  "@libp2p/record": "^4.0.4",
66
- "@libp2p/utils": "^6.0.6",
66
+ "@libp2p/utils": "^6.1.0",
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",
92
+ "@libp2p/interface-compliance-tests": "^6.1.5",
93
93
  "@libp2p/logger": "^5.1.0",
94
- "@libp2p/peer-store": "^11.0.6",
94
+ "@libp2p/peer-store": "^11.0.7",
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
@@ -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 { 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
+ }