@libp2p/kad-dht 14.0.2 → 14.1.0

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 (83) hide show
  1. package/README.md +36 -0
  2. package/dist/index.min.js +2 -2
  3. package/dist/src/constants.d.ts +4 -3
  4. package/dist/src/constants.d.ts.map +1 -1
  5. package/dist/src/constants.js +8 -3
  6. package/dist/src/constants.js.map +1 -1
  7. package/dist/src/content-fetching/index.d.ts +1 -0
  8. package/dist/src/content-fetching/index.d.ts.map +1 -1
  9. package/dist/src/content-fetching/index.js +5 -3
  10. package/dist/src/content-fetching/index.js.map +1 -1
  11. package/dist/src/index.d.ts +107 -2
  12. package/dist/src/index.d.ts.map +1 -1
  13. package/dist/src/index.js +36 -0
  14. package/dist/src/index.js.map +1 -1
  15. package/dist/src/kad-dht.d.ts +14 -1
  16. package/dist/src/kad-dht.d.ts.map +1 -1
  17. package/dist/src/kad-dht.js +76 -27
  18. package/dist/src/kad-dht.js.map +1 -1
  19. package/dist/src/network.d.ts +1 -0
  20. package/dist/src/network.d.ts.map +1 -1
  21. package/dist/src/network.js +4 -5
  22. package/dist/src/network.js.map +1 -1
  23. package/dist/src/peer-routing/index.d.ts.map +1 -1
  24. package/dist/src/peer-routing/index.js +9 -10
  25. package/dist/src/peer-routing/index.js.map +1 -1
  26. package/dist/src/providers.d.ts +21 -50
  27. package/dist/src/providers.d.ts.map +1 -1
  28. package/dist/src/providers.js +51 -192
  29. package/dist/src/providers.js.map +1 -1
  30. package/dist/src/query/manager.d.ts +1 -1
  31. package/dist/src/query/manager.d.ts.map +1 -1
  32. package/dist/src/query/manager.js +8 -19
  33. package/dist/src/query/manager.js.map +1 -1
  34. package/dist/src/query-self.d.ts +3 -1
  35. package/dist/src/query-self.d.ts.map +1 -1
  36. package/dist/src/query-self.js +17 -16
  37. package/dist/src/query-self.js.map +1 -1
  38. package/dist/src/reprovider.d.ts +63 -0
  39. package/dist/src/reprovider.d.ts.map +1 -0
  40. package/dist/src/reprovider.js +161 -0
  41. package/dist/src/reprovider.js.map +1 -0
  42. package/dist/src/routing-table/closest-peers.d.ts +1 -0
  43. package/dist/src/routing-table/closest-peers.d.ts.map +1 -1
  44. package/dist/src/routing-table/closest-peers.js +7 -0
  45. package/dist/src/routing-table/closest-peers.js.map +1 -1
  46. package/dist/src/routing-table/index.d.ts +1 -0
  47. package/dist/src/routing-table/index.d.ts.map +1 -1
  48. package/dist/src/routing-table/index.js +14 -11
  49. package/dist/src/routing-table/index.js.map +1 -1
  50. package/dist/src/rpc/handlers/get-value.d.ts +1 -0
  51. package/dist/src/rpc/handlers/get-value.d.ts.map +1 -1
  52. package/dist/src/rpc/handlers/get-value.js +3 -1
  53. package/dist/src/rpc/handlers/get-value.js.map +1 -1
  54. package/dist/src/rpc/handlers/put-value.d.ts +1 -0
  55. package/dist/src/rpc/handlers/put-value.d.ts.map +1 -1
  56. package/dist/src/rpc/handlers/put-value.js +3 -1
  57. package/dist/src/rpc/handlers/put-value.js.map +1 -1
  58. package/dist/src/rpc/index.d.ts +1 -0
  59. package/dist/src/rpc/index.d.ts.map +1 -1
  60. package/dist/src/rpc/index.js +9 -10
  61. package/dist/src/rpc/index.js.map +1 -1
  62. package/dist/src/utils.d.ts +20 -1
  63. package/dist/src/utils.d.ts.map +1 -1
  64. package/dist/src/utils.js +87 -4
  65. package/dist/src/utils.js.map +1 -1
  66. package/dist/typedoc-urls.json +3 -0
  67. package/package.json +11 -13
  68. package/src/constants.ts +11 -5
  69. package/src/content-fetching/index.ts +5 -3
  70. package/src/index.ts +116 -2
  71. package/src/kad-dht.ts +94 -41
  72. package/src/network.ts +5 -5
  73. package/src/peer-routing/index.ts +9 -11
  74. package/src/providers.ts +57 -244
  75. package/src/query/manager.ts +12 -28
  76. package/src/query-self.ts +20 -17
  77. package/src/reprovider.ts +226 -0
  78. package/src/routing-table/closest-peers.ts +9 -0
  79. package/src/routing-table/index.ts +16 -11
  80. package/src/rpc/handlers/get-value.ts +3 -1
  81. package/src/rpc/handlers/put-value.ts +3 -1
  82. package/src/rpc/index.ts +10 -10
  83. package/src/utils.ts +102 -4
