@libp2p/kad-dht 12.0.15 → 12.0.16-1cd5aae11

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 (59) 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/network.d.ts +3 -0
  12. package/dist/src/network.d.ts.map +1 -1
  13. package/dist/src/network.js +33 -8
  14. package/dist/src/network.js.map +1 -1
  15. package/dist/src/peer-list/peer-distance-list.d.ts +13 -4
  16. package/dist/src/peer-list/peer-distance-list.d.ts.map +1 -1
  17. package/dist/src/peer-list/peer-distance-list.js +29 -21
  18. package/dist/src/peer-list/peer-distance-list.js.map +1 -1
  19. package/dist/src/peer-routing/index.d.ts +5 -5
  20. package/dist/src/peer-routing/index.d.ts.map +1 -1
  21. package/dist/src/peer-routing/index.js +15 -24
  22. package/dist/src/peer-routing/index.js.map +1 -1
  23. package/dist/src/query/manager.d.ts +3 -0
  24. package/dist/src/query/manager.d.ts.map +1 -1
  25. package/dist/src/query/manager.js +14 -5
  26. package/dist/src/query/manager.js.map +1 -1
  27. package/dist/src/query/query-path.d.ts +6 -6
  28. package/dist/src/query/query-path.d.ts.map +1 -1
  29. package/dist/src/query/query-path.js +32 -20
  30. package/dist/src/query/query-path.js.map +1 -1
  31. package/dist/src/routing-table/index.d.ts +11 -5
  32. package/dist/src/routing-table/index.d.ts.map +1 -1
  33. package/dist/src/routing-table/index.js +84 -42
  34. package/dist/src/routing-table/index.js.map +1 -1
  35. package/dist/src/routing-table/k-bucket.d.ts +80 -115
  36. package/dist/src/routing-table/k-bucket.d.ts.map +1 -1
  37. package/dist/src/routing-table/k-bucket.js +165 -311
  38. package/dist/src/routing-table/k-bucket.js.map +1 -1
  39. package/dist/src/routing-table/refresh.d.ts.map +1 -1
  40. package/dist/src/routing-table/refresh.js +9 -4
  41. package/dist/src/routing-table/refresh.js.map +1 -1
  42. package/package.json +14 -16
  43. package/src/constants.ts +1 -1
  44. package/src/content-routing/index.ts +3 -2
  45. package/src/index.ts +37 -1
  46. package/src/network.ts +38 -9
  47. package/src/peer-list/peer-distance-list.ts +36 -25
  48. package/src/peer-routing/index.ts +19 -28
  49. package/src/query/manager.ts +18 -5
  50. package/src/query/query-path.ts +46 -30
  51. package/src/routing-table/index.ts +100 -46
  52. package/src/routing-table/k-bucket.ts +214 -359
  53. package/src/routing-table/refresh.ts +10 -4
  54. package/dist/src/query/utils.d.ts +0 -6
  55. package/dist/src/query/utils.d.ts.map +0 -1
  56. package/dist/src/query/utils.js +0 -53
  57. package/dist/src/query/utils.js.map +0 -1
  58. package/dist/typedoc-urls.json +0 -55
  59. package/src/query/utils.ts +0 -64
@@ -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
  }