@libp2p/circuit-relay-v2 2.1.4 → 2.1.5-3bc9769b8

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 (63) hide show
  1. package/dist/index.min.js +4 -4
  2. package/dist/src/constants.d.ts +0 -5
  3. package/dist/src/constants.d.ts.map +1 -1
  4. package/dist/src/constants.js +0 -5
  5. package/dist/src/constants.js.map +1 -1
  6. package/dist/src/errors.d.ts +22 -0
  7. package/dist/src/errors.d.ts.map +1 -1
  8. package/dist/src/errors.js +22 -0
  9. package/dist/src/errors.js.map +1 -1
  10. package/dist/src/index.d.ts +16 -1
  11. package/dist/src/index.d.ts.map +1 -1
  12. package/dist/src/index.js.map +1 -1
  13. package/dist/src/pb/index.d.ts +12 -1
  14. package/dist/src/pb/index.d.ts.map +1 -1
  15. package/dist/src/pb/index.js +78 -2
  16. package/dist/src/pb/index.js.map +1 -1
  17. package/dist/src/server/index.d.ts.map +1 -1
  18. package/dist/src/server/index.js +19 -10
  19. package/dist/src/server/index.js.map +1 -1
  20. package/dist/src/server/reservation-store.d.ts +5 -9
  21. package/dist/src/server/reservation-store.d.ts.map +1 -1
  22. package/dist/src/server/reservation-store.js +32 -33
  23. package/dist/src/server/reservation-store.js.map +1 -1
  24. package/dist/src/server/reservation-voucher.d.ts +1 -1
  25. package/dist/src/transport/discovery.d.ts +1 -0
  26. package/dist/src/transport/discovery.d.ts.map +1 -1
  27. package/dist/src/transport/discovery.js +38 -8
  28. package/dist/src/transport/discovery.js.map +1 -1
  29. package/dist/src/transport/index.d.ts +0 -6
  30. package/dist/src/transport/index.d.ts.map +1 -1
  31. package/dist/src/transport/index.js.map +1 -1
  32. package/dist/src/transport/listener.d.ts +3 -0
  33. package/dist/src/transport/listener.d.ts.map +1 -1
  34. package/dist/src/transport/listener.js +57 -27
  35. package/dist/src/transport/listener.js.map +1 -1
  36. package/dist/src/transport/reservation-store.d.ts +37 -14
  37. package/dist/src/transport/reservation-store.d.ts.map +1 -1
  38. package/dist/src/transport/reservation-store.js +144 -74
  39. package/dist/src/transport/reservation-store.js.map +1 -1
  40. package/dist/src/transport/transport.d.ts +2 -13
  41. package/dist/src/transport/transport.d.ts.map +1 -1
  42. package/dist/src/transport/transport.js +30 -54
  43. package/dist/src/transport/transport.js.map +1 -1
  44. package/dist/src/utils.d.ts +10 -1
  45. package/dist/src/utils.d.ts.map +1 -1
  46. package/dist/src/utils.js +22 -8
  47. package/dist/src/utils.js.map +1 -1
  48. package/package.json +14 -12
  49. package/src/constants.ts +0 -7
  50. package/src/errors.ts +25 -0
  51. package/src/index.ts +19 -1
  52. package/src/pb/index.proto +20 -1
  53. package/src/pb/index.ts +99 -3
  54. package/src/server/index.ts +21 -11
  55. package/src/server/reservation-store.ts +38 -42
  56. package/src/server/reservation-voucher.ts +2 -2
  57. package/src/transport/discovery.ts +47 -9
  58. package/src/transport/index.ts +0 -7
  59. package/src/transport/listener.ts +75 -35
  60. package/src/transport/reservation-store.ts +209 -95
  61. package/src/transport/transport.ts +34 -76
  62. package/src/utils.ts +29 -8
  63. package/dist/typedoc-urls.json +0 -23
@@ -1,15 +1,18 @@
1
- import { TypedEventEmitter, setMaxListeners } from '@libp2p/interface'
1
+ import { ListenError, TypedEventEmitter, setMaxListeners } from '@libp2p/interface'
2
2
  import { PeerMap } from '@libp2p/peer-collections'
3
- import { createBloomFilter } from '@libp2p/utils/filters'
3
+ import { createScalableCuckooFilter } from '@libp2p/utils/filters'
4
4
  import { PeerQueue } from '@libp2p/utils/peer-queue'
5
5
  import { multiaddr } from '@multiformats/multiaddr'
