@libp2p/kad-dht 12.0.14 → 12.0.15-43046b9ae

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 (69) hide show
  1. package/dist/index.min.js +4 -4
  2. package/dist/src/constants.d.ts.map +1 -1
  3. package/dist/src/constants.js +1 -1
  4. package/dist/src/constants.js.map +1 -1
  5. package/dist/src/content-routing/index.d.ts.map +1 -1
  6. package/dist/src/content-routing/index.js +3 -2
  7. package/dist/src/content-routing/index.js.map +1 -1
  8. package/dist/src/index.d.ts +34 -1
  9. package/dist/src/index.d.ts.map +1 -1
  10. package/dist/src/index.js.map +1 -1
  11. package/dist/src/kad-dht.d.ts.map +1 -1
  12. package/dist/src/kad-dht.js +3 -19
  13. package/dist/src/kad-dht.js.map +1 -1
  14. package/dist/src/network.d.ts +3 -0
  15. package/dist/src/network.d.ts.map +1 -1
  16. package/dist/src/network.js +33 -8
  17. package/dist/src/network.js.map +1 -1
  18. package/dist/src/peer-list/peer-distance-list.d.ts +13 -4
  19. package/dist/src/peer-list/peer-distance-list.d.ts.map +1 -1
  20. package/dist/src/peer-list/peer-distance-list.js +29 -21
  21. package/dist/src/peer-list/peer-distance-list.js.map +1 -1
  22. package/dist/src/peer-routing/index.d.ts +5 -5
  23. package/dist/src/peer-routing/index.d.ts.map +1 -1
  24. package/dist/src/peer-routing/index.js +16 -25
  25. package/dist/src/peer-routing/index.js.map +1 -1
  26. package/dist/src/query/manager.d.ts +3 -0
  27. package/dist/src/query/manager.d.ts.map +1 -1
  28. package/dist/src/query/manager.js +14 -5
  29. package/dist/src/query/manager.js.map +1 -1
  30. package/dist/src/query/query-path.d.ts +6 -6
  31. package/dist/src/query/query-path.d.ts.map +1 -1
  32. package/dist/src/query/query-path.js +32 -20
  33. package/dist/src/query/query-path.js.map +1 -1
  34. package/dist/src/routing-table/index.d.ts +11 -5
  35. package/dist/src/routing-table/index.d.ts.map +1 -1
  36. package/dist/src/routing-table/index.js +84 -42
  37. package/dist/src/routing-table/index.js.map +1 -1
  38. package/dist/src/routing-table/k-bucket.d.ts +80 -115
  39. package/dist/src/routing-table/k-bucket.d.ts.map +1 -1
  40. package/dist/src/routing-table/k-bucket.js +165 -311
  41. package/dist/src/routing-table/k-bucket.js.map +1 -1
  42. package/dist/src/routing-table/refresh.d.ts +1 -1
  43. package/dist/src/routing-table/refresh.d.ts.map +1 -1
  44. package/dist/src/routing-table/refresh.js +10 -5
  45. package/dist/src/routing-table/refresh.js.map +1 -1
  46. package/dist/src/rpc/handlers/find-node.d.ts +3 -0
  47. package/dist/src/rpc/handlers/find-node.d.ts.map +1 -1
  48. package/dist/src/rpc/handlers/find-node.js +10 -1
  49. package/dist/src/rpc/handlers/find-node.js.map +1 -1
  50. package/package.json +14 -16
  51. package/src/constants.ts +1 -1
  52. package/src/content-routing/index.ts +3 -2
  53. package/src/index.ts +37 -1
  54. package/src/kad-dht.ts +19 -21
  55. package/src/network.ts +38 -9
  56. package/src/peer-list/peer-distance-list.ts +36 -25
  57. package/src/peer-routing/index.ts +20 -29
  58. package/src/query/manager.ts +18 -5
  59. package/src/query/query-path.ts +46 -30
  60. package/src/routing-table/index.ts +100 -46
  61. package/src/routing-table/k-bucket.ts +214 -359
  62. package/src/routing-table/refresh.ts +11 -5
  63. package/src/rpc/handlers/find-node.ts +13 -1
  64. package/dist/src/query/utils.d.ts +0 -6
  65. package/dist/src/query/utils.d.ts.map +0 -1
  66. package/dist/src/query/utils.js +0 -53
  67. package/dist/src/query/utils.js.map +0 -1
  68. package/dist/typedoc-urls.json +0 -55
  69. package/src/query/utils.ts +0 -64
