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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/dist/index.min.js +2 -2
  2. package/dist/index.min.js.map +4 -4
  3. package/dist/src/constants.d.ts +2 -0
  4. package/dist/src/constants.d.ts.map +1 -1
  5. package/dist/src/constants.js +4 -0
  6. package/dist/src/constants.js.map +1 -1
  7. package/dist/src/content-fetching/index.d.ts +2 -2
  8. package/dist/src/content-fetching/index.d.ts.map +1 -1
  9. package/dist/src/content-fetching/index.js +6 -6
  10. package/dist/src/content-fetching/index.js.map +1 -1
  11. package/dist/src/content-routing/index.js +3 -3
  12. package/dist/src/content-routing/index.js.map +1 -1
  13. package/dist/src/errors.d.ts +0 -6
  14. package/dist/src/errors.d.ts.map +1 -1
  15. package/dist/src/errors.js +0 -9
  16. package/dist/src/errors.js.map +1 -1
  17. package/dist/src/index.d.ts +24 -5
  18. package/dist/src/index.d.ts.map +1 -1
  19. package/dist/src/index.js.map +1 -1
  20. package/dist/src/kad-dht.d.ts +8 -5
  21. package/dist/src/kad-dht.d.ts.map +1 -1
  22. package/dist/src/kad-dht.js +25 -18
  23. package/dist/src/kad-dht.js.map +1 -1
  24. package/dist/src/network.d.ts +1 -1
  25. package/dist/src/network.d.ts.map +1 -1
  26. package/dist/src/network.js +2 -1
  27. package/dist/src/network.js.map +1 -1
  28. package/dist/src/peer-distance-list.d.ts +4 -4
  29. package/dist/src/peer-distance-list.d.ts.map +1 -1
  30. package/dist/src/peer-distance-list.js +6 -6
  31. package/dist/src/peer-distance-list.js.map +1 -1
  32. package/dist/src/peer-routing/index.d.ts +6 -5
  33. package/dist/src/peer-routing/index.d.ts.map +1 -1
  34. package/dist/src/peer-routing/index.js +21 -32
  35. package/dist/src/peer-routing/index.js.map +1 -1
  36. package/dist/src/providers.d.ts +0 -3
  37. package/dist/src/providers.d.ts.map +1 -1
  38. package/dist/src/providers.js +11 -31
  39. package/dist/src/providers.js.map +1 -1
  40. package/dist/src/query/manager.d.ts.map +1 -1
  41. package/dist/src/query/manager.js +7 -3
  42. package/dist/src/query/manager.js.map +1 -1
  43. package/dist/src/query/query-path.d.ts.map +1 -1
  44. package/dist/src/query/query-path.js +11 -5
  45. package/dist/src/query/query-path.js.map +1 -1
  46. package/dist/src/query-self.d.ts.map +1 -1
  47. package/dist/src/query-self.js +2 -1
  48. package/dist/src/query-self.js.map +1 -1
  49. package/dist/src/record/validators.d.ts +2 -1
  50. package/dist/src/record/validators.d.ts.map +1 -1
  51. package/dist/src/record/validators.js +3 -3
  52. package/dist/src/record/validators.js.map +1 -1
  53. package/dist/src/reprovider.d.ts +1 -4
  54. package/dist/src/reprovider.d.ts.map +1 -1
  55. package/dist/src/reprovider.js +13 -13
  56. package/dist/src/reprovider.js.map +1 -1
  57. package/dist/src/routing-table/index.d.ts +6 -5
  58. package/dist/src/routing-table/index.d.ts.map +1 -1
  59. package/dist/src/routing-table/index.js +72 -44
  60. package/dist/src/routing-table/index.js.map +1 -1
  61. package/dist/src/routing-table/k-bucket.d.ts +20 -8
  62. package/dist/src/routing-table/k-bucket.d.ts.map +1 -1
  63. package/dist/src/routing-table/k-bucket.js +35 -16
  64. package/dist/src/routing-table/k-bucket.js.map +1 -1
  65. package/dist/src/routing-table/refresh.d.ts +5 -5
  66. package/dist/src/routing-table/refresh.d.ts.map +1 -1
  67. package/dist/src/routing-table/refresh.js +21 -15
  68. package/dist/src/routing-table/refresh.js.map +1 -1
  69. package/dist/src/rpc/handlers/find-node.d.ts.map +1 -1
  70. package/dist/src/rpc/handlers/find-node.js +40 -24
  71. package/dist/src/rpc/handlers/find-node.js.map +1 -1
  72. package/dist/src/rpc/handlers/get-providers.js +1 -1
  73. package/dist/src/rpc/handlers/get-providers.js.map +1 -1
  74. package/dist/src/rpc/handlers/get-value.js +1 -1
  75. package/dist/src/rpc/handlers/get-value.js.map +1 -1
  76. package/dist/src/rpc/index.d.ts +2 -1
  77. package/dist/src/rpc/index.d.ts.map +1 -1
  78. package/dist/src/rpc/index.js +50 -19
  79. package/dist/src/rpc/index.js.map +1 -1
  80. package/dist/src/topology-listener.d.ts +1 -1
  81. package/dist/src/topology-listener.d.ts.map +1 -1
  82. package/dist/src/topology-listener.js +1 -1
  83. package/dist/src/topology-listener.js.map +1 -1
  84. package/dist/src/utils.d.ts +3 -3
  85. package/dist/src/utils.d.ts.map +1 -1
  86. package/dist/src/utils.js +4 -3
  87. package/dist/src/utils.js.map +1 -1
  88. package/package.json +18 -18
  89. package/src/constants.ts +6 -0
  90. package/src/content-fetching/index.ts +7 -7
  91. package/src/content-routing/index.ts +3 -3
  92. package/src/errors.ts +0 -10
  93. package/src/index.ts +27 -5
  94. package/src/kad-dht.ts +28 -20
  95. package/src/network.ts +2 -1
  96. package/src/peer-distance-list.ts +7 -7
  97. package/src/peer-routing/index.ts +23 -34
  98. package/src/providers.ts +15 -33
  99. package/src/query/manager.ts +7 -3
  100. package/src/query/query-path.ts +11 -5
  101. package/src/query-self.ts +3 -1
  102. package/src/record/validators.ts +4 -3
  103. package/src/reprovider.ts +13 -16
  104. package/src/routing-table/index.ts +74 -45
  105. package/src/routing-table/k-bucket.ts +54 -20
  106. package/src/routing-table/refresh.ts +21 -16
  107. package/src/rpc/handlers/find-node.ts +41 -27
  108. package/src/rpc/handlers/get-providers.ts +1 -1
  109. package/src/rpc/handlers/get-value.ts +1 -1
  110. package/src/rpc/index.ts +54 -23
  111. package/src/topology-listener.ts +1 -1
  112. package/src/utils.ts +5 -4
  113. package/dist/typedoc-urls.json +0 -63