6
+ import { Circuit } from '@multiformats/multiaddr-matcher'
6
7
  import { pbStream } from 'it-protobuf-stream'
7
- import { DEFAULT_MAX_RESERVATION_QUEUE_LENGTH, DEFAULT_RESERVATION_COMPLETION_TIMEOUT, DEFAULT_RESERVATION_CONCURRENCY, KEEP_ALIVE_TAG, RELAY_TAG, RELAY_V2_HOP_CODEC } from '../constants.js'
8
+ import { nanoid } from 'nanoid'
9
+ import { DEFAULT_MAX_RESERVATION_QUEUE_LENGTH, DEFAULT_RESERVATION_COMPLETION_TIMEOUT, DEFAULT_RESERVATION_CONCURRENCY, KEEP_ALIVE_TAG, RELAY_V2_HOP_CODEC } from '../constants.js'
10
+ import { DoubleRelayError, HadEnoughRelaysError, RelayQueueFullError } from '../errors.js'
8
11
  import { HopMessage, Status } from '../pb/index.js'
9
12
  import { getExpirationMilliseconds } from '../utils.js'
10
13
  import type { Reservation } from '../pb/index.js'
11
- import type { TypedEventTarget, Libp2pEvents, AbortOptions, ComponentLogger, Logger, Connection, PeerId, PeerStore, Startable, Metrics, Peer } from '@libp2p/interface'
12
- import type { ConnectionManager, TransportManager } from '@libp2p/interface-internal'
14
+ import type { AbortOptions, TypedEventTarget, Libp2pEvents, ComponentLogger, Logger, PeerId, PeerStore, Startable, Metrics, Peer, Connection } from '@libp2p/interface'
15
+ import type { ConnectionManager } from '@libp2p/interface-internal'
13
16
  import type { Filter } from '@libp2p/utils/filters'
14
17
 
15
18
  // allow refreshing a relay reservation if it will expire in the next 10 minutes
