@libp2p/kad-dht 15.1.2 → 15.1.3-6c42ea64a

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 (113) hide show
  1. package/dist/index.min.js +2 -2
  2. package/dist/index.min.js.map +4 -4
  3. package/dist/src/constants.d.ts +2 -0
  4. package/dist/src/constants.d.ts.map +1 -1
  5. package/dist/src/constants.js +4 -0
  6. package/dist/src/constants.js.map +1 -1
  7. package/dist/src/content-fetching/index.d.ts +2 -2
  8. package/dist/src/content-fetching/index.d.ts.map +1 -1
  9. package/dist/src/content-fetching/index.js +6 -6
  10. package/dist/src/content-fetching/index.js.map +1 -1
  11. package/dist/src/content-routing/index.js +3 -3
  12. package/dist/src/content-routing/index.js.map +1 -1
  13. package/dist/src/errors.d.ts +0 -6
  14. package/dist/src/errors.d.ts.map +1 -1
  15. package/dist/src/errors.js +0 -9
  16. package/dist/src/errors.js.map +1 -1
  17. package/dist/src/index.d.ts +24 -5
  18. package/dist/src/index.d.ts.map +1 -1
  19. package/dist/src/index.js.map +1 -1
  20. package/dist/src/kad-dht.d.ts +8 -5
  21. package/dist/src/kad-dht.d.ts.map +1 -1
  22. package/dist/src/kad-dht.js +25 -18
  23. package/dist/src/kad-dht.js.map +1 -1
  24. package/dist/src/network.d.ts +1 -1
  25. package/dist/src/network.d.ts.map +1 -1
  26. package/dist/src/network.js +2 -1
  27. package/dist/src/network.js.map +1 -1
  28. package/dist/src/peer-distance-list.d.ts +4 -4
  29. package/dist/src/peer-distance-list.d.ts.map +1 -1
  30. package/dist/src/peer-distance-list.js +6 -6
  31. package/dist/src/peer-distance-list.js.map +1 -1
  32. package/dist/src/peer-routing/index.d.ts +6 -5
  33. package/dist/src/peer-routing/index.d.ts.map +1 -1
  34. package/dist/src/peer-routing/index.js +21 -32
  35. package/dist/src/peer-routing/index.js.map +1 -1
  36. package/dist/src/providers.d.ts +0 -3
  37. package/dist/src/providers.d.ts.map +1 -1
  38. package/dist/src/providers.js +11 -31
  39. package/dist/src/providers.js.map +1 -1
  40. package/dist/src/query/manager.d.ts.map +1 -1
  41. package/dist/src/query/manager.js +7 -3
  42. package/dist/src/query/manager.js.map +1 -1
  43. package/dist/src/query/query-path.d.ts.map +1 -1
  44. package/dist/src/query/query-path.js +11 -5
  45. package/dist/src/query/query-path.js.map +1 -1
  46. package/dist/src/query-self.d.ts.map +1 -1
  47. package/dist/src/query-self.js +2 -1
  48. package/dist/src/query-self.js.map +1 -1
  49. package/dist/src/record/validators.d.ts +2 -1
  50. package/dist/src/record/validators.d.ts.map +1 -1
  51. package/dist/src/record/validators.js +3 -3
  52. package/dist/src/record/validators.js.map +1 -1
  53. package/dist/src/reprovider.d.ts +1 -4
  54. package/dist/src/reprovider.d.ts.map +1 -1
  55. package/dist/src/reprovider.js +13 -13
  56. package/dist/src/reprovider.js.map +1 -1
  57. package/dist/src/routing-table/index.d.ts +6 -5
  58. package/dist/src/routing-table/index.d.ts.map +1 -1
  59. package/dist/src/routing-table/index.js +72 -44
  60. package/dist/src/routing-table/index.js.map +1 -1
  61. package/dist/src/routing-table/k-bucket.d.ts +20 -8
  62. package/dist/src/routing-table/k-bucket.d.ts.map +1 -1
  63. package/dist/src/routing-table/k-bucket.js +35 -16
  64. package/dist/src/routing-table/k-bucket.js.map +1 -1
  65. package/dist/src/routing-table/refresh.d.ts +5 -5
  66. package/dist/src/routing-table/refresh.d.ts.map +1 -1
  67. package/dist/src/routing-table/refresh.js +21 -15
  68. package/dist/src/routing-table/refresh.js.map +1 -1
  69. package/dist/src/rpc/handlers/find-node.d.ts.map +1 -1
  70. package/dist/src/rpc/handlers/find-node.js +40 -24
  71. package/dist/src/rpc/handlers/find-node.js.map +1 -1
  72. package/dist/src/rpc/handlers/get-providers.js +1 -1
  73. package/dist/src/rpc/handlers/get-providers.js.map +1 -1
  74. package/dist/src/rpc/handlers/get-value.js +1 -1
  75. package/dist/src/rpc/handlers/get-value.js.map +1 -1
  76. package/dist/src/rpc/index.d.ts +2 -1
  77. package/dist/src/rpc/index.d.ts.map +1 -1
  78. package/dist/src/rpc/index.js +50 -19
  79. package/dist/src/rpc/index.js.map +1 -1
  80. package/dist/src/topology-listener.d.ts +1 -1
  81. package/dist/src/topology-listener.d.ts.map +1 -1
  82. package/dist/src/topology-listener.js +1 -1
  83. package/dist/src/topology-listener.js.map +1 -1
  84. package/dist/src/utils.d.ts +3 -3
  85. package/dist/src/utils.d.ts.map +1 -1
  86. package/dist/src/utils.js +4 -3
  87. package/dist/src/utils.js.map +1 -1
  88. package/package.json +18 -18
  89. package/src/constants.ts +6 -0
  90. package/src/content-fetching/index.ts +7 -7
  91. package/src/content-routing/index.ts +3 -3
  92. package/src/errors.ts +0 -10
  93. package/src/index.ts +27 -5
  94. package/src/kad-dht.ts +28 -20
  95. package/src/network.ts +2 -1
  96. package/src/peer-distance-list.ts +7 -7
  97. package/src/peer-routing/index.ts +23 -34
  98. package/src/providers.ts +15 -33
  99. package/src/query/manager.ts +7 -3
  100. package/src/query/query-path.ts +11 -5
  101. package/src/query-self.ts +3 -1
  102. package/src/record/validators.ts +4 -3
  103. package/src/reprovider.ts +13 -16
  104. package/src/routing-table/index.ts +74 -45
  105. package/src/routing-table/k-bucket.ts +54 -20
  106. package/src/routing-table/refresh.ts +21 -16
  107. package/src/rpc/handlers/find-node.ts +41 -27
  108. package/src/rpc/handlers/get-providers.ts +1 -1
  109. package/src/rpc/handlers/get-value.ts +1 -1
  110. package/src/rpc/index.ts +54 -23
  111. package/src/topology-listener.ts +1 -1
  112. package/src/utils.ts +5 -4
  113. package/dist/typedoc-urls.json +0 -63