@@ -12,7 +12,7 @@ import {
12
12
  } from '../query/events.js'
13
13
  import { verifyRecord } from '../record/validators.js'
14
14
  import * as utils from '../utils.js'
15
- import type { KadDHTComponents, DHTRecord, DialPeerEvent, FinalPeerEvent, QueryEvent, Validators } from '../index.js'
15
+ import type { KadDHTComponents, DHTRecord, FinalPeerEvent, QueryEvent, Validators } from '../index.js'
16
16
  import type { Message } from '../message/dht.js'
17
17
  import type { Network } from '../network.js'
18
18
  import type { QueryManager, QueryOptions } from '../query/manager.js'
@@ -195,17 +195,17 @@ export class PeerRouting {
195
195
  }
196
196
 
197
197
  /**
198
- * Kademlia 'node lookup' operation on a key, which could be a the
199
- * bytes from a multihash or a peer ID
198
+ * Kademlia 'FIND_NODE' operation on a key, which could be the bytes from
199
+ * a multihash or a peer ID
200
200
  */
201
- async * getClosestPeers (key: Uint8Array, options: QueryOptions = {}): AsyncGenerator<DialPeerEvent | QueryEvent> {
201
+ async * getClosestPeers (key: Uint8Array, options: QueryOptions = {}): AsyncGenerator<QueryEvent> {
202
202
  this.log('getClosestPeers to %b', key)
203
- const id = await utils.convertBuffer(key)
204
- const tablePeers = this.routingTable.closestPeers(id)
203
+ const kadId = await utils.convertBuffer(key)
204
+ const tablePeers = this.routingTable.closestPeers(kadId)
205
205
  const self = this // eslint-disable-line @typescript-eslint/no-this-alias
206
206
 
207
- const peers = new PeerDistanceList(id, this.routingTable.kBucketSize)
208
- await Promise.all(tablePeers.map(async peer => { await peers.add(peer) }))
207
+ const peers = new PeerDistanceList(kadId, this.routingTable.kBucketSize)
208
+ await Promise.all(tablePeers.map(async peer => { await peers.add({ id: peer, multiaddrs: [] }) }))
209
209
 
210
210
  const getCloserPeersQuery: QueryFunc = async function * ({ peer, signal }) {
211
211
  self.log('closerPeersSingle %s from %p', uint8ArrayToString(key, 'base32'), peer)
@@ -221,31 +221,22 @@ export class PeerRouting {
221
221
  }
222
222
 
223
223
  for await (const event of this.queryManager.run(key, getCloserPeersQuery, options)) {
224
- yield event
225
-
226
224
  if (event.name === 'PEER_RESPONSE') {
227
- await Promise.all(event.closer.map(async peerData => { await peers.add(peerData.id) }))
225
+ await Promise.all(event.closer.map(async peerData => {
226
+ await peers.add(peerData)
227
+ }))
228
228
  }
229
+
230
+ yield event
229
231
  }
230
232
 
231
233
  this.log('found %d peers close to %b', peers.length, key)
232
234
 
233
- for (const peerId of peers.peers) {
234
- try {
235
- const peer = await this.peerStore.get(peerId)
236
-
237
- yield finalPeerEvent({
238
- from: this.peerId,
239
- peer: {
240
- id: peerId,
241
- multiaddrs: peer.addresses.map(({ multiaddr }) => multiaddr)
242
- }
243
- }, options)
244
- } catch (err: any) {
245
- if (err.code !== 'ERR_NOT_FOUND') {
246
- throw err
247
- }
248
- }
235
+ for (const peer of peers.peers) {
236
+ yield finalPeerEvent({
237
+ from: this.peerId,
238
+ peer
239
+ }, options)
249
240
  }
250
241
  }
251
242
 
@@ -255,7 +246,7 @@ export class PeerRouting {
255
246
  *
256
247
  * Note: The peerStore is updated with new addresses found for the given peer.
257
248
  */
258
- async * getValueOrPeers (peer: PeerId, key: Uint8Array, options: RoutingOptions = {}): AsyncGenerator<DialPeerEvent | QueryEvent> {
249
+ async * getValueOrPeers (peer: PeerId, key: Uint8Array, options: RoutingOptions = {}): AsyncGenerator<QueryEvent> {
259
250
  for await (const event of this._getValueSingle(peer, key, options)) {
260
251
  if (event.name === 'PEER_RESPONSE') {
261
252
  if (event.record != null) {
@@ -319,7 +310,7 @@ export class PeerRouting {
319
310
  if (output.length > 0) {
320
311
  this.log('getCloserPeersOffline found %d peer(s) closer to %b than %p', output.length, key, closerThan)
321
312
  } else {
322
- this.log('getCloserPeersOffline could not find peer closer to %b than %p', key, closerThan)
313
+ this.log('getCloserPeersOffline could not find peer closer to %b than %p with %d peers in the routing table', key, closerThan, this.routingTable.size)
323
314
  }
324
315
 
325
316
  return output
@@ -1,4 +1,4 @@
1
- import { TypedEventEmitter, CustomEvent, setMaxListeners } from '@libp2p/interface'
1
+ import { setMaxListeners } from '@libp2p/interface'
2
2
  import { PeerSet } from '@libp2p/peer-collections'
3
3
  import { anySignal } from 'any-signal'
4
4
  import merge from 'it-merge'
@@ -13,6 +13,7 @@ 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
15
  import type { ComponentLogger, Metric, Metrics, PeerId, RoutingOptions, Startable } from '@libp2p/interface'
16
+ import type { ConnectionManager } from '@libp2p/interface-internal'
16
17
  import type { DeferredPromise } from 'p-defer'
17
18
 
18
19
  export interface CleanUpEvents {
@@ -31,6 +32,7 @@ export interface QueryManagerComponents {
31
32
  peerId: PeerId
32
33
  metrics?: Metrics
33
34
  logger: ComponentLogger
35
+ connectionManager: ConnectionManager
34
36
  }
35
37
 
36
38
  export interface QueryOptions extends RoutingOptions {
@@ -53,6 +55,7 @@ export class QueryManager implements Startable {
53
55
  private queries: number
54
56
  private readonly logger: ComponentLogger
55
57
  private readonly peerId: PeerId
58
+ private readonly connectionManager: ConnectionManager
56
59
  private readonly routingTable: RoutingTable
57
60
  private initialQuerySelfHasRun?: DeferredPromise<void>
58
61
  private readonly logPrefix: string
@@ -73,6 +76,7 @@ export class QueryManager implements Startable {
73
76
  this.routingTable = init.routingTable
74
77
  this.logger = components.logger
75
78
  this.peerId = components.peerId
79
+ this.connectionManager = components.connectionManager
76
80
 
77
81
  if (components.metrics != null) {
78
82
  this.metrics = {
@@ -151,7 +155,6 @@ export class QueryManager implements Startable {
151
155
 
152
156
  // query a subset of peers up to `kBucketSize / 2` in length
153
157
  const startTime = Date.now()
154
- const cleanUp = new TypedEventEmitter<CleanUpEvents>()
155
158
  let queryFinished = false
156
159
 
157
160
  try {
@@ -190,11 +193,11 @@ export class QueryManager implements Startable {
190
193
  pathIndex: index,
191
194
  numPaths: peersToQuery.length,
192
195
  alpha: this.alpha,
193
- cleanUp,
194
196
  queryFuncTimeout: options.queryFuncTimeout,
195
197
  log,
196
198
  peersSeen,
197
- onProgress: options.onProgress
199
+ onProgress: options.onProgress,
200
+ connectionManager: this.connectionManager
198
201
  })
199
202
  })
200
203
 
@@ -204,6 +207,17 @@ export class QueryManager implements Startable {
204
207
  log.error('query error', event.error)
205
208
  }
206
209
 
210
+ if (event.name === 'PEER_RESPONSE') {
211
+ for (const peer of [...event.closer, ...event.providers]) {
212
+ // eslint-disable-next-line max-depth
213
+ if (!(await this.connectionManager.isDialable(peer.multiaddrs))) {
214
+ continue
215
+ }
216
+
217
+ await this.routingTable.add(peer.id)
218
+ }
219
+ }
220
+
207
221
  yield event
208
222
  }
209
223
 
@@ -229,7 +243,6 @@ export class QueryManager implements Startable {
229
243
  stopQueryTimer()
230
244
  }
231
245
 
232
- cleanUp.dispatchEvent(new CustomEvent('cleanup'))
233
246
  log('query:done in %dms', Date.now() - startTime)
234
247
  }
235
248
  }
@@ -1,19 +1,16 @@
1
- import { setMaxListeners } from '@libp2p/interface'
1
+ import { CodeError, setMaxListeners } from '@libp2p/interface'
2
+ import { Queue } from '@libp2p/utils/queue'
2
3
  import { anySignal } from 'any-signal'
3
- import Queue from 'p-queue'
4
- import { toString } from 'uint8arrays/to-string'
5
- import { xor } from 'uint8arrays/xor'
4
+ import { xor as uint8ArrayXor } from 'uint8arrays/xor'
5
+ import { xorCompare as uint8ArrayXorCompare } from 'uint8arrays/xor-compare'
6
6
  import { convertPeerId, convertBuffer } from '../utils.js'
7
7
  import { queryErrorEvent } from './events.js'
8
- import { queueToGenerator } from './utils.js'
9
- import type { CleanUpEvents } from './manager.js'
10
8
  import type { QueryEvent } from '../index.js'
11
9
  import type { QueryFunc } from '../query/types.js'
12
- import type { Logger, TypedEventTarget, PeerId, RoutingOptions } from '@libp2p/interface'
10
+ import type { Logger, PeerId, RoutingOptions, AbortOptions } from '@libp2p/interface'
11
+ import type { ConnectionManager } from '@libp2p/interface-internal'
13
12
  import type { PeerSet } from '@libp2p/peer-collections'
14
13
 
15
- const MAX_XOR = BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF')
16
-
17
14
  export interface QueryPathOptions extends RoutingOptions {
18
15
  /**
19
16
  * What are we trying to find
@@ -55,11 +52,6 @@ export interface QueryPathOptions extends RoutingOptions {
55
52
  */
56
53
  numPaths: number
57
54
 
58
- /**
59
- * will emit a 'cleanup' event if the caller exits the for..await of early
60
- */
61
- cleanUp: TypedEventTarget<CleanUpEvents>
62
-
63
55
  /**
64
56
  * A timeout for queryFunc in ms
65
57
  */
@@ -74,6 +66,15 @@ export interface QueryPathOptions extends RoutingOptions {
74
66
  * Set of peers seen by this and other paths
75
67
  */
76
68
  peersSeen: PeerSet
69
+
70
+ /**
71
+ * The libp2p connection manager
72
+ */
73
+ connectionManager: ConnectionManager
74
+ }
75
+
76
+ interface QueryQueueOptions extends AbortOptions {
77
+ distance: Uint8Array
77
78
  }
78
79
 
79
80
  /**
@@ -81,11 +82,12 @@ export interface QueryPathOptions extends RoutingOptions {
81
82
  * every peer encountered that we have not seen before
82
83
  */
83
84
  export async function * queryPath (options: QueryPathOptions): AsyncGenerator<QueryEvent, void, undefined> {
84
- const { key, startingPeer, ourPeerId, signal, query, alpha, pathIndex, numPaths, cleanUp, queryFuncTimeout, log, peersSeen } = options
85
+ const { key, startingPeer, ourPeerId, signal, query, alpha, pathIndex, numPaths, queryFuncTimeout, log, peersSeen, connectionManager } = options
85
86
  // Only ALPHA node/value lookups are allowed at any given time for each process
86
87
  // https://github.com/libp2p/specs/tree/master/kad-dht#alpha-concurrency-parameter-%CE%B1
87
- const queue = new Queue({
88
- concurrency: alpha
88
+ const queue = new Queue<QueryEvent | undefined, QueryQueueOptions>({
89
+ concurrency: alpha,
90
+ sort: (a, b) => uint8ArrayXorCompare(a.options.distance, b.options.distance)
89
91
  })
90
92
 
91
93
  // perform lookups on kadId, not the actual value
@@ -102,7 +104,7 @@ export async function * queryPath (options: QueryPathOptions): AsyncGenerator<Qu
102
104
 
103
105
  peersSeen.add(peer)
104
106
 
105
- const peerXor = BigInt('0x' + toString(xor(peerKadId, kadId), 'base16'))
107
+ const peerXor = uint8ArrayXor(peerKadId, kadId)
106
108
 
107
109
  queue.add(async () => {
108
110
  const signals = [signal]
@@ -141,11 +143,16 @@ export async function * queryPath (options: QueryPathOptions): AsyncGenerator<Qu
141
143
  continue
142
144
  }
143
145
 
146
+ if (!(await connectionManager.isDialable(closerPeer.multiaddrs))) { // eslint-disable-line max-depth
147
+ log('not querying undialable peer')
148
+ continue
149
+ }
150
+
144
151
  const closerPeerKadId = await convertPeerId(closerPeer.id)
145
- const closerPeerXor = BigInt('0x' + toString(xor(closerPeerKadId, kadId), 'base16'))
152
+ const closerPeerXor = uint8ArrayXor(closerPeerKadId, kadId)
146
153
 
147
154
  // only continue query if closer peer is actually closer
148
- if (closerPeerXor > peerXor) { // eslint-disable-line max-depth
155
+ if (uint8ArrayXorCompare(closerPeerXor, peerXor) !== -1) { // eslint-disable-line max-depth
149
156
  log('skipping %p as they are not closer to %b than %p', closerPeer.id, key, peer)
150
157
  continue
151
158
  }
@@ -154,7 +161,10 @@ export async function * queryPath (options: QueryPathOptions): AsyncGenerator<Qu
154
161
  queryPeer(closerPeer.id, closerPeerKadId)
155
162
  }
156
163
  }
157
- queue.emit('completed', event)
164
+
165
+ queue.safeDispatchEvent('completed', {
166
+ detail: event
167
+ })
158
168
  }
159
169
  } catch (err: any) {
160
170
  if (!signal.aborted) {
@@ -167,13 +177,7 @@ export async function * queryPath (options: QueryPathOptions): AsyncGenerator<Qu
167
177
  compoundSignal.clear()
168
178
  }
169
179
  }, {
170
- // use xor value as the queue priority - closer peers should execute first
171
- // subtract it from MAX_XOR because higher priority values execute sooner
172
-
173
- // @ts-expect-error this is supposed to be a Number but it's ok to use BigInts
174
- // as long as all priorities are BigInts since we won't mix BigInts and Number
175
- // values in arithmetic operations
176
- priority: MAX_XOR - peerXor
180
+ distance: peerXor
177
181
  }).catch(err => {
178
182
  log.error(err)
179
183
  })
@@ -182,6 +186,18 @@ export async function * queryPath (options: QueryPathOptions): AsyncGenerator<Qu
182
186
  // begin the query with the starting peer
183
187
  queryPeer(startingPeer, await convertPeerId(startingPeer))
184
188
 
185
- // yield results as they come in
186
- yield * queueToGenerator(queue, signal, cleanUp, log)
189
+ try {
190
+ // yield results as they come in
191
+ for await (const event of queue.toGenerator({ signal })) {
192
+ if (event != null) {
193
+ yield event
194
+ }
195
+ }
196
+ } catch (err) {
197
+ if (signal.aborted) {
198
+ throw new CodeError('Query aborted', 'ERR_QUERY_ABORTED')
199
+ }
200
+
201
+ throw err
202
+ }
187
203
  }
@@ -4,19 +4,22 @@ import { PeerQueue } from '@libp2p/utils/peer-queue'
4
4
  import { pbStream } from 'it-protobuf-stream'
5
5
  import { Message, MessageType } from '../message/dht.js'
6
6
  import * as utils from '../utils.js'
7
- import { KBucket, type PingEventDetails } from './k-bucket.js'
7
+ import { KBucket, isLeafBucket, type Bucket, type PingEventDetails } from './k-bucket.js'
8
8
  import type { ComponentLogger, Logger, Metric, Metrics, PeerId, PeerStore, Startable, Stream } from '@libp2p/interface'
9
9
  import type { ConnectionManager } from '@libp2p/interface-internal'
10
10
 
11
11
  export const KAD_CLOSE_TAG_NAME = 'kad-close'
12
12
  export const KAD_CLOSE_TAG_VALUE = 50
13
13
  export const KBUCKET_SIZE = 20
14
+ export const PREFIX_LENGTH = 32
14
15
  export const PING_TIMEOUT = 10000
15
16
  export const PING_CONCURRENCY = 10
16
17
 
17
18
  export interface RoutingTableInit {
18
19
  logPrefix: string
19
20
  protocol: string
21
+ prefixLength?: number
22
+ splitThreshold?: number
20
23
  kBucketSize?: number
21
24
  pingTimeout?: number
22
25
  pingConcurrency?: number
@@ -48,6 +51,8 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
48
51
 
49
52
  private readonly log: Logger
50
53
  private readonly components: RoutingTableComponents
54
+ private readonly prefixLength: number
55
+ private readonly splitThreshold: number
51
56
  private readonly pingTimeout: number
52
57
  private readonly pingConcurrency: number
53
58
  private running: boolean
@@ -56,26 +61,29 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
56
61
  private readonly tagValue: number
57
62
  private readonly metrics?: {
58
63
  routingTableSize: Metric
64
+ routingTableKadBucketTotal: Metric
65
+ routingTableKadBucketAverageOccupancy: Metric
66
+ routingTableKadBucketMaxDepth: Metric
59
67
  }
60
68
 
61
69
  constructor (components: RoutingTableComponents, init: RoutingTableInit) {
62
70
  super()
63
71
 
64
- const { kBucketSize, pingTimeout, logPrefix, pingConcurrency, protocol, tagName, tagValue } = init
65
-
66
72
  this.components = components
67
- this.log = components.logger.forComponent(`${logPrefix}:routing-table`)
68
- this.kBucketSize = kBucketSize ?? KBUCKET_SIZE
69
- this.pingTimeout = pingTimeout ?? PING_TIMEOUT
70
- this.pingConcurrency = pingConcurrency ?? PING_CONCURRENCY
73
+ this.log = components.logger.forComponent(`${init.logPrefix}:routing-table`)
74
+ this.kBucketSize = init.kBucketSize ?? KBUCKET_SIZE
75
+ this.pingTimeout = init.pingTimeout ?? PING_TIMEOUT
76
+ this.pingConcurrency = init.pingConcurrency ?? PING_CONCURRENCY
71
77
  this.running = false
72
- this.protocol = protocol
73
- this.tagName = tagName ?? KAD_CLOSE_TAG_NAME
74
- this.tagValue = tagValue ?? KAD_CLOSE_TAG_VALUE
78
+ this.protocol = init.protocol
79
+ this.tagName = init.tagName ?? KAD_CLOSE_TAG_NAME
80
+ this.tagValue = init.tagValue ?? KAD_CLOSE_TAG_VALUE
81
+ this.prefixLength = init.prefixLength ?? PREFIX_LENGTH
82
+ this.splitThreshold = init.splitThreshold ?? KBUCKET_SIZE
75
83
 
76
84
  this.pingQueue = new PeerQueue({
77
85
  concurrency: this.pingConcurrency,
78
- metricName: `${logPrefix.replaceAll(':', '_')}_ping_queue`,
86
+ metricName: `${init.logPrefix.replaceAll(':', '_')}_ping_queue`,
79
87
  metrics: this.components.metrics
80
88
  })
81
89
  this.pingQueue.addEventListener('error', evt => {
@@ -84,7 +92,10 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
84
92
 
85
93
  if (this.components.metrics != null) {
86
94
  this.metrics = {
87
- routingTableSize: this.components.metrics.registerMetric(`${logPrefix.replaceAll(':', '_')}_routing_table_size`)
95
+ routingTableSize: this.components.metrics.registerMetric(`${init.logPrefix.replaceAll(':', '_')}_routing_table_size`),
96
+ routingTableKadBucketTotal: this.components.metrics.registerMetric(`${init.logPrefix.replaceAll(':', '_')}_routing_table_kad_bucket_total`),
97
+ routingTableKadBucketAverageOccupancy: this.components.metrics.registerMetric(`${init.logPrefix.replaceAll(':', '_')}_routing_table_kad_bucket_average_occupancy`),
98
+ routingTableKadBucketMaxDepth: this.components.metrics.registerMetric(`${init.logPrefix.replaceAll(':', '_')}_routing_table_kad_bucket_max_depth`)
88
99
  }
89
100
  }
90
101
  }
@@ -97,8 +108,13 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
97
108
  this.running = true
98
109
 
99
110
  const kBuck = new KBucket({
100
- localNodeId: await utils.convertPeerId(this.components.peerId),
101
- numberOfNodesPerKBucket: this.kBucketSize,
111
+ localPeer: {
112
+ kadId: await utils.convertPeerId(this.components.peerId),
113
+ peerId: this.components.peerId
114
+ },
115
+ kBucketSize: this.kBucketSize,
116
+ prefixLength: this.prefixLength,
117
+ splitThreshold: this.splitThreshold,
102
118
  numberOfNodesToPing: 1
103
119
  })
104
120
  this.kb = kBuck
@@ -110,6 +126,20 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
110
126
  })
111
127
  })
112
128
 
129
+ let peerStorePeers = 0
130
+
131
+ // add existing peers from the peer store to routing table
132
+ for (const peer of await this.components.peerStore.all()) {
133
+ if (peer.protocols.includes(this.protocol)) {
134
+ const id = await utils.convertPeerId(peer.id)
135
+
136
+ this.kb.add({ kadId: id, peerId: peer.id })
137
+ peerStorePeers++
138
+ }
139
+ }
140
+
141
+ this.log('added %d peer store peers to the routing table', peerStorePeers)
142
+
113
143
  // tag kad-close peers
114
144
  this._tagPeers(kBuck)
115
145
  }
@@ -130,7 +160,7 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
130
160
 
131
161
  const updatePeerTags = utils.debounce(() => {
132
162
  const newClosest = new PeerSet(
133
- kBuck.closest(kBuck.localNodeId, KBUCKET_SIZE).map(contact => contact.peer)
163
+ kBuck.closest(kBuck.localPeer.kadId, KBUCKET_SIZE)
134
164
  )
135
165
  const addedPeers = newClosest.difference(kClosest)
136
166
  const removedPeers = kClosest.difference(newClosest)
@@ -165,12 +195,12 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
165
195
  kBuck.addEventListener('added', (evt) => {
166
196
  updatePeerTags()
167
197
 
168
- this.safeDispatchEvent('peer:add', { detail: evt.detail.peer })
198
+ this.safeDispatchEvent('peer:add', { detail: evt.detail.peerId })
169
199
  })
170
200
  kBuck.addEventListener('removed', (evt) => {
171
201
  updatePeerTags()
172
202
 
173
- this.safeDispatchEvent('peer:remove', { detail: evt.detail.peer })
203
+ this.safeDispatchEvent('peer:remove', { detail: evt.detail.peerId })
174
204
  })
175
205
  }
176
206
 
@@ -197,7 +227,7 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
197
227
  const results = await Promise.all(
198
228
  oldContacts.map(async oldContact => {
199
229
  // if a previous ping wants us to ping this contact, re-use the result
200
- const pingJob = this.pingQueue.find(oldContact.peer)
230
+ const pingJob = this.pingQueue.find(oldContact.peerId)
201
231
 
202
232
  if (pingJob != null) {
203
233
  return pingJob.join()
@@ -211,8 +241,8 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
211
241
  signal: AbortSignal.timeout(this.pingTimeout)
212
242
  }
213
243
 
214
- this.log('pinging old contact %p', oldContact.peer)
215
- const connection = await this.components.connectionManager.openConnection(oldContact.peer, options)
244
+ this.log('pinging old contact %p', oldContact.peerId)
245
+ const connection = await this.components.connectionManager.openConnection(oldContact.peerId, options)
216
246
  stream = await connection.newStream(this.protocol, options)
217
247
 
218
248
  const pb = pbStream(stream)
@@ -232,9 +262,9 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
232
262
  if (this.running && this.kb != null) {
233
263
  // only evict peers if we are still running, otherwise we evict
234
264
  // when dialing is cancelled due to shutdown in progress
235
- this.log.error('could not ping peer %p', oldContact.peer, err)
236
- this.log('evicting old contact after ping failed %p', oldContact.peer)
237
- this.kb.remove(oldContact.id)
265
+ this.log.error('could not ping peer %p', oldContact.peerId, err)
266
+ this.log('evicting old contact after ping failed %p', oldContact.peerId)
267
+ this.kb.remove(oldContact.kadId)
238
268
  }
239
269
 
240
270
  stream?.abort(err)
@@ -244,7 +274,7 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
244
274
  this.metrics?.routingTableSize.update(this.size)
245
275
  }
246
276
  }, {
247
- peerId: oldContact.peer
277
+ peerId: oldContact.peerId
248
278
  })
249
279
  })
250
280
  )
@@ -254,7 +284,7 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
254
284
  .length
255
285
 
256
286
  if (this.running && responded < oldContacts.length && this.kb != null) {
257
- this.log('adding new contact %p', newContact.peer)
287
+ this.log('adding new contact %p', newContact.peerId)
258
288
  this.kb.add(newContact)
259
289
  }
260
290
  }
@@ -277,20 +307,14 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
277
307
  */
278
308
  async find (peer: PeerId): Promise<PeerId | undefined> {
279
309
  const key = await utils.convertPeerId(peer)
280
- const closest = this.closestPeer(key)
281
-
282
- if (closest != null && peer.equals(closest)) {
283
- return closest
284
- }
285
-
286
- return undefined
310
+ return this.kb?.get(key)?.peerId
287
311
  }
288
312
 
289
313
  /**
290
- * Retrieve the closest peers to the given key
314
+ * Retrieve the closest peers to the given kadId
291
315
  */
292
- closestPeer (key: Uint8Array): PeerId | undefined {
293
- const res = this.closestPeers(key, 1)
316
+ closestPeer (kadId: Uint8Array): PeerId | undefined {
317
+ const res = this.closestPeers(kadId, 1)
294
318
 
295
319
  if (res.length > 0) {
296
320
  return res[0]
@@ -300,33 +324,31 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
300
324
  }
301
325
 
302
326
  /**
303
- * Retrieve the `count`-closest peers to the given key
327
+ * Retrieve the `count`-closest peers to the given kadId
304
328
  */
305
- closestPeers (key: Uint8Array, count = this.kBucketSize): PeerId[] {
329
+ closestPeers (kadId: Uint8Array, count = this.kBucketSize): PeerId[] {
306
330
  if (this.kb == null) {
307
331
  return []
308
332
  }
309
333
 
310
- const closest = this.kb.closest(key, count)
311
-
312
- return closest.map(p => p.peer)
334
+ return [...this.kb.closest(kadId, count)]
313
335
  }
314
336
 
315
337
  /**
316
338
  * Add or update the routing table with the given peer
317
339
  */
318
- async add (peer: PeerId): Promise<void> {
340
+ async add (peerId: PeerId): Promise<void> {
319
341
  if (this.kb == null) {
320
342
  throw new Error('RoutingTable is not started')
321
343
  }
322
344
 
323
- const id = await utils.convertPeerId(peer)
345
+ const kadId = await utils.convertPeerId(peerId)
324
346
 
325
- this.kb.add({ id, peer })
347
+ this.kb.add({ kadId, peerId })
326
348
 
327
- this.log('added %p with kad id %b', peer, id)
349
+ this.log('added %p with kad id %b', peerId, kadId)
328
350
 
329
- this.metrics?.routingTableSize.update(this.size)
351
+ this.updateMetrics()
330
352
  }
331
353
 
332
354
  /**
@@ -341,6 +363,38 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
341
363
 
342
364
  this.kb.remove(id)
343
365
 
344
- this.metrics?.routingTableSize.update(this.size)
366
+ this.updateMetrics()
367
+ }
368
+
369
+ private updateMetrics (): void {
370
+ if (this.metrics == null || this.kb == null) {
371
+ return
372
+ }
373
+
374
+ let size = 0
375
+ let buckets = 0
376
+ let maxDepth = 0
377
+
378
+ function count (bucket: Bucket): void {
379
+ if (isLeafBucket(bucket)) {
380
+ if (bucket.depth > maxDepth) {
381
+ maxDepth = bucket.depth
382
+ }
383
+
384
+ buckets++
385
+ size += bucket.peers.length
386
+ return
387
+ }
388
+
389
+ count(bucket.left)
390
+ count(bucket.right)
391
+ }
392
+
393
+ count(this.kb.root)
394
+
395
+ this.metrics.routingTableSize.update(size)
396
+ this.metrics.routingTableKadBucketTotal.update(buckets)
397
+ this.metrics.routingTableKadBucketAverageOccupancy.update(Math.round(size / buckets))
398
+ this.metrics.routingTableKadBucketMaxDepth.update(maxDepth)
345
399
  }
346
400
  }