@@ -0,0 +1,226 @@
1
+ import { TypedEventEmitter, setMaxListeners } from '@libp2p/interface'
2
+ import { AdaptiveTimeout } from '@libp2p/utils/adaptive-timeout'
3
+ import { Queue } from '@libp2p/utils/queue'
4
+ import drain from 'it-drain'
5
+ import { PROVIDERS_VALIDITY, REPROVIDE_CONCURRENCY, REPROVIDE_INTERVAL, REPROVIDE_MAX_QUEUE_SIZE, REPROVIDE_THRESHOLD } from './constants.js'
6
+ import { parseProviderKey, readProviderTime, timeOperationMethod } from './utils.js'
7
+ import type { ContentRouting } from './content-routing/index.js'
8
+ import type { OperationMetrics } from './kad-dht.js'
9
+ import type { AbortOptions, ComponentLogger, Logger, Metrics, PeerId } from '@libp2p/interface'
10
+ import type { AddressManager } from '@libp2p/interface-internal'
11
+ import type { AdaptiveTimeoutInit } from '@libp2p/utils/adaptive-timeout'
12
+ import type { Datastore } from 'interface-datastore'
13
+ import type { Mortice } from 'mortice'
14
+ import type { CID } from 'multiformats/cid'
15
+
16
+ export interface ReproviderComponents {
17
+ peerId: PeerId
18
+ datastore: Datastore
19
+ logger: ComponentLogger
20
+ addressManager: AddressManager
21
+ metrics?: Metrics
22
+ }
23
+
24
+ export interface ReproviderInit {
25
+ logPrefix: string
26
+ metricsPrefix: string
27
+ datastorePrefix: string
28
+ contentRouting: ContentRouting
29
+ lock: Mortice
30
+ operationMetrics: OperationMetrics
31
+ concurrency?: number
32
+ maxQueueSize?: number
33
+ threshold?: number
34
+ validity?: number
35
+ interval?: number
36
+ timeout?: Omit<AdaptiveTimeoutInit, 'metricsName' | 'metrics'>
37
+ }
38
+
39
+ interface QueueJobOptions extends AbortOptions {
40
+ cid: CID
41
+ }
42
+
43
+ interface ReprovideEvents {
44
+ 'reprovide:start': CustomEvent
45
+ 'reprovide:end': CustomEvent
46
+ }
47
+
48
+ export class Reprovider extends TypedEventEmitter<ReprovideEvents> {
49
+ private readonly log: Logger
50
+ private readonly reprovideQueue: Queue<void, QueueJobOptions>
51
+ private readonly maxQueueSize: number
52
+ private readonly datastore: Datastore
53
+ private timeout?: ReturnType<typeof setTimeout>
54
+ private readonly reprovideTimeout: AdaptiveTimeout
55
+ private running: boolean
56
+ private shutdownController?: AbortController
57
+ private readonly reprovideThreshold: number
58
+ private readonly contentRouting: ContentRouting
59
+ private readonly datastorePrefix: string
60
+ private readonly addressManager: AddressManager
61
+ private readonly validity: number
62
+ private readonly interval: number
63
+ private readonly lock: Mortice
64
+ private readonly peerId: PeerId
65
+
66
+ constructor (components: ReproviderComponents, init: ReproviderInit) {
67
+ super()
68
+
69
+ this.log = components.logger.forComponent(`${init.logPrefix}:reprovider`)
70
+ this.peerId = components.peerId
71
+ this.reprovideQueue = new Queue({
72
+ concurrency: init.concurrency ?? REPROVIDE_CONCURRENCY,
73
+ metrics: components.metrics,
74
+ metricName: `${init.metricsPrefix}_reprovide_queue`
75
+ })
76
+ this.reprovideTimeout = new AdaptiveTimeout({
77
+ ...(init.timeout ?? {}),
78
+ metrics: components.metrics,
79
+ metricName: `${init.metricsPrefix}_reprovide_timeout_milliseconds`
80
+ })
81
+ this.datastore = components.datastore
82
+ this.addressManager = components.addressManager
83
+ this.datastorePrefix = `/${init.datastorePrefix}/provider`
84
+ this.reprovideThreshold = init.threshold ?? REPROVIDE_THRESHOLD
85
+ this.maxQueueSize = init.maxQueueSize ?? REPROVIDE_MAX_QUEUE_SIZE
86
+ this.validity = init.validity ?? PROVIDERS_VALIDITY
87
+ this.interval = init.interval ?? REPROVIDE_INTERVAL
88
+ this.contentRouting = init.contentRouting
89
+ this.lock = init.lock
90
+ this.running = false
91
+
92
+ this.reprovide = timeOperationMethod(this.reprovide.bind(this), init.operationMetrics, 'PROVIDE')
93
+ }
94
+
95
+ start (): void {
96
+ if (this.running) {
97
+ return
98
+ }
99
+
100
+ this.running = true
101
+
102
+ this.shutdownController = new AbortController()
103
+ setMaxListeners(Infinity, this.shutdownController.signal)
104
+
105
+ this.timeout = setTimeout(() => {
106
+ this.cleanUp().catch(err => {
107
+ this.log.error('error running reprovide/cleanup - %e', err)
108
+ })
109
+ }, this.interval)
110
+ }
111
+
112
+ stop (): void {
113
+ this.running = false
114
+ this.reprovideQueue.clear()
115
+ clearTimeout(this.timeout)
116
+ this.shutdownController?.abort()
117
+ }
118
+
119
+ /**
120
+ * Check all provider records. Delete them if they have expired, reprovide
121
+ * them if the provider is us and the expiry is within the reprovide window.
122
+ */
123
+ private async cleanUp (): Promise<void> {
124
+ const release = await this.lock.writeLock()
125
+
126
+ try {
127
+ this.safeDispatchEvent('reprovide:start')
128
+
129
+ // Get all provider entries from the datastore
130
+ for await (const entry of this.datastore.query({
131
+ prefix: this.datastorePrefix
132
+ })) {
133
+ try {
134
+ // Add a delete to the batch for each expired entry
135
+ const { cid, peerId } = parseProviderKey(entry.key)
136
+ const created = readProviderTime(entry.value).getTime()
137
+ const expires = created + this.validity
138
+ const now = Date.now()
139
+ const expired = now > expires
140
+
141
+ this.log.trace('comparing: %d < %d = %s %s', created, now - this.validity, expired, expired ? '(expired)' : '')
142
+
143
+ // delete the record if it has expired
144
+ if (expired) {
145
+ await this.datastore.delete(entry.key)
146
+ }
147
+
148
+ // if the provider is us and we are within the reprovide threshold,
149
+ // reprovide the record
150
+ if (this.peerId.equals(peerId) && (now - expires) < this.reprovideThreshold) {
151
+ this.queueReprovide(cid)
152
+ .catch(err => {
153
+ this.log.error('could not reprovide %c - %e', cid, err)
154
+ })
155
+ }
156
+ } catch (err: any) {
157
+ this.log.error('error processing datastore key %s - %e', entry.key, err.message)
158
+ }
159
+ }
160
+
161
+ this.log('reprovide/cleanup successful')
162
+ } finally {
163
+ release()
164
+ this.safeDispatchEvent('reprovide:end')
165
+
166
+ if (this.running) {
167
+ this.timeout = setTimeout(() => {
168
+ this.cleanUp().catch(err => {
169
+ this.log.error('error running re-provide - %e', err)
170
+ })
171
+ }, this.interval)
172
+ }
173
+ }
174
+ }
175
+
176
+ private async queueReprovide (cid: CID): Promise<void> {
177
+ if (!this.running) {
178
+ return
179
+ }
180
+
181
+ this.log.trace('waiting for queue capacity before adding %c to re-provide queue', cid)
182
+ await this.reprovideQueue.onSizeLessThan(this.maxQueueSize)
183
+
184
+ const existingJob = this.reprovideQueue.queue.find(job => job.options.cid.equals(cid))
185
+
186
+ if (existingJob != null) {
187
+ this.log.trace('not adding %c to re-provide queue - already in queue', cid)
188
+ return existingJob.join()
189
+ }
190
+
191
+ this.log.trace('adding %c to re-provide queue', cid)
192
+
193
+ this.reprovideQueue.add(async (options): Promise<void> => {
194
+ options.signal?.throwIfAborted()
195
+
196
+ if (!this.running) {
197
+ return
198
+ }
199
+
200
+ this.log.trace('re-providing %c', cid)
201
+
202
+ // use adaptive timeout
203
+ const signal = this.reprovideTimeout.getTimeoutSignal(options)
204
+
205
+ try {
206
+ // reprovide
207
+ await this.reprovide(options.cid, options)
208
+ } finally {
209
+ this.reprovideTimeout.cleanUp(signal)
210
+ }
211
+
212
+ this.log.trace('re-provided %c', cid)
213
+ }, {
214
+ signal: this.shutdownController?.signal,
215
+ cid
216
+ })
217
+ .catch(err => {
218
+ this.log.error('could not re-provide key %c - %e', cid, err)
219
+ })
220
+ }
221
+
222
+ private async reprovide (cid: CID, options?: AbortOptions): Promise<void> {
223
+ // reprovide
224
+ await drain(this.contentRouting.provide(cid, this.addressManager.getAddresses(), options))
225
+ }
226
+ }
@@ -42,6 +42,7 @@ export class ClosestPeers implements Startable {
42
42
  private readonly closeTagName: string
43
43
  private readonly closeTagValue: number
44
44
  private readonly log: Logger
45
+ private running: boolean
45
46
 
46
47
  constructor (components: ClosestPeersComponents, init: ClosestPeersInit) {
47
48
  this.components = components
@@ -54,9 +55,16 @@ export class ClosestPeers implements Startable {
54
55
 
55
56
  this.closestPeers = new PeerSet()
56
57
  this.onPeerPing = this.onPeerPing.bind(this)
58
+ this.running = false
57
59
  }
58
60
 
59
61
  async start (): Promise<void> {
62
+ if (this.running) {
63
+ return
64
+ }
65
+
66
+ this.running = true
67
+
60
68
  const targetKadId = await convertPeerId(this.components.peerId)
61
69
  this.newPeers = new PeerDistanceList(targetKadId, this.peerSetSize)
62
70
  this.routingTable.addEventListener('peer:ping', this.onPeerPing)
@@ -70,6 +78,7 @@ export class ClosestPeers implements Startable {
70
78
  }
71
79
 
72
80
  stop (): void {
81
+ this.running = false
73
82
  this.routingTable.removeEventListener('peer:ping', this.onPeerPing)
74
83
  clearTimeout(this.timeout)
75
84
  }
@@ -30,6 +30,7 @@ export const POPULATE_FROM_DATASTORE_LIMIT = 1000
30
30
 
31
31
  export interface RoutingTableInit {
32
32
  logPrefix: string
33
+ metricsPrefix: string
33
34
  protocol: string
34
35
  prefixLength?: number
35
36
  splitThreshold?: number
@@ -116,26 +117,26 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
116
117
 
117
118
  this.pingOldContactQueue = new PeerQueue({
118
119
  concurrency: init.pingOldContactConcurrency ?? PING_OLD_CONTACT_CONCURRENCY,
119
- metricName: `${init.logPrefix.replaceAll(':', '_')}_ping_old_contact_queue`,
120
+ metricName: `${init.metricsPrefix}_ping_old_contact_queue`,
120
121
  metrics: this.components.metrics,
121
122
  maxSize: init.pingOldContactMaxQueueSize ?? PING_OLD_CONTACT_MAX_QUEUE_SIZE
122
123
  })
123
124
  this.pingOldContactTimeout = new AdaptiveTimeout({
124
125
  ...(init.pingOldContactTimeout ?? {}),
125
126
  metrics: this.components.metrics,
126
- metricName: `${init.logPrefix.replaceAll(':', '_')}_routing_table_ping_old_contact_time_milliseconds`
127
+ metricName: `${init.metricsPrefix}_routing_table_ping_old_contact_time_milliseconds`
127
128
  })
128
129
 
129
130
  this.pingNewContactQueue = new PeerQueue({
130
131
  concurrency: init.pingNewContactConcurrency ?? PING_NEW_CONTACT_CONCURRENCY,
131
- metricName: `${init.logPrefix.replaceAll(':', '_')}_ping_new_contact_queue`,
132
+ metricName: `${init.metricsPrefix}_ping_new_contact_queue`,
132
133
  metrics: this.components.metrics,
133
134
  maxSize: init.pingNewContactMaxQueueSize ?? PING_NEW_CONTACT_MAX_QUEUE_SIZE
134
135
  })
135
136
  this.pingNewContactTimeout = new AdaptiveTimeout({
136
137
  ...(init.pingNewContactTimeout ?? {}),
137
138
  metrics: this.components.metrics,
138
- metricName: `${init.logPrefix.replaceAll(':', '_')}_routing_table_ping_new_contact_time_milliseconds`
139
+ metricName: `${init.metricsPrefix}_routing_table_ping_new_contact_time_milliseconds`
139
140
  })
140
141
 
141
142
  this.kb = new KBucket({
@@ -161,13 +162,13 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
161
162
 
162
163
  if (this.components.metrics != null) {
163
164
  this.metrics = {
164
- routingTableSize: this.components.metrics.registerMetric(`${init.logPrefix.replaceAll(':', '_')}_routing_table_size`),
165
- routingTableKadBucketTotal: this.components.metrics.registerMetric(`${init.logPrefix.replaceAll(':', '_')}_routing_table_kad_bucket_total`),
166
- routingTableKadBucketAverageOccupancy: this.components.metrics.registerMetric(`${init.logPrefix.replaceAll(':', '_')}_routing_table_kad_bucket_average_occupancy`),
167
- routingTableKadBucketMinOccupancy: this.components.metrics.registerMetric(`${init.logPrefix.replaceAll(':', '_')}_routing_table_kad_bucket_min_occupancy`),
168
- routingTableKadBucketMaxOccupancy: this.components.metrics.registerMetric(`${init.logPrefix.replaceAll(':', '_')}_routing_table_kad_bucket_max_occupancy`),
169
- routingTableKadBucketMaxDepth: this.components.metrics.registerMetric(`${init.logPrefix.replaceAll(':', '_')}_routing_table_kad_bucket_max_depth`),
170
- kadBucketEvents: this.components.metrics.registerCounterGroup(`${init.logPrefix.replaceAll(':', '_')}_kad_bucket_events_total`)
165
+ routingTableSize: this.components.metrics.registerMetric(`${init.metricsPrefix}_routing_table_size`),
166
+ routingTableKadBucketTotal: this.components.metrics.registerMetric(`${init.metricsPrefix}_routing_table_kad_bucket_total`),
167
+ routingTableKadBucketAverageOccupancy: this.components.metrics.registerMetric(`${init.metricsPrefix}_routing_table_kad_bucket_average_occupancy`),
168
+ routingTableKadBucketMinOccupancy: this.components.metrics.registerMetric(`${init.metricsPrefix}_routing_table_kad_bucket_min_occupancy`),
169
+ routingTableKadBucketMaxOccupancy: this.components.metrics.registerMetric(`${init.metricsPrefix}_routing_table_kad_bucket_max_occupancy`),
170
+ routingTableKadBucketMaxDepth: this.components.metrics.registerMetric(`${init.metricsPrefix}_routing_table_kad_bucket_max_depth`),
171
+ kadBucketEvents: this.components.metrics.registerCounterGroup(`${init.metricsPrefix}_kad_bucket_events_total`)
171
172
  }
172
173
  }
173
174
  }
@@ -177,6 +178,10 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
177
178
  }
178
179
 
179
180
  async start (): Promise<void> {
181
+ if (this.running) {
182
+ return
183
+ }
184
+
180
185
  this.running = true
181
186
 
182
187
  await start(this.closestPeerTagger)
@@ -28,9 +28,11 @@ export class GetValueHandler implements DHTMessageHandler {
28
28
  private readonly datastore: Datastore
29
29
  private readonly peerRouting: PeerRouting
30
30
  private readonly log: Logger
31
+ private readonly datastorePrefix: string
31
32
 
32
33
  constructor (components: GetValueHandlerComponents, init: GetValueHandlerInit) {
33
34
  this.log = components.logger.forComponent(`${init.logPrefix}:rpc:handlers:get-value`)
35
+ this.datastorePrefix = `/${init.logPrefix.replaceAll(':', '/')}/record`
34
36
  this.peerStore = components.peerStore
35
37
  this.datastore = components.datastore
36
38
  this.peerRouting = init.peerRouting
@@ -108,7 +110,7 @@ export class GetValueHandler implements DHTMessageHandler {
108
110
  */
109
111
  async _checkLocalDatastore (key: Uint8Array): Promise<Libp2pRecord | undefined> {
110
112
  this.log('checkLocalDatastore looking for %b', key)
111
- const dsKey = bufferToRecordKey(key)
113
+ const dsKey = bufferToRecordKey(this.datastorePrefix, key)
112
114
 
113
115
  // Fetch value from ds
114
116
  let rawRecord
@@ -22,12 +22,14 @@ export class PutValueHandler implements DHTMessageHandler {
22
22
  private readonly components: PutValueHandlerComponents
23
23
  private readonly validators: Validators
24
24
  private readonly log: Logger
25
+ private readonly datastorePrefix: string
25
26
 
26
27
  constructor (components: PutValueHandlerComponents, init: PutValueHandlerInit) {
27
28
  const { validators } = init
28
29
 
29
30
  this.components = components
30
31
  this.log = components.logger.forComponent(`${init.logPrefix}:rpc:handlers:put-value`)
32
+ this.datastorePrefix = `/${init.logPrefix.replaceAll(':', '/')}/record`
31
33
  this.validators = validators
32
34
  }
33
35
 
@@ -48,7 +50,7 @@ export class PutValueHandler implements DHTMessageHandler {
48
50
  await verifyRecord(this.validators, deserializedRecord)
49
51
 
50
52
  deserializedRecord.timeReceived = new Date()
51
- const recordKey = bufferToRecordKey(deserializedRecord.key)
53
+ const recordKey = bufferToRecordKey(this.datastorePrefix, deserializedRecord.key)
52
54
  await this.components.datastore.put(recordKey, deserializedRecord.serialize().subarray())
53
55
  this.log('put record for %b into datastore under key %k', key, recordKey)
54
56
  } catch (err: any) {
package/src/rpc/index.ts CHANGED
@@ -24,6 +24,7 @@ export interface RPCInit {
24
24
  peerRouting: PeerRouting
25
25
  validators: Validators
26
26
  logPrefix: string
27
+ metricsPrefix: string
27
28
  peerInfoMapper: PeerInfoMapper
28
29
  }
29
30
 
@@ -41,21 +42,20 @@ export class RPC {
41
42
  }
42
43
 
43
44
  constructor (components: RPCComponents, init: RPCInit) {
44
- const { providers, peerRouting, validators, logPrefix, peerInfoMapper } = init
45
45
  this.metrics = {
46
- operations: components.metrics?.registerCounterGroup(`${logPrefix.replaceAll(':', '_')}_inbound_rpc_requests_total`),
47
- errors: components.metrics?.registerCounterGroup(`${logPrefix.replaceAll(':', '_')}_inbound_rpc_errors_total`)
46
+ operations: components.metrics?.registerCounterGroup(`${init.metricsPrefix}_inbound_rpc_requests_total`),
47
+ errors: components.metrics?.registerCounterGroup(`${init.metricsPrefix}_inbound_rpc_errors_total`)
48
48
  }
49
49
 
50
- this.log = components.logger.forComponent(`${logPrefix}:rpc`)
50
+ this.log = components.logger.forComponent(`${init.logPrefix}:rpc`)
51
51
  this.routingTable = init.routingTable
52
52
  this.handlers = {
53
- [MessageType.GET_VALUE.toString()]: new GetValueHandler(components, { peerRouting, logPrefix }),
54
- [MessageType.PUT_VALUE.toString()]: new PutValueHandler(components, { validators, logPrefix }),
55
- [MessageType.FIND_NODE.toString()]: new FindNodeHandler(components, { peerRouting, logPrefix, peerInfoMapper }),
56
- [MessageType.ADD_PROVIDER.toString()]: new AddProviderHandler(components, { providers, logPrefix }),
57
- [MessageType.GET_PROVIDERS.toString()]: new GetProvidersHandler(components, { peerRouting, providers, logPrefix, peerInfoMapper }),
58
- [MessageType.PING.toString()]: new PingHandler(components, { logPrefix })
53
+ [MessageType.GET_VALUE.toString()]: new GetValueHandler(components, init),
54
+ [MessageType.PUT_VALUE.toString()]: new PutValueHandler(components, init),
55
+ [MessageType.FIND_NODE.toString()]: new FindNodeHandler(components, init),
56
+ [MessageType.ADD_PROVIDER.toString()]: new AddProviderHandler(components, init),
57
+ [MessageType.GET_PROVIDERS.toString()]: new GetProvidersHandler(components, init),
58
+ [MessageType.PING.toString()]: new PingHandler(components, init)
59
59
  }
60
60
  }
61
61
 
package/src/utils.ts CHANGED
@@ -1,13 +1,16 @@
1
- import { peerIdFromMultihash } from '@libp2p/peer-id'
1
+ import { peerIdFromMultihash, peerIdFromString } from '@libp2p/peer-id'
2
2
  import { Libp2pRecord } from '@libp2p/record'
3
3
  import { isPrivateIp } from '@libp2p/utils/private-ip'
4
4
  import { Key } from 'interface-datastore/key'
5
+ import { CID } from 'multiformats/cid'
6
+ import * as raw from 'multiformats/codecs/raw'
5
7
  import * as Digest from 'multiformats/hashes/digest'
6
8
  import { sha256 } from 'multiformats/hashes/sha2'
9
+ import * as varint from 'uint8-varint'
7
10
  import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
8
11
  import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
9
12
  import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
10
- import { RECORD_KEY_PREFIX } from './constants.js'
13
+ import type { Operation, OperationMetrics } from './kad-dht.js'
11
14
  import type { PeerId, PeerInfo } from '@libp2p/interface'
12
15
  import type { Multiaddr } from '@multiformats/multiaddr'
13
16
 
@@ -110,8 +113,8 @@ export function bufferToKey (buf: Uint8Array): Key {
110
113
  /**
111
114
  * Convert a Uint8Array to their SHA2-256 hash
112
115
  */
113
- export function bufferToRecordKey (buf: Uint8Array): Key {
114
- return new Key(`${RECORD_KEY_PREFIX}/${uint8ArrayToString(buf, 'base32')}`, false)
116
+ export function bufferToRecordKey (prefix: string, buf: Uint8Array): Key {
117
+ return new Key(`${prefix}/${uint8ArrayToString(buf, 'base32')}`, false)
115
118
  }
116
119
 
117
120
  /**
@@ -189,3 +192,98 @@ export function multiaddrIsPublic (multiaddr: Multiaddr): boolean {
189
192
 
190
193
  return false
191
194
  }
195
+
196
+ /**
197
+ * Parse the CID and provider peer id from the key
198
+ */
199
+ export function parseProviderKey (key: Key): { cid: CID, peerId: PeerId } {
200
+ const parts = key.toString().split('/')
201
+ const peerIdStr = parts.pop()
202
+ const cidStr = parts.pop()
203
+
204
+ if (peerIdStr == null || cidStr == null) {
205
+ throw new Error(`incorrectly formatted provider entry key in datastore: ${key.toString()}`)
206
+ }
207
+
208
+ return {
209
+ cid: CID.createV1(raw.code, Digest.decode(uint8ArrayFromString(cidStr, 'base32'))),
210
+ peerId: peerIdFromString(peerIdStr)
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Encode the given key its matching datastore key
216
+ */
217
+ export function toProviderKey (prefix: string, cid: CID | string, peerId?: PeerId): Key {
218
+ const cidStr = typeof cid === 'string' ? cid : uint8ArrayToString(cid.multihash.bytes, 'base32')
219
+
220
+ const parts = [
221
+ prefix,
222
+ cidStr
223
+ ]
224
+
225
+ if (peerId != null) {
226
+ parts.push(peerId.toString())
227
+ }
228
+
229
+ return new Key(parts.join('/'))
230
+ }
231
+
232
+ export function readProviderTime (buf: Uint8Array): Date {
233
+ return new Date(varint.decode(buf))
234
+ }
235
+
236
+ /**
237
+ * Wraps the passed generator function with timing metrics
238
+ */
239
+ export function timeOperationGenerator (fn: (...args: any[]) => AsyncGenerator<any>, operationMetrics: OperationMetrics, type: Operation): (...args: any[]) => AsyncGenerator<any> {
240
+ return async function * (...args: any[]): AsyncGenerator<any> {
241
+ const stopSuccessTimer = operationMetrics.queryTime?.timer(type)
242
+ const stopErrorTimer = operationMetrics.errorTime?.timer(type)
243
+ let errored = false
244
+
245
+ try {
246
+ operationMetrics.queries?.increment({ [type]: true })
247
+
248
+ yield * fn(...args)
249
+ } catch (err) {
250
+ errored = true
251
+ stopErrorTimer?.()
252
+ operationMetrics.errors?.increment({ [type]: true })
253
+
254
+ throw err
255
+ } finally {
256
+ operationMetrics.queries?.decrement({ [type]: true })
257
+
258
+ if (!errored) {
259
+ stopSuccessTimer?.()
260
+ }
261
+ }
262
+ }
263
+ }
264
+
265
+ export function timeOperationMethod (fn: (...args: any[]) => Promise<any>, operationMetrics: OperationMetrics, type: Operation): (...args: any[]) => Promise<any> {
266
+ return async function (...args: any[]): Promise<any> {
267
+ const stopSuccessTimer = operationMetrics?.queryTime?.timer(type)
268
+ const stopErrorTimer = operationMetrics?.errorTime?.timer(type)
269
+ let errored = false
270
+
271
+ try {
272
+ operationMetrics.queries?.increment({ [type]: true })
273
+
274
+ return await fn(...args)
275
+ } catch (err) {
276
+ errored = true
277
+ stopErrorTimer?.()
278
+ operationMetrics.errors?.increment({ [type]: true })
279
+
280
+ throw err
281
+ } finally {
282
+ operationMetrics.queries?.decrement({ [type]: true })
283
+
284
+ if (!errored) {
285
+ stopSuccessTimer?.()
286
+ }
287
+ }
288
+ }
289
+ }