@@ -1,4 +1,4 @@
1
- import { PeerMap } from '@libp2p/peer-collections'
1
+ import { PeerMap, trackedPeerMap } from '@libp2p/peer-collections'
2
2
  import map from 'it-map'
3
3
  import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
4
4
  import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
@@ -6,7 +6,7 @@ import { xor as uint8ArrayXor } from 'uint8arrays/xor'
6
6
  import { PeerDistanceList } from '../peer-distance-list.js'
7
7
  import { convertPeerId } from '../utils.js'
8
8
  import { KBUCKET_SIZE, LAST_PING_THRESHOLD, PING_OLD_CONTACT_COUNT, PREFIX_LENGTH } from './index.js'
9
- import type { PeerId, AbortOptions } from '@libp2p/interface'
9
+ import type { PeerId, AbortOptions, Metrics } from '@libp2p/interface'
10
10
 
11
11
  export interface PingFunction {
12
12
  /**
@@ -28,21 +28,26 @@ export interface OnAddCallback {
28
28
  /**
29
29
  * Invoked when a new peer is added to the routing tables
30
30
  */
31
- (peer: Peer, bucket: LeafBucket): Promise<void>
31
+ (peer: Peer, bucket: LeafBucket, options?: AbortOptions): Promise<void>
32
32
  }
33
33
 
34
34
  export interface OnRemoveCallback {
35
35
  /**
36
36
  * Invoked when a peer is evicted from the routing tables
37
37
  */
38
- (peer: Peer, bucket: LeafBucket): Promise<void>
38
+ (peer: Peer, bucket: LeafBucket, options?: AbortOptions): Promise<void>
39
39
  }
40
40
 
41
41
  export interface OnMoveCallback {
42
42
  /**
43
43
  * Invoked when a peer is moved between buckets in the routing tables
44
44
  */
45
- (peer: Peer, oldBucket: LeafBucket, newBucket: LeafBucket): Promise<void>
45
+ (peer: Peer, oldBucket: LeafBucket, newBucket: LeafBucket, options?: AbortOptions): Promise<void>
46
+ }
47
+
48
+ export interface KBucketComponents {
49
+ peerId: PeerId
50
+ metrics?: Metrics
46
51
  }
47
52
 
