@libp2p/kad-dht 14.0.2-d34642db1 → 14.1.0-a5cd8cfbe

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) 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/package.json +12 -14
  67. package/src/constants.ts +11 -5
  68. package/src/content-fetching/index.ts +5 -3
  69. package/src/index.ts +116 -2
  70. package/src/kad-dht.ts +94 -41
  71. package/src/network.ts +5 -5
  72. package/src/peer-routing/index.ts +9 -11
  73. package/src/providers.ts +57 -244
  74. package/src/query/manager.ts +12 -28
  75. package/src/query-self.ts +20 -17
  76. package/src/reprovider.ts +226 -0
  77. package/src/routing-table/closest-peers.ts +9 -0
  78. package/src/routing-table/index.ts +16 -11
  79. package/src/rpc/handlers/get-value.ts +3 -1
  80. package/src/rpc/handlers/put-value.ts +3 -1
  81. package/src/rpc/index.ts +10 -10
  82. 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
+ }