package/src/providers.ts CHANGED
@@ -3,13 +3,11 @@ import * as varint from 'uint8-varint'
3
3
  import { parseProviderKey, readProviderTime, toProviderKey } from './utils.js'
4
4
  import type { AbortOptions, ComponentLogger, Logger, Metrics, PeerId } from '@libp2p/interface'
5
5
  import type { Datastore } from 'interface-datastore'
6
- import type { Mortice } from 'mortice'
7
6
  import type { CID } from 'multiformats'
8
7
 
9
8
  export interface ProvidersInit {
10
9
  logPrefix: string
11
10
  datastorePrefix: string
12
- lock: Mortice
13
11
  }
14
12
 
15
13
  export interface ProvidersComponents {
@@ -18,6 +16,10 @@ export interface ProvidersComponents {
18
16
  metrics?: Metrics
19
17
  }
20
18
 
19
+ interface WriteProviderEntryOptions extends AbortOptions {
20
+ time?: Date
21
+ }
22
+
21
23
  /**
22
24
  * Provides a mechanism to add and remove provider records from the datastore
23
25
  */
@@ -25,67 +27,47 @@ export class Providers {
25
27
  private readonly log: Logger
26
28
  private readonly datastore: Datastore
27
29
  private readonly datastorePrefix: string
28
- private readonly lock: Mortice
29
30
 
30
31
  constructor (components: ProvidersComponents, init: ProvidersInit) {
31
32
  this.log = components.logger.forComponent(`${init.logPrefix}:providers`)
32
33
  this.datastorePrefix = `${init.datastorePrefix}/provider`
33
34
  this.datastore = components.datastore
34
- this.lock = init.lock
35
35
  }
36
36
 
37
37
  /**
38
38
  * Add a new provider for the given CID
39
39
  */
40
40
  async addProvider (cid: CID, provider: PeerId, options?: AbortOptions): Promise<void> {
41
- const release = await this.lock.readLock()
42
-
43
- try {
44
- this.log.trace('%p provides %s', provider, cid)
45
- await this.writeProviderEntry(cid, provider)
46
- } finally {
47
- release()
48
- }
41
+ this.log.trace('%p provides %s', provider, cid)
42
+ await this.writeProviderEntry(cid, provider, options)
49
43
  }
50
44
 
51
45
  /**
52
46
  * Remove a provider for the given CID
53
47
  */
54
48
  async removeProvider (cid: CID, provider: PeerId, options?: AbortOptions): Promise<void> {
55
- const release = await this.lock.writeLock()
56
-
57
- try {
58
- const key = toProviderKey(this.datastorePrefix, cid, provider)
59
- this.log.trace('%p no longer provides %s', provider, cid)
60
- await this.datastore.delete(key)
61
- } finally {
62
- release()
63
- }
49
+ const key = toProviderKey(this.datastorePrefix, cid, provider)
50
+ this.log.trace('%p no longer provides %s', provider, cid)
51
+ await this.datastore.delete(key, options)
64
52
  }
65
53
 
66
54
  /**
67
55
  * Get a list of providers for the given CID
68
56
  */
69
57
  async getProviders (cid: CID, options?: AbortOptions): Promise<PeerId[]> {
70
- const release = await this.lock.readLock()
58
+ this.log.trace('get providers for %c', cid)
59
+ const provs = await this.loadProviders(cid, options)
60
+ this.log.trace('got %d providers for %c', provs.size, cid)
71
61
 
72
- try {
73
- this.log.trace('get providers for %c', cid)
74
- const provs = await this.loadProviders(cid, options)
75
- this.log.trace('got %d providers for %c', provs.size, cid)
76
-
77
- return [...provs.keys()]
78
- } finally {
79
- release()
80
- }
62
+ return [...provs.keys()]
81
63
  }
82
64
 
83
65
  /**
84
66
  * Write a provider into the given store
85
67
  */
86
- private async writeProviderEntry (cid: CID, peerId: PeerId, time: Date = new Date(), options?: AbortOptions): Promise<void> {
68
+ private async writeProviderEntry (cid: CID, peerId: PeerId, options?: WriteProviderEntryOptions): Promise<void> {
87
69
  const key = toProviderKey(this.datastorePrefix, cid, peerId)
88
- const buffer = varint.encode(time.getTime())
70
+ const buffer = varint.encode(options?.time?.getTime() ?? Date.now())
89
71
 
90
72
  await this.datastore.put(key, buffer, options)
91
73
  }
@@ -1,7 +1,7 @@
1
- import { setMaxListeners } from '@libp2p/interface'
2
1
  import { createScalableCuckooFilter } from '@libp2p/utils/filters'
3
2
  import { anySignal } from 'any-signal'
4
3
  import merge from 'it-merge'
4
+ import { setMaxListeners } from 'main-event'
5
5
  import { pEvent } from 'p-event'
6
6
  import { raceSignal } from 'race-signal'
7
7
  import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
@@ -165,8 +165,12 @@ export class QueryManager implements Startable {
165
165
 
166
166
  log('query:start')
167
167
 
168
- const id = await convertBuffer(key)
169
- const peers = this.routingTable.closestPeers(id, this.routingTable.kBucketSize)
168
+ const id = await convertBuffer(key, {
169
+ signal
170
+ })
171
+ const peers = this.routingTable.closestPeers(id, {
172
+ count: this.routingTable.kBucketSize
173
+ })
170
174
 
171
175
  // split peers into d buckets evenly(ish)
172
176
  const peersToQuery = peers.sort(() => {
@@ -1,8 +1,8 @@
1
+ import { AbortError } from '@libp2p/interface'
1
2
  import { Queue } from '@libp2p/utils/queue'
2
3
  import { pushable } from 'it-pushable'
3
4
  import { xor as uint8ArrayXor } from 'uint8arrays/xor'
4
5
  import { xorCompare as uint8ArrayXorCompare } from 'uint8arrays/xor-compare'
5
- import { QueryAbortedError } from '../errors.js'
6
6
  import { convertPeerId, convertBuffer } from '../utils.js'
7
7
  import { pathEndedEvent, queryErrorEvent } from './events.js'
8
8
  import type { QueryEvent } from '../index.js'
@@ -106,11 +106,13 @@ export async function * queryPath (options: QueryPathOptions): AsyncGenerator<Qu
106
106
 
107
107
  signal.addEventListener('abort', () => {
108
108
  queue.abort()
109
- events.end(new QueryAbortedError())
109
+ events.end(new AbortError())
110
110
  })
111
111
 
112
112
  // perform lookups on kadId, not the actual value
113
- const kadId = await convertBuffer(key)
113
+ const kadId = await convertBuffer(key, {
114
+ signal
115
+ })
114
116
 
115
117
  /**
116
118
  * Adds the passed peer to the query queue if it's not us and no other path
@@ -159,7 +161,9 @@ export async function * queryPath (options: QueryPathOptions): AsyncGenerator<Qu
159
161
  continue
160
162
  }
161
163
 
162
- const closerPeerKadId = await convertPeerId(closerPeer.id)
164
+ const closerPeerKadId = await convertPeerId(closerPeer.id, {
165
+ signal
166
+ })
163
167
  const closerPeerXor = uint8ArrayXor(closerPeerKadId, kadId)
164
168
 
165
169
  // only continue query if closer peer is actually closer
@@ -206,7 +210,9 @@ export async function * queryPath (options: QueryPathOptions): AsyncGenerator<Qu
206
210
  // begin the query with the starting peers
207
211
  await Promise.all(
208
212
  startingPeers.map(async startingPeer => {
209
- queryPeer({ id: startingPeer, multiaddrs: [] }, await convertPeerId(startingPeer))
213
+ queryPeer({ id: startingPeer, multiaddrs: [] }, await convertPeerId(startingPeer, {
214
+ signal
215
+ }))
210
216
  })
211
217
  )
212
218
 
package/src/query-self.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { setMaxListeners } from '@libp2p/interface'
2
1
  import { anySignal } from 'any-signal'
3
2
  import length from 'it-length'
4
3
  import { pipe } from 'it-pipe'
5
4
  import take from 'it-take'
5
+ import { setMaxListeners } from 'main-event'
6
6
  import pDefer from 'p-defer'
7
7
  import { QUERY_SELF_INTERVAL, QUERY_SELF_TIMEOUT, K, QUERY_SELF_INITIAL_INTERVAL } from './constants.js'
8
8
  import { timeOperationMethod } from './utils.js'
@@ -133,6 +133,8 @@ export class QuerySelf implements Startable {
133
133
  async (source) => length(source)
134
134
  )
135
135
 
136
+ signal?.throwIfAborted()
137
+
136
138
  const duration = Date.now() - start
137
139
 
138
140
  this.log('self-query found %d peers in %dms', peers, duration)
@@ -4,13 +4,14 @@ import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
4
4
  import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
5
5
  import type { Validators } from '../index.js'
6
6
  import type { Libp2pRecord } from '@libp2p/record'
7
+ import type { AbortOptions } from 'it-pushable'
7
8
 
8
9
  /**
9
10
  * Checks a record and ensures it is still valid.
10
11
  * It runs the needed validators.
11
12
  * If verification fails the returned Promise will reject with the error.
12
13
  */
13
- export async function verifyRecord (validators: Validators, record: Libp2pRecord): Promise<void> {
14
+ export async function verifyRecord (validators: Validators, record: Libp2pRecord, options?: AbortOptions): Promise<void> {
14
15
  const key = record.key
15
16
  const keyString = uint8ArrayToString(key)
16
17
  const parts = keyString.split('/')
@@ -26,7 +27,7 @@ export async function verifyRecord (validators: Validators, record: Libp2pRecord
26
27
  throw new InvalidParametersError(`No validator available for key type "${parts[1]}"`)
27
28
  }
28
29
 
29
- await validator(key, record.value)
30
+ await validator(key, record.value, options)
30
31
  }
31
32
 
32
33
  /**
@@ -38,7 +39,7 @@ export async function verifyRecord (validators: Validators, record: Libp2pRecord
38
39
  * @param {Uint8Array} key - A valid key is of the form `'/pk/<keymultihash>'`
39
40
  * @param {Uint8Array} publicKey - The public key to validate against (protobuf encoded).
40
41
  */
41
- const validatePublicKeyRecord = async (key: Uint8Array, publicKey: Uint8Array): Promise<void> => {
42
+ const validatePublicKeyRecord = async (key: Uint8Array, publicKey: Uint8Array, options?: AbortOptions): Promise<void> => {
42
43
  if (!(key instanceof Uint8Array)) {
43
44
  throw new InvalidParametersError('"key" must be a Uint8Array')
44
45
  }
package/src/reprovider.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { TypedEventEmitter, setMaxListeners } from '@libp2p/interface'
2
1
  import { AdaptiveTimeout } from '@libp2p/utils/adaptive-timeout'
3
2
  import { Queue } from '@libp2p/utils/queue'
4
3
  import drain from 'it-drain'
5
- import { PROVIDERS_VALIDITY, REPROVIDE_CONCURRENCY, REPROVIDE_INTERVAL, REPROVIDE_MAX_QUEUE_SIZE, REPROVIDE_THRESHOLD } from './constants.js'
4
+ import { TypedEventEmitter, setMaxListeners } from 'main-event'
5
+ import { PROVIDERS_VALIDITY, REPROVIDE_CONCURRENCY, REPROVIDE_INTERVAL, REPROVIDE_MAX_QUEUE_SIZE, REPROVIDE_THRESHOLD, REPROVIDE_TIMEOUT } from './constants.js'
6
6
  import { parseProviderKey, readProviderTime, timeOperationMethod } from './utils.js'
7
7
  import type { ContentRouting } from './content-routing/index.js'
8
8
  import type { OperationMetrics } from './kad-dht.js'
@@ -10,7 +10,6 @@ import type { AbortOptions, ComponentLogger, Logger, Metrics, PeerId } from '@li
10
10
  import type { AddressManager } from '@libp2p/interface-internal'
11
11
  import type { AdaptiveTimeoutInit } from '@libp2p/utils/adaptive-timeout'
12
12
  import type { Datastore } from 'interface-datastore'
13
- import type { Mortice } from 'mortice'
14
13
  import type { CID } from 'multiformats/cid'
15
14
 
16
15
  export interface ReproviderComponents {
@@ -26,7 +25,6 @@ export interface ReproviderInit {
26
25
  metricsPrefix: string
27
26
  datastorePrefix: string
28
27
  contentRouting: ContentRouting
29
- lock: Mortice
30
28
  operationMetrics: OperationMetrics
31
29
  concurrency?: number
32
30
  maxQueueSize?: number
@@ -60,7 +58,6 @@ export class Reprovider extends TypedEventEmitter<ReprovideEvents> {
60
58
  private readonly addressManager: AddressManager
61
59
  private readonly validity: number
62
60
  private readonly interval: number
63
- private readonly lock: Mortice
64
61
  private readonly peerId: PeerId
65
62
 
66
63
  constructor (components: ReproviderComponents, init: ReproviderInit) {
@@ -86,7 +83,6 @@ export class Reprovider extends TypedEventEmitter<ReprovideEvents> {
86
83
  this.validity = init.validity ?? PROVIDERS_VALIDITY
87
84
  this.interval = init.interval ?? REPROVIDE_INTERVAL
88
85
  this.contentRouting = init.contentRouting
89
- this.lock = init.lock
90
86
  this.running = false
91
87
 
92
88
  this.reprovide = timeOperationMethod(this.reprovide.bind(this), init.operationMetrics, 'PROVIDE')
@@ -103,7 +99,9 @@ export class Reprovider extends TypedEventEmitter<ReprovideEvents> {
103
99
  setMaxListeners(Infinity, this.shutdownController.signal)
104
100
 
105
101
  this.timeout = setTimeout(() => {
106
- this.cleanUp().catch(err => {
102
+ this.cleanUp({
103
+ signal: AbortSignal.timeout(REPROVIDE_TIMEOUT)
104
+ }).catch(err => {
107
105
  this.log.error('error running reprovide/cleanup - %e', err)
108
106
  })
109
107
  }, this.interval)
@@ -120,16 +118,14 @@ export class Reprovider extends TypedEventEmitter<ReprovideEvents> {
120
118
  * Check all provider records. Delete them if they have expired, reprovide
121
119
  * them if the provider is us and the expiry is within the reprovide window.
122
120
  */
123
- private async cleanUp (): Promise<void> {
124
- const release = await this.lock.writeLock()
125
-
121
+ private async cleanUp (options?: AbortOptions): Promise<void> {
126
122
  try {
127
123
  this.safeDispatchEvent('reprovide:start')
128
124
 
129
125
  // Get all provider entries from the datastore
130
126
  for await (const entry of this.datastore.query({
131
127
  prefix: this.datastorePrefix
132
- })) {
128
+ }, options)) {
133
129
  try {
134
130
  // Add a delete to the batch for each expired entry
135
131
  const { cid, peerId } = parseProviderKey(entry.key)
@@ -142,7 +138,7 @@ export class Reprovider extends TypedEventEmitter<ReprovideEvents> {
142
138
 
143
139
  // delete the record if it has expired
144
140
  if (expired) {
145
- await this.datastore.delete(entry.key)
141
+ await this.datastore.delete(entry.key, options)
146
142
  }
147
143
 
148
144
  // if the provider is us and we are within the reprovide threshold,
@@ -160,12 +156,13 @@ export class Reprovider extends TypedEventEmitter<ReprovideEvents> {
160
156
 
161
157
  this.log('reprovide/cleanup successful')
162
158
  } finally {
163
- release()
164
159
  this.safeDispatchEvent('reprovide:end')
165
160
 
166
161
  if (this.running) {
167
162
  this.timeout = setTimeout(() => {
168
- this.cleanUp().catch(err => {
163
+ this.cleanUp({
164
+ signal: AbortSignal.timeout(REPROVIDE_TIMEOUT)
165
+ }).catch(err => {
169
166
  this.log.error('error running re-provide - %e', err)
170
167
  })
171
168
  }, this.interval)
@@ -173,13 +170,13 @@ export class Reprovider extends TypedEventEmitter<ReprovideEvents> {
173
170
  }
174
171
  }
175
172
 
176
- private async queueReprovide (cid: CID): Promise<void> {
173
+ private async queueReprovide (cid: CID, options?: AbortOptions): Promise<void> {
177
174
  if (!this.running) {
178
175
  return
179
176
  }
180
177
 
181
178
  this.log.trace('waiting for queue capacity before adding %c to re-provide queue', cid)
182
- await this.reprovideQueue.onSizeLessThan(this.maxQueueSize)
179
+ await this.reprovideQueue.onSizeLessThan(this.maxQueueSize, options)
183
180
 
184
181
  const existingJob = this.reprovideQueue.queue.find(job => job.options.cid.equals(cid))
185
182
 
@@ -1,12 +1,13 @@
1
- import { TypedEventEmitter, setMaxListeners, start, stop } from '@libp2p/interface'
1
+ import { start, stop } from '@libp2p/interface'
2
2
  import { AdaptiveTimeout } from '@libp2p/utils/adaptive-timeout'
3
3
  import { PeerQueue } from '@libp2p/utils/peer-queue'
4
4
  import { anySignal } from 'any-signal'
5
5
  import parallel from 'it-parallel'
6
+ import { TypedEventEmitter, setMaxListeners } from 'main-event'
6
7
  import * as utils from '../utils.js'
7
8
  import { ClosestPeers } from './closest-peers.js'
8
9
  import { KBucket, isLeafBucket } from './k-bucket.js'
9
- import type { Bucket, LeafBucket, Peer } from './k-bucket.js'
10
+ import type { Bucket, GetClosestPeersOptions, LeafBucket, Peer } from './k-bucket.js'
10
11
  import type { Network } from '../network.js'
11
12
  import type { AbortOptions, ComponentLogger, CounterGroup, Logger, Metric, Metrics, PeerId, PeerStore, Startable, Stream } from '@libp2p/interface'
12
13
  import type { Ping } from '@libp2p/ping'
@@ -97,6 +98,8 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
97
98
  kadBucketEvents: CounterGroup<'ping_old_contact' | 'ping_old_contact_error' | 'ping_new_contact' | 'ping_new_contact_error' | 'peer_added' | 'peer_removed'>
98
99
  }
99
100
 
101
+ private shutdownController: AbortController
102
+
100
103
  constructor (components: RoutingTableComponents, init: RoutingTableInit) {
101
104
  super()
102
105
 
@@ -114,6 +117,7 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
114
117
  this.peerRemoved = this.peerRemoved.bind(this)
115
118
  this.populateFromDatastoreOnStart = init.populateFromDatastoreOnStart ?? POPULATE_FROM_DATASTORE_ON_START
116
119
  this.populateFromDatastoreLimit = init.populateFromDatastoreLimit ?? POPULATE_FROM_DATASTORE_LIMIT
120
+ this.shutdownController = new AbortController()
117
121
 
118
122
  this.pingOldContactQueue = new PeerQueue({
119
123
  concurrency: init.pingOldContactConcurrency ?? PING_OLD_CONTACT_CONCURRENCY,
@@ -139,7 +143,7 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
139
143
  metricName: `${init.metricsPrefix}_routing_table_ping_new_contact_time_milliseconds`
140
144
  })
141
145
 
142
- this.kb = new KBucket({
146
+ this.kb = new KBucket(components, {
143
147
  kBucketSize: init.kBucketSize,
144
148
  prefixLength: init.prefixLength,
145
149
  splitThreshold: init.splitThreshold,
@@ -148,7 +152,8 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
148
152
  ping: this.pingOldContacts,
149
153
  verify: this.verifyNewContact,
150
154
  onAdd: this.peerAdded,
151
- onRemove: this.peerRemoved
155
+ onRemove: this.peerRemoved,
156
+ metricsPrefix: init.metricsPrefix
152
157
  })
153
158
 
154
159
  this.closestPeerTagger = new ClosestPeers(this.components, {
@@ -184,11 +189,13 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
184
189
 
185
190
  this.running = true
186
191
 
187
- await start(this.closestPeerTagger)
188
- await this.kb.addSelfPeer(this.components.peerId)
192
+ this.shutdownController = new AbortController()
193
+ await start(this.closestPeerTagger, this.kb)
189
194
  }
190
195
 
191
196
  async afterStart (): Promise<void> {
197
+ let peerStorePeers = 0
198
+
192
199
  // do this async to not block startup but iterate serially to not overwhelm
193
200
  // the ping queue
194
201
  Promise.resolve().then(async () => {
@@ -196,48 +203,60 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
196
203
  return
197
204
  }
198
205
 
199
- let peerStorePeers = 0
200
-
201
- // add existing peers from the peer store to routing table
202
- for (const peer of await this.components.peerStore.all({
203
- filters: [(peer) => {
204
- return peer.protocols.includes(this.protocol) && peer.tags.has(KAD_PEER_TAG_NAME)
205
- }],
206
- limit: this.populateFromDatastoreLimit
207
- })) {
208
- if (!this.running) {
209
- // bail if we've been shut down
210
- return
211
- }
206
+ const signal = anySignal([
207
+ this.shutdownController.signal,
208
+ AbortSignal.timeout(20_000)
209
+ ])
210
+ setMaxListeners(Infinity, signal)
211
+
212
+ try {
213
+ // add existing peers from the peer store to routing table
214
+ for (const peer of await this.components.peerStore.all({
215
+ filters: [(peer) => {
216
+ return peer.protocols.includes(this.protocol) && peer.tags.has(KAD_PEER_TAG_NAME)
217
+ }],
218
+ limit: this.populateFromDatastoreLimit,
219
+ signal
220
+ })) {
221
+ if (!this.running) {
222
+ // bail if we've been shut down
223
+ return
224
+ }
212
225
 
213
- try {
214
- await this.add(peer.id)
215
- peerStorePeers++
216
- } catch (err) {
217
- this.log('failed to add peer %p to routing table, removing kad-dht peer tags - %e')
218
- await this.components.peerStore.merge(peer.id, {
219
- tags: {
220
- [this.peerTagName]: undefined
221
- }
222
- })
226
+ try {
227
+ await this.add(peer.id, {
228
+ signal
229
+ })
230
+ peerStorePeers++
231
+ } catch (err) {
232
+ this.log('failed to add peer %p to routing table, removing kad-dht peer tags - %e')
233
+ await this.components.peerStore.merge(peer.id, {
234
+ tags: {
235
+ [this.peerTagName]: undefined
236
+ }
237
+ })
238
+ }
223
239
  }
240
+ } finally {
241
+ signal.clear()
224
242
  }
225
243
 
226
244
  this.log('added %d peer store peers to the routing table', peerStorePeers)
227
245
  })
228
246
  .catch(err => {
229
- this.log.error('error adding peer store peers to the routing table %e', err)
247
+ this.log.error('error adding %d, peer store peers to the routing table - %e', peerStorePeers, err)
230
248
  })
231
249
  }
232
250
 
233
251
  async stop (): Promise<void> {
234
252
  this.running = false
235
- await stop(this.closestPeerTagger)
253
+ await stop(this.closestPeerTagger, this.kb)
236
254
  this.pingOldContactQueue.abort()
237
255
  this.pingNewContactQueue.abort()
256
+ this.shutdownController.abort()
238
257
  }
239
258
 
240
- private async peerAdded (peer: Peer, bucket: LeafBucket): Promise<void> {
259
+ private async peerAdded (peer: Peer, bucket: LeafBucket, options?: AbortOptions): Promise<void> {
241
260
  if (!this.components.peerId.equals(peer.peerId)) {
242
261
  await this.components.peerStore.merge(peer.peerId, {
243
262
  tags: {
@@ -245,7 +264,7 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
245
264
  value: this.peerTagValue
246
265
  }
247
266
  }
248
- })
267
+ }, options)
249
268
  }
250
269
 
251
270
  this.updateMetrics()
@@ -253,13 +272,13 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
253
272
  this.safeDispatchEvent('peer:add', { detail: peer.peerId })
254
273
  }
255
274
 
256
- private async peerRemoved (peer: Peer, bucket: LeafBucket): Promise<void> {
275
+ private async peerRemoved (peer: Peer, bucket: LeafBucket, options?: AbortOptions): Promise<void> {
257
276
  if (!this.components.peerId.equals(peer.peerId)) {
258
277
  await this.components.peerStore.merge(peer.peerId, {
259
278
  tags: {
260
279
  [this.peerTagName]: undefined
261
280
  }
262
- })
281
+ }, options)
263
282
  }
264
283
 
265
284
  this.updateMetrics()
@@ -309,7 +328,11 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
309
328
 
310
329
  const result = await this.pingOldContactQueue.add(async (options) => {
311
330
  const signal = this.pingOldContactTimeout.getTimeoutSignal()
312
- const signals = anySignal([signal, options?.signal])
331
+ const signals = anySignal([
332
+ signal,
333
+ this.shutdownController.signal,
334
+ options?.signal
335
+ ])
313
336
  setMaxListeners(Infinity, signal, signals)
314
337
 
315
338
  try {
@@ -341,7 +364,11 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
341
364
 
342
365
  async verifyNewContact (contact: Peer, options?: AbortOptions): Promise<boolean> {
343
366
  const signal = this.pingNewContactTimeout.getTimeoutSignal()
344
- const signals = anySignal([signal, options?.signal])
367
+ const signals = anySignal([
368
+ signal,
369
+ this.shutdownController.signal,
370
+ options?.signal
371
+ ])
345
372
  setMaxListeners(Infinity, signal, signals)
346
373
 
347
374
  try {
@@ -408,8 +435,8 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
408
435
  /**
409
436
  * Find a specific peer by id
410
437
  */
411
- async find (peer: PeerId): Promise<PeerId | undefined> {
412
- const kadId = await utils.convertPeerId(peer)
438
+ async find (peer: PeerId, options?: AbortOptions): Promise<PeerId | undefined> {
439
+ const kadId = await utils.convertPeerId(peer, options)
413
440
  return this.kb.get(kadId)?.peerId
414
441
  }
415
442
 
@@ -417,7 +444,9 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
417
444
  * Retrieve the closest peers to the given kadId
418
445
  */
419
446
  closestPeer (kadId: Uint8Array): PeerId | undefined {
420
- const res = this.closestPeers(kadId, 1)
447
+ const res = this.closestPeers(kadId, {
448
+ count: 1
449
+ })
421
450
 
422
451
  if (res.length > 0) {
423
452
  return res[0]
@@ -429,12 +458,12 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
429
458
  /**
430
459
  * Retrieve the `count`-closest peers to the given kadId
431
460
  */
432
- closestPeers (kadId: Uint8Array, count = this.kBucketSize): PeerId[] {
461
+ closestPeers (kadId: Uint8Array, options?: GetClosestPeersOptions): PeerId[] {
433
462
  if (this.kb == null) {
434
463
  return []
435
464
  }
436
465
 
437
- return [...this.kb.closest(kadId, count)]
466
+ return [...this.kb.closest(kadId, options)]
438
467
  }
439
468
 
440
469
  /**
@@ -451,14 +480,14 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
451
480
  /**
452
481
  * Remove a given peer from the table
453
482
  */
454
- async remove (peer: PeerId): Promise<void> {
483
+ async remove (peer: PeerId, options?: AbortOptions): Promise<void> {
455
484
  if (this.kb == null) {
456
485
  throw new Error('RoutingTable is not started')
457
486
  }
458
487
 
459
- const kadId = await utils.convertPeerId(peer)
488
+ const kadId = await utils.convertPeerId(peer, options)
460
489
 
461
- await this.kb.remove(kadId)
490
+ await this.kb.remove(kadId, options)
462
491
  }
463
492
 
464
493
  private updateMetrics (): void {