48
53
  export interface KBucketOptions {
@@ -97,6 +102,7 @@ export interface KBucketOptions {
97
102
  verify: VerifyFunction
98
103
  onAdd?: OnAddCallback
99
104
  onRemove?: OnRemoveCallback
105
+ metricsPrefix?: string
100
106
  }
101
107
 
102
108
  export interface Peer {
@@ -124,11 +130,17 @@ export function isLeafBucket (obj: any): obj is LeafBucket {
124
130
  return Array.isArray(obj?.peers)
125
131
  }
126
132
 
133
+ export interface GetClosestPeersOptions extends AbortOptions {
134
+ count?: number
135
+ exclude?: PeerId[]
136
+ }
137
+
127
138
  /**
128
139
  * Implementation of a Kademlia DHT routing table as a prefix binary trie with
129
140
  * configurable prefix length, bucket split threshold and size.
130
141
  */
131
142
  export class KBucket {
143
+ private readonly peerId: PeerId
132
144
  public root: Bucket
133
145
  public localPeer?: Peer
134
146
  private readonly prefixLength: number
@@ -143,7 +155,8 @@ export class KBucket {
143
155
  private readonly onMove?: OnMoveCallback
144
156
  private readonly addingPeerMap: PeerMap<Promise<void>>
145
157
 
146
- constructor (options: KBucketOptions) {
158
+ constructor (components: KBucketComponents, options: KBucketOptions) {
159
+ this.peerId = components.peerId
147
160
  this.prefixLength = options.prefixLength ?? PREFIX_LENGTH
148
161
  this.kBucketSize = options.kBucketSize ?? KBUCKET_SIZE
149
162
  this.splitThreshold = options.splitThreshold ?? this.kBucketSize
@@ -153,7 +166,10 @@ export class KBucket {
153
166
  this.verify = options.verify
154
167
  this.onAdd = options.onAdd
155
168
  this.onRemove = options.onRemove
156
- this.addingPeerMap = new PeerMap()
169
+ this.addingPeerMap = trackedPeerMap({
170
+ name: `${options.metricsPrefix}_adding_peer_map`,
171
+ metrics: components.metrics
172
+ })
157
173
 
158
174
  this.root = {
159
175
  prefix: '',
@@ -162,10 +178,24 @@ export class KBucket {
162
178
  }
163
179
  }
164
180
 
165
- async addSelfPeer (peerId: PeerId): Promise<void> {
181
+ async start (): Promise<void> {
182
+ await this.addSelfPeer(this.peerId)
183
+ }
184
+
185
+ stop (): void {
186
+ this.addingPeerMap.clear()
187
+
188
+ this.root = {
189
+ prefix: '',
190
+ depth: 0,
191
+ peers: []
192
+ }
193
+ }
194
+
195
+ async addSelfPeer (peerId: PeerId, options?: AbortOptions): Promise<void> {
166
196
  this.localPeer = {
167
197
  peerId,
168
- kadId: await convertPeerId(peerId),
198
+ kadId: await convertPeerId(peerId, options),
169
199
  lastPing: Date.now()
170
200
  }
171
201
  }
@@ -176,7 +206,7 @@ export class KBucket {
176
206
  async add (peerId: PeerId, options?: AbortOptions): Promise<void> {
177
207
  const peer = {
178
208
  peerId,
179
- kadId: await convertPeerId(peerId),
209
+ kadId: await convertPeerId(peerId, options),
180
210
  lastPing: 0
181
211
  }
182
212
 
@@ -206,7 +236,7 @@ export class KBucket {
206
236
  // are there too many peers in the bucket and can we make the trie deeper?
207
237
  if (bucket.peers.length === this.splitThreshold && bucket.depth < this.prefixLength) {
208
238
  // split the bucket
209
- await this._split(bucket)
239
+ await this._split(bucket, options)
210
240
 
211
241
  // try again
212
242
  await this._add(peer, options)
@@ -219,7 +249,7 @@ export class KBucket {
219
249
  // we've ping this peer previously, just add them to the bucket
220
250
  if (!needsPing(peer, this.lastPingThreshold)) {
221
251
  bucket.peers.push(peer)
222
- await this.onAdd?.(peer, bucket)
252
+ await this.onAdd?.(peer, bucket, options)
223
253
  return
224
254
  }
225
255
 
@@ -274,7 +304,7 @@ export class KBucket {
274
304
 
275
305
  for await (const toEvict of this.ping(toPing, options)) {
276
306
  evicted = true
277
- await this.remove(toEvict.kadId)
307
+ await this.remove(toEvict.kadId, options)
278
308
  }
279
309
 
280
310
  // did not evict any peers, cannot add new contact
@@ -293,10 +323,14 @@ export class KBucket {
293
323
  * @param {Uint8Array} id - Contact node id
294
324
  * @returns {Generator<Peer, void, undefined>} Array Maximum of n closest contacts to the node id
295
325
  */
296
- * closest (id: Uint8Array, n: number = this.kBucketSize): Generator<PeerId, void, undefined> {
297
- const list = new PeerDistanceList(id, n)
326
+ * closest (id: Uint8Array, options?: GetClosestPeersOptions): Generator<PeerId, void, undefined> {
327
+ const list = new PeerDistanceList(id, options?.count ?? this.kBucketSize)
298
328
 
299
329
  for (const peer of this.toIterable()) {
330
+ if (options?.exclude?.some(p => p.equals(peer.peerId)) === true) {
331
+ continue
332
+ }
333
+
300
334
  list.addWithKadId({ id: peer.peerId, multiaddrs: [] }, peer.kadId)
301
335
  }
302
336
 
@@ -351,14 +385,14 @@ export class KBucket {
351
385
  *
352
386
  * @param {Uint8Array} kadId - The ID of the contact to remove
353
387
  */
354
- async remove (kadId: Uint8Array): Promise<void> {
388
+ async remove (kadId: Uint8Array, options?: AbortOptions): Promise<void> {
355
389
  const bucket = this._determineBucket(kadId)
356
390
  const index = this._indexOf(bucket, kadId)
357
391
 
358
392
  if (index > -1) {
359
393
  const peer = bucket.peers.splice(index, 1)[0]
360
394
 
361
- await this.onRemove?.(peer, bucket)
395
+ await this.onRemove?.(peer, bucket, options)
362
396
  }
363
397
  }
364
398
 
@@ -439,7 +473,7 @@ export class KBucket {
439
473
  *
440
474
  * @param {any} bucket - bucket for splitting
441
475
  */
442
- private async _split (bucket: LeafBucket): Promise<void> {
476
+ private async _split (bucket: LeafBucket, options?: AbortOptions): Promise<void> {
443
477
  // create child buckets
444
478
  const left: LeafBucket = {
445
479
  prefix: '0',
@@ -458,10 +492,10 @@ export class KBucket {
458
492
 
459
493
  if (bitString[bucket.depth] === '0') {
460
494
  left.peers.push(peer)
461
- await this.onMove?.(peer, bucket, left)
495
+ await this.onMove?.(peer, bucket, left, options)
462
496
  } else {
463
497
  right.peers.push(peer)
464
- await this.onMove?.(peer, bucket, right)
498
+ await this.onMove?.(peer, bucket, right, options)
465
499
  }
466
500
  }
467
501
 
@@ -1,7 +1,8 @@
1
1
  import { randomBytes } from '@libp2p/crypto'
2
- import { setMaxListeners } from '@libp2p/interface'
3
2
  import { peerIdFromMultihash } from '@libp2p/peer-id'
3
+ import { anySignal } from 'any-signal'
4
4
  import length from 'it-length'
5
+ import { setMaxListeners } from 'main-event'
5
6
  import * as Digest from 'multiformats/hashes/digest'
6
7
  import { sha256 } from 'multiformats/hashes/sha2'
7
8
  import { xor as uint8ArrayXor } from 'uint8arrays/xor'
@@ -9,7 +10,7 @@ import { TABLE_REFRESH_INTERVAL, TABLE_REFRESH_QUERY_TIMEOUT } from '../constant
9
10
  import GENERATED_PREFIXES from './generated-prefix-list.js'
10
11
  import type { RoutingTable } from './index.js'
11
12
  import type { PeerRouting } from '../peer-routing/index.js'
12
- import type { ComponentLogger, Logger, PeerId } from '@libp2p/interface'
13
+ import type { AbortOptions, ComponentLogger, Logger, PeerId } from '@libp2p/interface'
13
14
 
14
15
  /**
15
16
  * Cannot generate random KadIds longer than this + 1
@@ -70,7 +71,7 @@ export class RoutingTableRefresh {
70
71
  * that is close to the requested peer ID and query that, then network
71
72
  * peers will tell us who they know who is close to the fake ID
72
73
  */
73
- refreshTable (force: boolean = false): void {
74
+ refreshTable (force: boolean = false, options?: AbortOptions): void {
74
75
  this.log('refreshing routing table')
75
76
 
76
77
  const prefixLength = this._maxCommonPrefix()
@@ -97,14 +98,14 @@ export class RoutingTableRefresh {
97
98
  Promise.all(
98
99
  refreshCommonPrefixLengths.map(async (lastRefresh, index) => {
99
100
  try {
100
- await this._refreshCommonPrefixLength(index, lastRefresh, force)
101
+ await this._refreshCommonPrefixLength(index, lastRefresh, force, options)
101
102
 
102
103
  if (this._numPeersForCpl(prefixLength) === 0) {
103
104
  const lastCpl = Math.min(2 * (index + 1), refreshCommonPrefixLengths.length - 1)
104
105
 
105
106
  for (let n = index + 1; n < lastCpl + 1; n++) {
106
107
  try {
107
- await this._refreshCommonPrefixLength(n, lastRefresh, force)
108
+ await this._refreshCommonPrefixLength(n, lastRefresh, force, options)
108
109
  } catch (err: any) {
109
110
  this.log.error(err)
110
111
  }
@@ -127,26 +128,30 @@ export class RoutingTableRefresh {
127
128
  })
128
129
  }
129
130
 
130
- async _refreshCommonPrefixLength (cpl: number, lastRefresh: Date, force: boolean): Promise<void> {
131
+ async _refreshCommonPrefixLength (cpl: number, lastRefresh: Date, force: boolean, options?: AbortOptions): Promise<void> {
131
132
  if (!force && lastRefresh.getTime() > (Date.now() - this.refreshInterval)) {
132
133
  this.log('not running refresh for cpl %s as time since last refresh not above interval', cpl)
133
134
  return
134
135
  }
135
136
 
136
137
  // gen a key for the query to refresh the cpl
137
- const peerId = await this._generateRandomPeerId(cpl)
138
+ const peerId = this._generateRandomPeerId(cpl)
138
139
 
139
140
  this.log('starting refreshing cpl %s with key %p (routing table size was %s)', cpl, peerId, this.routingTable.size)
140
141
 
141
- const signal = AbortSignal.timeout(this.refreshQueryTimeout)
142
+ const signal = anySignal([options?.signal, AbortSignal.timeout(this.refreshQueryTimeout)])
142
143
  setMaxListeners(Infinity, signal)
143
144
 
144
- const peers = await length(this.peerRouting.getClosestPeers(peerId.toMultihash().bytes, {
145
- signal
146
- }))
145
+ try {
146
+ const peers = await length(this.peerRouting.getClosestPeers(peerId.toMultihash().bytes, {
147
+ signal
148
+ }))
147
149
 
148
- this.log(`found ${peers} peers that were close to imaginary peer %p`, peerId)
149
- this.log('finished refreshing cpl %s with key %p (routing table size is now %s)', cpl, peerId, this.routingTable.size)
150
+ this.log(`found ${peers} peers that were close to imaginary peer %p`, peerId)
151
+ this.log('finished refreshing cpl %s with key %p (routing table size is now %s)', cpl, peerId, this.routingTable.size)
152
+ } finally {
153
+ signal.clear()
154
+ }
150
155
  }
151
156
 
152
157
  _getTrackedCommonPrefixLengthsForRefresh (maxCommonPrefix: number): Date[] {
@@ -164,7 +169,7 @@ export class RoutingTableRefresh {
164
169
  return dates
165
170
  }
166
171
 
167
- async _generateRandomPeerId (targetCommonPrefixLength: number): Promise<PeerId> {
172
+ _generateRandomPeerId (targetCommonPrefixLength: number): PeerId {
168
173
  if (this.routingTable.kb == null) {
169
174
  throw new Error('Routing table not started')
170
175
  }
@@ -176,13 +181,13 @@ export class RoutingTableRefresh {
176
181
  const randomData = randomBytes(2)
177
182
  const randomUint16 = (randomData[1] << 8) + randomData[0]
178
183
 
179
- const key = await this._makePeerId(this.routingTable.kb.localPeer.kadId, randomUint16, targetCommonPrefixLength)
184
+ const key = this._makePeerId(this.routingTable.kb.localPeer.kadId, randomUint16, targetCommonPrefixLength)
180
185
  const multihash = Digest.decode(key)
181
186
 
182
187
  return peerIdFromMultihash(multihash)
183
188
  }
184
189
 
185
- async _makePeerId (localKadId: Uint8Array, randomPrefix: number, targetCommonPrefixLength: number): Promise<Uint8Array> {
190
+ _makePeerId (localKadId: Uint8Array, randomPrefix: number, targetCommonPrefixLength: number): Uint8Array {
186
191
  if (targetCommonPrefixLength > MAX_COMMON_PREFIX_LENGTH) {
187
192
  throw new Error(`Cannot generate peer ID for common prefix length greater than ${MAX_COMMON_PREFIX_LENGTH}`)
188
193
  }
@@ -42,38 +42,52 @@ export class FindNodeHandler implements DHTMessageHandler {
42
42
  * Process `FindNode` DHT messages
43
43
  */
44
44
  async handle (peerId: PeerId, msg: Message): Promise<Message> {
45
- this.log('incoming request from %p for peers closer to %b', peerId, msg.key)
45
+ this.log('incoming request from %p for peers close to %b', peerId, msg.key)
46
+ try {
47
+ if (msg.key == null) {
48
+ throw new InvalidMessageError('Invalid FIND_NODE message received - key was missing')
49
+ }
46
50
 
47
- if (msg.key == null) {
48
- throw new InvalidMessageError('Invalid FIND_NODE message received - key was missing')
49
- }
50
-
51
- const closer: PeerInfo[] = await this.peerRouting.getCloserPeersOffline(msg.key, peerId)
51
+ const closer: PeerInfo[] = await this.peerRouting.getClosestPeersOffline(msg.key, {
52
+ exclude: [
53
+ // never tell a peer about itself
54
+ peerId,
52
55
 
53
- if (uint8ArrayEquals(this.peerId.toMultihash().bytes, msg.key)) {
54
- closer.push({
55
- id: this.peerId,
56
- multiaddrs: this.addressManager.getAddresses().map(ma => ma.decapsulateCode(protocols('p2p').code))
56
+ // do not include the server in the results
57
+ this.peerId
58
+ ]
57
59
  })
58
- }
59
60
 
60
- const response: Message = {
61
- type: MessageType.FIND_NODE,
62
- clusterLevel: msg.clusterLevel,
63
- closer: closer
64
- .map(this.peerInfoMapper)
65
- .filter(({ multiaddrs }) => multiaddrs.length)
66
- .map(peerInfo => ({
67
- id: peerInfo.id.toMultihash().bytes,
68
- multiaddrs: peerInfo.multiaddrs.map(ma => ma.bytes)
69
- })),
70
- providers: []
71
- }
61
+ if (uint8ArrayEquals(this.peerId.toMultihash().bytes, msg.key)) {
62
+ closer.push({
63
+ id: this.peerId,
64
+ multiaddrs: this.addressManager.getAddresses().map(ma => ma.decapsulateCode(protocols('p2p').code))
65
+ })
66
+ }
72
67
 
73
- if (response.closer.length === 0) {
74
- this.log('could not find any peers closer to %b than %p', msg.key, peerId)
75
- }
68
+ const response: Message = {
69
+ type: MessageType.FIND_NODE,
70
+ clusterLevel: msg.clusterLevel,
71
+ closer: closer
72
+ .map(this.peerInfoMapper)
73
+ .filter(({ multiaddrs }) => multiaddrs.length)
74
+ .map(peerInfo => ({
75
+ id: peerInfo.id.toMultihash().bytes,
76
+ multiaddrs: peerInfo.multiaddrs.map(ma => ma.bytes)
77
+ })),
78
+ providers: []
79
+ }
80
+
81
+ if (response.closer.length === 0) {
82
+ this.log('could not find any peers closer to %b for %p', msg.key, peerId)
83
+ } else {
84
+ this.log('found %d peers close to %b for %p', response.closer.length, msg.key, peerId)
85
+ }
76
86
 
77
- return response
87
+ return response
88
+ } catch (err: any) {
89
+ this.log('error during finding peers closer to %b for %p - %e', msg.key, peerId, err)
90
+ throw err
91
+ }
78
92
  }
79
93
  }
@@ -67,7 +67,7 @@ export class GetProvidersHandler implements DHTMessageHandler {
67
67
 
68
68
  return info
69
69
  })),
70
- this.peerRouting.getCloserPeersOffline(msg.key, peerId)
70
+ this.peerRouting.getClosestPeersOffline(msg.key)
71
71
  ])
72
72
 
73
73
  const response: Message = {
@@ -84,7 +84,7 @@ export class GetValueHandler implements DHTMessageHandler {
84
84
 
85
85
  const [record, closer] = await Promise.all([
86
86
  this._checkLocalDatastore(key),
87
- this.peerRouting.getCloserPeersOffline(key, peerId)
87
+ this.peerRouting.getClosestPeersOffline(key)
88
88
  ])
89
89
 
90
90
  if (record != null) {
package/src/rpc/index.ts CHANGED
@@ -1,5 +1,5 @@
1
- import * as lp from 'it-length-prefixed'
2
- import { pipe } from 'it-pipe'
1
+ import { TimeoutError } from '@libp2p/interface'
2
+ import { pbStream } from 'it-protobuf-stream'
3
3
  import { Message, MessageType } from '../message/dht.js'
4
4
  import { AddProviderHandler } from './handlers/add-provider.js'
5
5
  import { FindNodeHandler } from './handlers/find-node.js'
@@ -15,7 +15,7 @@ import type { GetProvidersHandlerComponents } from './handlers/get-providers.js'
15
15
  import type { GetValueHandlerComponents } from './handlers/get-value.js'
16
16
  import type { PutValueHandlerComponents } from './handlers/put-value.js'
17
17
  import type { RoutingTable } from '../routing-table/index.js'
18
- import type { CounterGroup, Logger, Metrics, PeerId, IncomingStreamData } from '@libp2p/interface'
18
+ import type { CounterGroup, Logger, Metrics, PeerId, IncomingStreamData, MetricGroup } from '@libp2p/interface'
19
19
 
20
20
  export interface DHTMessageHandler {
21
21
  handle(peerId: PeerId, msg: Message): Promise<Message | undefined>
@@ -30,6 +30,7 @@ export interface RPCInit {
30
30
  metricsPrefix: string
31
31
  datastorePrefix: string
32
32
  peerInfoMapper: PeerInfoMapper
33
+ incomingMessageTimeout?: number
33
34
  }
34
35
 
35
36
  export interface RPCComponents extends GetValueHandlerComponents, PutValueHandlerComponents, FindNodeHandlerComponents, GetProvidersHandlerComponents {
@@ -38,21 +39,24 @@ export interface RPCComponents extends GetValueHandlerComponents, PutValueHandle
38
39
 
39
40
  export class RPC {
40
41
  private readonly handlers: Record<string, DHTMessageHandler>
41
- private readonly routingTable: RoutingTable
42
42
  private readonly log: Logger
43
43
  private readonly metrics: {
44
44
  operations?: CounterGroup
45
45
  errors?: CounterGroup
46
+ rpcTime?: MetricGroup
46
47
  }
47
48
 
49
+ private readonly incomingMessageTimeout: number
50
+
48
51
  constructor (components: RPCComponents, init: RPCInit) {
49
52
  this.metrics = {
50
53
  operations: components.metrics?.registerCounterGroup(`${init.metricsPrefix}_inbound_rpc_requests_total`),
51
- errors: components.metrics?.registerCounterGroup(`${init.metricsPrefix}_inbound_rpc_errors_total`)
54
+ errors: components.metrics?.registerCounterGroup(`${init.metricsPrefix}_inbound_rpc_errors_total`),
55
+ rpcTime: components.metrics?.registerMetricGroup(`${init.metricsPrefix}_inbound_rpc_time_seconds`, { label: 'operation' })
52
56
  }
53
57
 
54
58
  this.log = components.logger.forComponent(`${init.logPrefix}:rpc`)
55
- this.routingTable = init.routingTable
59
+ this.incomingMessageTimeout = init.incomingMessageTimeout ?? 10_000
56
60
  this.handlers = {
57
61
  [MessageType.GET_VALUE.toString()]: new GetValueHandler(components, init),
58
62
  [MessageType.PUT_VALUE.toString()]: new PutValueHandler(components, init),
@@ -92,34 +96,61 @@ export class RPC {
92
96
  * Handle incoming streams on the dht protocol
93
97
  */
94
98
  onIncomingStream (data: IncomingStreamData): void {
95
- let message = 'unknown'
99
+ const message = 'unknown'
96
100
 
97
101
  Promise.resolve().then(async () => {
98
102
  const { stream, connection } = data
99
- const peerId = connection.remotePeer
100
103
 
101
- const self = this
104
+ const abortListener = (): void => {
105
+ stream.abort(new TimeoutError())
106
+ }
107
+
108
+ let signal = AbortSignal.timeout(this.incomingMessageTimeout)
109
+ signal.addEventListener('abort', abortListener)
110
+
111
+ const messages = pbStream(stream).pb(Message)
112
+
113
+ try {
114
+ while (true) {
115
+ const message = await messages.read({
116
+ signal
117
+ })
102
118
 
103
- await pipe(
104
- stream,
105
- (source) => lp.decode(source),
106
- async function * (source) {
107
- for await (const msg of source) {
119
+ const stopSuccessTimer = this.metrics?.rpcTime?.timer(message.type.toString())
120
+ const stopErrorTimer = this.metrics?.rpcTime?.timer(message.type.toString())
121
+ let errored = false
122
+
123
+ try {
108
124
  // handle the message
109
- const desMessage = Message.decode(msg)
110
- message = desMessage.type
111
- self.log('incoming %s from %p', desMessage.type, peerId)
112
- const res = await self.handleMessage(peerId, desMessage)
125
+ this.log('incoming %s from %p', message.type, connection.remotePeer)
126
+ const res = await this.handleMessage(connection.remotePeer, message)
113
127
 
114
128
  // Not all handlers will return a response
115
129
  if (res != null) {
116
- yield Message.encode(res)
130
+ await messages.write(res, {
131
+ signal
132
+ })
133
+ }
134
+ } catch (err) {
135
+ errored = true
136
+ stopErrorTimer?.()
137
+
138
+ throw err
139
+ } finally {
140
+ if (!errored) {
141
+ stopSuccessTimer?.()
117
142
  }
118
143
  }
119
- },
120
- (source) => lp.encode(source),
121
- stream
122
- )
144
+
145
+ // we have received a message so reset the timeout controller to
146
+ // allow the remote to send another
147
+ signal.removeEventListener('abort', abortListener)
148
+ signal = AbortSignal.timeout(this.incomingMessageTimeout)
149
+ signal.addEventListener('abort', abortListener)
150
+ }
151
+ } catch (err: any) {
152
+ stream.abort(err)
153
+ }
123
154
  })
124
155
  .catch(err => {
125
156
  this.log.error('error handling %s RPC message from %p - %e', message, data.connection.remotePeer, err)
@@ -1,4 +1,4 @@
1
- import { TypedEventEmitter } from '@libp2p/interface'
1
+ import { TypedEventEmitter } from 'main-event'
2
2
  import type { KadDHTComponents } from './index.js'
3
3
  import type { Logger, PeerId, Startable } from '@libp2p/interface'
4
4
 
package/src/utils.ts CHANGED
@@ -11,7 +11,7 @@ import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
11
11
  import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
12
12
  import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
13
13
  import type { Operation, OperationMetrics } from './kad-dht.js'
14
- import type { PeerId, PeerInfo } from '@libp2p/interface'
14
+ import type { AbortOptions, PeerId, PeerInfo } from '@libp2p/interface'
15
15
  import type { Multiaddr } from '@multiformats/multiaddr'
16
16
 
17
17
  // const IPNS_PREFIX = uint8ArrayFromString('/ipns/')
@@ -90,8 +90,9 @@ export function passthroughMapper (info: PeerInfo): PeerInfo {
90
90
  /**
91
91
  * Creates a DHT ID by hashing a given Uint8Array
92
92
  */
93
- export async function convertBuffer (buf: Uint8Array): Promise<Uint8Array> {
93
+ export async function convertBuffer (buf: Uint8Array, options?: AbortOptions): Promise<Uint8Array> {
94
94
  const multihash = await sha256.digest(buf)
95
+ options?.signal?.throwIfAborted()
95
96
 
96
97
  return multihash.digest
97
98
  }
@@ -99,8 +100,8 @@ export async function convertBuffer (buf: Uint8Array): Promise<Uint8Array> {
99
100
  /**
100
101
  * Creates a DHT ID by hashing a Peer ID
101
102
  */
102
- export async function convertPeerId (peerId: PeerId): Promise<Uint8Array> {
103
- return convertBuffer(peerId.toMultihash().bytes)
103
+ export async function convertPeerId (peerId: PeerId, options?: AbortOptions): Promise<Uint8Array> {
104
+ return convertBuffer(peerId.toMultihash().bytes, options)
104
105
  }
105
106
 
106
107
  /**
@@ -1,63 +0,0 @@
1
- {
2
- "codec": "https://libp2p.github.io/js-libp2p/functions/_libp2p_kad-dht.MessageType.codec.html",
3
- "EventTypes": "https://libp2p.github.io/js-libp2p/enums/_libp2p_kad-dht.EventTypes.html",
4
- ".:EventTypes": "https://libp2p.github.io/js-libp2p/enums/_libp2p_kad-dht.EventTypes.html",
5
- "MessageType": "https://libp2p.github.io/js-libp2p/enums/_libp2p_kad-dht.MessageType.html",
6
- "AddPeerEvent": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.AddPeerEvent.html",
7
- ".:AddPeerEvent": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.AddPeerEvent.html",
8
- "DHTRecord": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.DHTRecord.html",
9
- ".:DHTRecord": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.DHTRecord.html",
10
- "DialPeerEvent": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.DialPeerEvent.html",
11
- ".:DialPeerEvent": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.DialPeerEvent.html",
12
- "DisjointPath": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.DisjointPath.html",
13
- ".:DisjointPath": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.DisjointPath.html",
14
- "FinalPeerEvent": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.FinalPeerEvent.html",
15
- ".:FinalPeerEvent": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.FinalPeerEvent.html",
16
- "KadDHT": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.KadDHT.html",
17
- ".:KadDHT": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.KadDHT.html",
18
- "KadDHTComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.KadDHTComponents.html",
19
- ".:KadDHTComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.KadDHTComponents.html",
20
- "KadDHTInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.KadDHTInit.html",
21
- ".:KadDHTInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.KadDHTInit.html",
22
- "PathEndedEvent": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.PathEndedEvent.html",
23
- ".:PathEndedEvent": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.PathEndedEvent.html",
24
- "PeerInfoMapper": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.PeerInfoMapper.html",
25
- ".:PeerInfoMapper": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.PeerInfoMapper.html",
26
- "PeerResponseEvent": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.PeerResponseEvent.html",
27
- ".:PeerResponseEvent": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.PeerResponseEvent.html",
28
- "ProviderEvent": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.ProviderEvent.html",
29
- ".:ProviderEvent": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.ProviderEvent.html",
30
- "ProvidersInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.ProvidersInit.html",
31
- ".:ProvidersInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.ProvidersInit.html",
32
- "QueryErrorEvent": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.QueryErrorEvent.html",
33
- ".:QueryErrorEvent": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.QueryErrorEvent.html",
34
- "ReProvideInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.ReProvideInit.html",
35
- ".:ReProvideInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.ReProvideInit.html",
36
- "RoutingTable": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.RoutingTable.html",
37
- ".:RoutingTable": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.RoutingTable.html",
38
- "SelectFn": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.SelectFn.html",
39
- ".:SelectFn": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.SelectFn.html",
40
- "SendQueryEvent": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.SendQueryEvent.html",
41
- ".:SendQueryEvent": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.SendQueryEvent.html",
42
- "SingleKadDHT": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.SingleKadDHT.html",
43
- ".:SingleKadDHT": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.SingleKadDHT.html",
44
- "ValidateFn": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.ValidateFn.html",
45
- ".:ValidateFn": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.ValidateFn.html",
46
- "ValueEvent": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.ValueEvent.html",
47
- ".:ValueEvent": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_kad-dht.ValueEvent.html",
48
- "DHTProgressEvents": "https://libp2p.github.io/js-libp2p/types/_libp2p_kad-dht.DHTProgressEvents.html",
49
- ".:DHTProgressEvents": "https://libp2p.github.io/js-libp2p/types/_libp2p_kad-dht.DHTProgressEvents.html",
50
- "MessageName": "https://libp2p.github.io/js-libp2p/types/_libp2p_kad-dht.MessageName.html",
51
- ".:MessageName": "https://libp2p.github.io/js-libp2p/types/_libp2p_kad-dht.MessageName.html",
52
- "QueryEvent": "https://libp2p.github.io/js-libp2p/types/_libp2p_kad-dht.QueryEvent.html",
53
- ".:QueryEvent": "https://libp2p.github.io/js-libp2p/types/_libp2p_kad-dht.QueryEvent.html",
54
- "Selectors": "https://libp2p.github.io/js-libp2p/types/_libp2p_kad-dht.Selectors.html",
55
- ".:Selectors": "https://libp2p.github.io/js-libp2p/types/_libp2p_kad-dht.Selectors.html",
56
- "Validators": "https://libp2p.github.io/js-libp2p/types/_libp2p_kad-dht.Validators.html",
57
- ".:Validators": "https://libp2p.github.io/js-libp2p/types/_libp2p_kad-dht.Validators.html",
58
- "kadDHT": "https://libp2p.github.io/js-libp2p/functions/_libp2p_kad-dht.kadDHT.html",
59
- ".:kadDHT": "https://libp2p.github.io/js-libp2p/functions/_libp2p_kad-dht.kadDHT.html",
60
- "passthroughMapper": "https://libp2p.github.io/js-libp2p/functions/_libp2p_kad-dht.passthroughMapper.html",
61
- "removePrivateAddressesMapper": "https://libp2p.github.io/js-libp2p/functions/_libp2p_kad-dht.removePrivateAddressesMapper.html",
62
- "removePublicAddressesMapper": "https://libp2p.github.io/js-libp2p/functions/_libp2p_kad-dht.removePublicAddressesMapper.html"
63
- }