@@ -24,7 +27,6 @@ const REFRESH_TIMEOUT_MIN = 30 * 1000
24
27
  export interface ReservationStoreComponents {
25
28
  peerId: PeerId
26
29
  connectionManager: ConnectionManager
27
- transportManager: TransportManager
28
30
  peerStore: PeerStore
29
31
  events: TypedEventTarget<Libp2pEvents>
30
32
  logger: ComponentLogger
@@ -44,11 +46,6 @@ export interface ReservationStoreInit {
44
46
  */
45
47
  reservationConcurrency?: number
46
48
 
47
- /**
48
- * How many discovered relays to allow in the reservation store
49
- */
50
- discoverRelays?: number
51
-
52
49
  /**
53
50
  * Limit the number of potential relays we will dial
54
51
  *
@@ -66,37 +63,60 @@ export interface ReservationStoreInit {
66
63
 
67
64
  export type RelayType = 'discovered' | 'configured'
68
65
 
69
- interface RelayEntry {
66
+ export interface DiscoveredRelayEntry {
70
67
  timeout: ReturnType<typeof setTimeout>
71
- type: RelayType
68
+ type: 'discovered'
72
69
  reservation: Reservation
73
70
 
74
71
  /**
75
72
  * Stores the id of the connection we have to the relay
76
73
  */
77
74
  connection: string
75
+
76
+ /**
77
+ * Stores the identifier returned when the reservation was requested
78
+ */
79
+ id: string
80
+ }
81
+
82
+ export interface ConfiguredRelayEntry {
83
+ timeout: ReturnType<typeof setTimeout>
84
+ type: 'configured'
85
+ reservation: Reservation
86
+
87
+ /**
88
+ * Stores the id of the connection we have to the relay
89
+ */
90
+ connection: string
91
+ }
92
+
93
+ export type RelayEntry = DiscoveredRelayEntry | ConfiguredRelayEntry
94
+
95
+ export interface RelayReservation {
96
+ relay: PeerId
97
+ details: RelayEntry
78
98
  }
79
99
 
80
100
  export interface ReservationStoreEvents {
81
101
  'relay:not-enough-relays': CustomEvent
82
- 'relay:removed': CustomEvent<PeerId>
83
- 'relay:created-reservation': CustomEvent<PeerId>
102
+ 'relay:found-enough-relays': CustomEvent
103
+ 'relay:removed': CustomEvent<RelayReservation>
104
+ 'relay:created-reservation': CustomEvent<RelayReservation>
84
105
  }
85
106
 
86
107
  export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents> implements Startable {
87
108
  private readonly peerId: PeerId
88
109
  private readonly connectionManager: ConnectionManager
89
- private readonly transportManager: TransportManager
90
110
  private readonly peerStore: PeerStore
91
111
  private readonly events: TypedEventTarget<Libp2pEvents>
92
- private readonly reserveQueue: PeerQueue
112
+ private readonly reserveQueue: PeerQueue<RelayReservation>
93
113
  private readonly reservations: PeerMap<RelayEntry>
94
- private readonly maxDiscoveredRelays: number
114
+ private readonly pendingReservations: string[]
95
115
  private readonly maxReservationQueueLength: number
96
116
  private readonly reservationCompletionTimeout: number
97
117
  private started: boolean
98
118
  private readonly log: Logger
99
- private readonly relayFilter: Filter
119
+ private relayFilter: Filter
100
120
 
101
121
  constructor (components: ReservationStoreComponents, init?: ReservationStoreInit) {
102
122
  super()
@@ -104,15 +124,14 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
104
124
  this.log = components.logger.forComponent('libp2p:circuit-relay:transport:reservation-store')
105
125
  this.peerId = components.peerId
106
126
  this.connectionManager = components.connectionManager
107
- this.transportManager = components.transportManager
108
127
  this.peerStore = components.peerStore
109
128
  this.events = components.events
110
129
  this.reservations = new PeerMap()
111
- this.maxDiscoveredRelays = init?.discoverRelays ?? 0
130
+ this.pendingReservations = []
112
131
  this.maxReservationQueueLength = init?.maxReservationQueueLength ?? DEFAULT_MAX_RESERVATION_QUEUE_LENGTH
113
132
  this.reservationCompletionTimeout = init?.reservationCompletionTimeout ?? DEFAULT_RESERVATION_COMPLETION_TIMEOUT
114
133
  this.started = false
115
- this.relayFilter = createBloomFilter(100)
134
+ this.relayFilter = createScalableCuckooFilter(100)
116
135
 
117
136
  // ensure we don't listen on multiple relays simultaneously
118
137
  this.reserveQueue = new PeerQueue({
@@ -132,7 +151,7 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
132
151
  return
133
152
  }
134
153
 
135
- this.#removeReservation(evt.detail.remotePeer, reservation)
154
+ this.#removeReservation(evt.detail.remotePeer)
136
155
  .catch(err => {
137
156
  this.log('could not remove relay %p - %e', evt.detail, err)
138
157
  })
@@ -153,7 +172,7 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
153
172
  .then(async () => {
154
173
  const relayPeers: Peer[] = await this.peerStore.all({
155
174
  filters: [(peer) => {
156
- return peer.tags.has(RELAY_TAG)
175
+ return peer.tags.has(KEEP_ALIVE_TAG)
157
176
  }]
158
177
  })
159
178
 
@@ -164,17 +183,18 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
164
183
  relayPeers.map(async peer => {
165
184
  await this.peerStore.merge(peer.id, {
166
185
  tags: {
167
- [RELAY_TAG]: undefined,
168
186
  [KEEP_ALIVE_TAG]: undefined
169
187
  }
170
188
  })
171
189
  })
172
190
  )
173
191
 
174
- if (this.reservations.size < this.maxDiscoveredRelays) {
175
- this.log('not enough relays %d/%d', this.reservations.size, this.maxDiscoveredRelays)
176
- this.safeDispatchEvent('relay:not-enough-relays', {})
177
- }
192
+ this.log('redialing %d old relays', relayPeers.length)
193
+ await Promise.all(
194
+ relayPeers.map(async peer => this.addRelay(peer.id, 'discovered'))
195
+ )
196
+
197
+ this.#checkReservationCount()
178
198
  })
179
199
  .catch(err => {
180
200
  this.log.error(err)
@@ -190,36 +210,46 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
190
210
  this.started = false
191
211
  }
192
212
 
213
+ reserveRelay (): string {
214
+ const id = nanoid()
215
+
216
+ this.pendingReservations.push(id)
217
+
218
+ this.#checkReservationCount()
219
+
220
+ return id
221
+ }
222
+
193
223
  /**
194
224
  * If the number of current relays is beneath the configured `maxReservations`
195
225
  * value, and the passed peer id is not our own, and we have a non-relayed
196
226
  * connection to the remote, and the remote peer speaks the hop protocol, try
197
227
  * to reserve a slot on the remote peer
198
228
  */
199
- async addRelay (peerId: PeerId, type: RelayType): Promise<void> {
229
+ async addRelay (peerId: PeerId, type: RelayType): Promise<RelayReservation> {
200
230
  if (this.peerId.equals(peerId)) {
201
231
  this.log.trace('not trying to use self as relay')
202
- return
232
+ throw new ListenError('Cannot use self as relay')
203
233
  }
204
234
 
205
235
  if (this.reserveQueue.size > this.maxReservationQueueLength) {
206
- this.log.trace('not adding potential relay peer %p as the queue is full', peerId)
207
- return
236
+ throw new RelayQueueFullError('The reservation queue is full')
208
237
  }
209
238
 
210
- if (this.reserveQueue.has(peerId)) {
239
+ const existingJob = this.reserveQueue.find(peerId)
240
+
241
+ if (existingJob != null) {
211
242
  this.log.trace('potential relay peer %p is already in the reservation queue', peerId)
212
- return
243
+ return existingJob.join()
213
244
  }
214
245
 
215
246
  if (this.relayFilter.has(peerId.toMultihash().bytes)) {
216
- this.log.trace('potential relay peer %p has failed previously, not trying again', peerId)
217
- return
247
+ throw new ListenError('The relay was previously invalid')
218
248
  }
219
249
 
220
250
  this.log.trace('try to reserve relay slot with %p', peerId)
221
251
 
222
- await this.reserveQueue.add(async () => {
252
+ return this.reserveQueue.add(async () => {
223
253
  const start = Date.now()
224
254
 
225
255
  try {
@@ -227,24 +257,31 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
227
257
  const existingReservation = this.reservations.get(peerId)
228
258
 
229
259
  if (existingReservation != null) {
230
- if (this.connectionManager.getConnections(peerId).map(conn => conn.id).includes(existingReservation.connection) && getExpirationMilliseconds(existingReservation.reservation.expire) > REFRESH_WINDOW) {
231
- this.log('already have relay reservation with %p but we are still connected and it does not expire soon', peerId)
232
- return
260
+ const connections = this.connectionManager.getConnections(peerId)
261
+ let connected = false
262
+
263
+ if (connections.length === 0) {
264
+ this.log('already have relay reservation with %p but we are no longer connected', peerId)
233
265
  }
234
266
 
235
- this.log('already have relay reservation with %p but the original connection is no longer open', peerId)
236
- await this.#removeReservation(peerId, existingReservation)
237
- }
267
+ if (connections.map(conn => conn.id).includes(existingReservation.connection)) {
268
+ this.log('already have relay reservation with %p and the original connection is still open', peerId)
269
+ connected = true
270
+ }
238
271
 
239
- if (type === 'discovered' && [...this.reservations.values()].reduce((acc, curr) => {
240
- if (curr.type === 'discovered') {
241
- acc++
272
+ if (connected && getExpirationMilliseconds(existingReservation.reservation.expire) > REFRESH_WINDOW) {
273
+ this.log('already have relay reservation with %p but we are still connected and it does not expire soon', peerId)
274
+ return {
275
+ relay: peerId,
276
+ details: existingReservation
277
+ } satisfies RelayReservation
242
278
  }
243
279
 
244
- return acc
245
- }, 0) >= this.maxDiscoveredRelays) {
246
- this.log.trace('already have enough discovered relays')
247
- return
280
+ await this.#removeReservation(peerId)
281
+ }
282
+
283
+ if (type === 'discovered' && this.pendingReservations.length === 0) {
284
+ throw new HadEnoughRelaysError('Not making reservation on discovered relay because we do not need any more relays')
248
285
  }
249
286
 
250
287
  const signal = AbortSignal.timeout(this.reservationCompletionTimeout)
@@ -254,44 +291,67 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
254
291
  signal
255
292
  })
256
293
 
257
- if (connection.remoteAddr.protoNames().includes('p2p-circuit')) {
258
- this.log('not creating reservation over relayed connection')
259
- return
294
+ if (Circuit.matches(connection.remoteAddr)) {
295
+ throw new DoubleRelayError('not creating reservation over relayed connection')
260
296
  }
261
297
 
262
298
  const reservation = await this.#createReservation(connection, {
263
299
  signal
264
300
  })
265
301
 
266
- this.log('created reservation on relay peer %p', peerId)
267
-
268
302
  const expiration = getExpirationMilliseconds(reservation.expire)
269
303
 
304
+ this.log('created reservation on relay peer %p, expiry date is %s', peerId, new Date(Date.now() + expiration).toString())
305
+
270
306
  // sets a lower bound on the timeout, and also don't let it go over
271
307
  // 2^31 - 1 (setTimeout will only accept signed 32 bit integers)
272
308
  const timeoutDuration = Math.min(Math.max(expiration - REFRESH_TIMEOUT, REFRESH_TIMEOUT_MIN), Math.pow(2, 31) - 1)
273
309
 
274
310
  const timeout = setTimeout(() => {
275
- this.addRelay(peerId, type).catch(err => {
276
- this.log.error('could not refresh reservation to relay %p', peerId, err)
277
- })
311
+ this.log('refresh reservation to relay %p', peerId)
312
+
313
+ this.addRelay(peerId, type)
314
+ .catch(async err => {
315
+ this.log.error('could not refresh reservation to relay %p - %e', peerId, err)
316
+ await this.#removeReservation(peerId)
317
+ })
318
+ .catch(err => {
319
+ this.log.error('could not remove expired reservation to relay %p - %e', peerId, err)
320
+ })
278
321
  }, timeoutDuration)
279
322
 
323
+ let res: RelayEntry
324
+
325
+ // assign a reservation id if one was requested
326
+ if (type === 'discovered') {
327
+ const id = this.pendingReservations.pop()
328
+
329
+ if (id == null) {
330
+ throw new HadEnoughRelaysError('Made reservation on relay but did not need any more discovered relays')
331
+ }
332
+
333
+ res = {
334
+ timeout,
335
+ reservation,
336
+ type,
337
+ connection: connection.id,
338
+ id
339
+ }
340
+ } else {
341
+ res = {
342
+ timeout,
343
+ reservation,
344
+ type,
345
+ connection: connection.id
346
+ }
347
+ }
348
+
280
349
  // we've managed to create a reservation successfully
281
- this.reservations.set(peerId, {
282
- timeout,
283
- reservation,
284
- type,
285
- connection: connection.id
286
- })
350
+ this.reservations.set(peerId, res)
287
351
 
288
352
  // ensure we don't close the connection to the relay
289
353
  await this.peerStore.merge(peerId, {
290
354
  tags: {
291
- [RELAY_TAG]: {
292
- value: 1,
293
- ttl: expiration
294
- },
295
355
  [KEEP_ALIVE_TAG]: {
296
356
  value: 1,
297
357
  ttl: expiration
@@ -299,27 +359,37 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
299
359
  }
300
360
  })
301
361
 
302
- // listen on multiaddr that only the circuit transport is listening for
303
- await this.transportManager.listen([multiaddr(`/p2p/${peerId.toString()}/p2p-circuit`)])
362
+ // check to see if we have discovered enough relays
363
+ this.#checkReservationCount()
364
+
365
+ const result: RelayReservation = {
366
+ relay: peerId,
367
+ details: res
368
+ }
304
369
 
305
370
  this.safeDispatchEvent('relay:created-reservation', {
306
- detail: peerId
371
+ detail: result
307
372
  })
308
- } catch (err) {
309
- this.log.error('could not reserve slot on %p after %dms', peerId, Date.now() - start, err)
310
373
 
311
- // cancel the renewal timeout if it's been set
312
- const reservation = this.reservations.get(peerId)
374
+ return result
375
+ } catch (err: any) {
376
+ if (!(type === 'discovered' && err.name === 'HadEnoughRelaysError')) {
377
+ this.log.error('could not reserve slot on %p after %dms - %e', peerId, Date.now() - start, err)
378
+ }
313
379
 
314
- if (reservation != null) {
315
- clearTimeout(reservation.timeout)
380
+ // don't try this peer again if dialing failed or they do not support
381
+ // the hop protocol
382
+ if (err.name === 'DialError' || err.name === 'UnsupportedProtocolError') {
383
+ this.relayFilter.add(peerId.toMultihash().bytes)
316
384
  }
317
385
 
318
386
  // if listening failed, remove the reservation
319
- this.reservations.delete(peerId)
387
+ this.#removeReservation(peerId)
388
+ .catch(err => {
389
+ this.log.error('could not remove reservation on %p after reserving slot failed - %e', peerId, err)
390
+ })
320
391
 
321
- // don't try this peer again
322
- this.relayFilter.add(peerId.toMultihash().bytes)
392
+ throw err
323
393
  }
324
394
  }, {
325
395
  peerId
@@ -334,16 +404,26 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
334
404
  return this.reservations.get(peerId)?.reservation
335
405
  }
336
406
 
337
- reservationCount (): number {
338
- return this.reservations.size
407
+ reservationCount (type?: RelayType): number {
408
+ if (type == null) {
409
+ return this.reservations.size
410
+ }
411
+
412
+ return [...this.reservations.values()].reduce((acc, curr) => {
413
+ if (curr.type === type) {
414
+ acc++
415
+ }
416
+
417
+ return acc
418
+ }, 0)
339
419
  }
340
420
 
341
- async cancelReservations (): Promise<void> {
342
- await Promise.all(
343
- [...this.reservations.entries()].map(async ([peerId, reservation]) => {
344
- await this.#removeReservation(peerId, reservation)
345
- })
346
- )
421
+ cancelReservations (): void {
422
+ [...this.reservations.values()].forEach(reservation => {
423
+ clearTimeout(reservation.timeout)
424
+ })
425
+
426
+ this.reservations.clear()
347
427
  }
348
428
 
349
429
  async #createReservation (connection: Connection, options: AbortOptions): Promise<Reservation> {
@@ -353,11 +433,14 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
353
433
  const stream = await connection.newStream(RELAY_V2_HOP_CODEC, options)
354
434
  const pbstr = pbStream(stream)
355
435
  const hopstr = pbstr.pb(HopMessage)
436
+
437
+ this.log.trace('send RESERVE to %p', connection.remotePeer)
356
438
  await hopstr.write({ type: HopMessage.Type.RESERVE }, options)
357
439
 
358
440
  let response: HopMessage
359
441
 
360
442
  try {
443
+ this.log.trace('read response from %p', connection.remotePeer)
361
444
  response = await hopstr.read(options)
362
445
  } catch (err: any) {
363
446
  stream.abort(err)
@@ -368,6 +451,8 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
368
451
  }
369
452
  }
370
453
 
454
+ this.log.trace('read response from %p', response)
455
+
371
456
  if (response.status === Status.OK && response.reservation != null) {
372
457
  // check that the returned relay has the relay address - this can be
373
458
  // omitted when requesting a reservation from a go-libp2p relay we
@@ -405,24 +490,53 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
405
490
  /**
406
491
  * Remove listen relay
407
492
  */
408
- async #removeReservation (peerId: PeerId, reservation: RelayEntry): Promise<void> {
493
+ async #removeReservation (peerId: PeerId): Promise<void> {
494
+ const reservation = this.reservations.get(peerId)
495
+
496
+ if (reservation == null) {
497
+ return
498
+ }
499
+
409
500
  this.log('removing relay reservation with %p from local store', peerId)
410
501
  clearTimeout(reservation.timeout)
411
502
  this.reservations.delete(peerId)
412
503
 
504
+ // discover a new relay for this discovery request
505
+ if (reservation.type === 'discovered') {
506
+ this.pendingReservations.push(
507
+ reservation.id
508
+ )
509
+ }
510
+
413
511
  // untag the relay
414
512
  await this.peerStore.merge(peerId, {
415
513
  tags: {
416
- [RELAY_TAG]: undefined,
417
514
  [KEEP_ALIVE_TAG]: undefined
418
515
  }
419
516
  })
420
517
 
421
- this.safeDispatchEvent('relay:removed', { detail: peerId })
518
+ this.safeDispatchEvent('relay:removed', {
519
+ detail: {
520
+ relay: peerId,
521
+ details: reservation
522
+ }
523
+ })
524
+
525
+ // maybe trigger discovery of new relays
526
+ this.#checkReservationCount()
527
+ }
528
+
529
+ #checkReservationCount (): void {
530
+ if (this.pendingReservations.length === 0) {
531
+ this.log.trace('have discovered enough relays')
532
+ this.reserveQueue.clear()
533
+ this.safeDispatchEvent('relay:found-enough-relays')
422
534
 
423
- if (this.reservations.size < this.maxDiscoveredRelays) {
424
- this.log('not enough relays %d/%d', this.reservations.size, this.maxDiscoveredRelays)
425
- this.safeDispatchEvent('relay:not-enough-relays', {})
535
+ return
426
536
  }
537
+
538
+ this.relayFilter = createScalableCuckooFilter(100)
539
+ this.log('not discovered enough relays %d/%d', this.reservations.size, this.pendingReservations.length)
540
+ this.safeDispatchEvent('relay:not-enough-relays')
427
541
  }
428
542
  }