@libp2p/circuit-relay-v2 2.1.5 → 3.0.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 (41) hide show
  1. package/dist/index.min.js +4 -4
  2. package/dist/src/constants.d.ts +0 -1
  3. package/dist/src/constants.d.ts.map +1 -1
  4. package/dist/src/constants.js +0 -1
  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/transport/discovery.d.ts +1 -0
  11. package/dist/src/transport/discovery.d.ts.map +1 -1
  12. package/dist/src/transport/discovery.js +38 -8
  13. package/dist/src/transport/discovery.js.map +1 -1
  14. package/dist/src/transport/index.d.ts +0 -6
  15. package/dist/src/transport/index.d.ts.map +1 -1
  16. package/dist/src/transport/index.js.map +1 -1
  17. package/dist/src/transport/listener.d.ts +3 -0
  18. package/dist/src/transport/listener.d.ts.map +1 -1
  19. package/dist/src/transport/listener.js +57 -27
  20. package/dist/src/transport/listener.js.map +1 -1
  21. package/dist/src/transport/reservation-store.d.ts +37 -14
  22. package/dist/src/transport/reservation-store.d.ts.map +1 -1
  23. package/dist/src/transport/reservation-store.js +125 -80
  24. package/dist/src/transport/reservation-store.js.map +1 -1
  25. package/dist/src/transport/transport.d.ts +2 -13
  26. package/dist/src/transport/transport.d.ts.map +1 -1
  27. package/dist/src/transport/transport.js +22 -49
  28. package/dist/src/transport/transport.js.map +1 -1
  29. package/dist/src/utils.d.ts +5 -2
  30. package/dist/src/utils.d.ts.map +1 -1
  31. package/dist/src/utils.js +7 -4
  32. package/dist/src/utils.js.map +1 -1
  33. package/package.json +10 -9
  34. package/src/constants.ts +0 -2
  35. package/src/errors.ts +25 -0
  36. package/src/transport/discovery.ts +47 -9
  37. package/src/transport/index.ts +0 -7
  38. package/src/transport/listener.ts +75 -35
  39. package/src/transport/reservation-store.ts +184 -102
  40. package/src/transport/transport.ts +25 -71
  41. package/src/utils.ts +12 -4
@@ -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,9 +63,25 @@ export interface ReservationStoreInit {
66
63
 
67
64
  export type RelayType = 'discovered' | 'configured'
68
65
 
69
- interface RelayEntry {
66
+ export interface DiscoveredRelayEntry {
67
+ timeout: ReturnType<typeof setTimeout>
68
+ type: 'discovered'
69
+ reservation: Reservation
70
+
71
+ /**
72
+ * Stores the id of the connection we have to the relay
73
+ */
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 {
70
83
  timeout: ReturnType<typeof setTimeout>
71
- type: RelayType
84
+ type: 'configured'
72
85
  reservation: Reservation
73
86
 
74
87
  /**
@@ -77,26 +90,33 @@ interface RelayEntry {
77
90
  connection: string
78
91
  }
79
92
 
93
+ export type RelayEntry = DiscoveredRelayEntry | ConfiguredRelayEntry
94
+
95
+ export interface RelayReservation {
96
+ relay: PeerId
97
+ details: RelayEntry
98
+ }
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 {
@@ -241,21 +271,17 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
241
271
 
242
272
  if (connected && getExpirationMilliseconds(existingReservation.reservation.expire) > REFRESH_WINDOW) {
243
273
  this.log('already have relay reservation with %p but we are still connected and it does not expire soon', peerId)
244
- return
274
+ return {
275
+ relay: peerId,
276
+ details: existingReservation
277
+ } satisfies RelayReservation
245
278
  }
246
279
 
247
- await this.#removeReservation(peerId, existingReservation)
280
+ await this.#removeReservation(peerId)
248
281
  }
249
282
 
250
- if (type === 'discovered' && [...this.reservations.values()].reduce((acc, curr) => {
251
- if (curr.type === 'discovered') {
252
- acc++
253
- }
254
-
255
- return acc
256
- }, 0) >= this.maxDiscoveredRelays) {
257
- this.log.trace('already have enough discovered relays')
258
- return
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')
259
285
  }
260
286
 
261
287
  const signal = AbortSignal.timeout(this.reservationCompletionTimeout)
@@ -265,9 +291,8 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
265
291
  signal
266
292
  })
267
293
 
268
- if (connection.remoteAddr.protoNames().includes('p2p-circuit')) {
269
- this.log('not creating reservation over relayed connection')
270
- return
294
+ if (Circuit.matches(connection.remoteAddr)) {
295
+ throw new DoubleRelayError('not creating reservation over relayed connection')
271
296
  }
272
297
 
273
298
  const reservation = await this.#createReservation(connection, {
@@ -288,36 +313,45 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
288
313
  this.addRelay(peerId, type)
289
314
  .catch(async err => {
290
315
  this.log.error('could not refresh reservation to relay %p - %e', peerId, err)
291
-
292
- const reservation = this.reservations.get(peerId)
293
-
294
- if (reservation == null) {
295
- this.log.error('did not have reservation after refreshing reservation failed %p', peerId)
296
- return
297
- }
298
-
299
- await this.#removeReservation(peerId, reservation)
316
+ await this.#removeReservation(peerId)
300
317
  })
301
318
  .catch(err => {
302
319
  this.log.error('could not remove expired reservation to relay %p - %e', peerId, err)
303
320
  })
304
321
  }, timeoutDuration)
305
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
+
306
349
  // we've managed to create a reservation successfully
307
- this.reservations.set(peerId, {
308
- timeout,
309
- reservation,
310
- type,
311
- connection: connection.id
312
- })
350
+ this.reservations.set(peerId, res)
313
351
 
314
352
  // ensure we don't close the connection to the relay
315
353
  await this.peerStore.merge(peerId, {
316
354
  tags: {
317
- [RELAY_TAG]: {
318
- value: 1,
319
- ttl: expiration
320
- },
321
355
  [KEEP_ALIVE_TAG]: {
322
356
  value: 1,
323
357
  ttl: expiration
@@ -325,28 +359,37 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
325
359
  }
326
360
  })
327
361
 
328
- // listen on multiaddr that only the circuit transport is listening for
329
- await this.transportManager.listen([multiaddr(`/p2p/${peerId.toString()}/p2p-circuit/p2p/${this.peerId.toString()}`)])
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
+ }
330
369
 
331
370
  this.safeDispatchEvent('relay:created-reservation', {
332
- detail: peerId
371
+ detail: result
333
372
  })
334
- } catch (err) {
335
- this.log.error('could not reserve slot on %p after %dms', peerId, Date.now() - start, err)
336
373
 
337
- // don't try this peer again
338
- this.relayFilter.add(peerId.toMultihash().bytes)
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
+ }
339
379
 
340
- // cancel the renewal timeout if it's been set
341
- const reservation = this.reservations.get(peerId)
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)
384
+ }
342
385
 
343
386
  // if listening failed, remove the reservation
344
- if (reservation != null) {
345
- this.#removeReservation(peerId, reservation)
346
- .catch(err => {
347
- this.log.error('could not remove reservation on %p after reserving slot failed - %e', peerId, err)
348
- })
349
- }
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
+ })
391
+
392
+ throw err
350
393
  }
351
394
  }, {
352
395
  peerId
@@ -361,16 +404,26 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
361
404
  return this.reservations.get(peerId)?.reservation
362
405
  }
363
406
 
364
- reservationCount (): number {
365
- 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)
366
419
  }
367
420
 
368
- async cancelReservations (): Promise<void> {
369
- await Promise.all(
370
- [...this.reservations.entries()].map(async ([peerId, reservation]) => {
371
- await this.#removeReservation(peerId, reservation)
372
- })
373
- )
421
+ cancelReservations (): void {
422
+ [...this.reservations.values()].forEach(reservation => {
423
+ clearTimeout(reservation.timeout)
424
+ })
425
+
426
+ this.reservations.clear()
374
427
  }
375
428
 
376
429
  async #createReservation (connection: Connection, options: AbortOptions): Promise<Reservation> {
@@ -380,11 +433,14 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
380
433
  const stream = await connection.newStream(RELAY_V2_HOP_CODEC, options)
381
434
  const pbstr = pbStream(stream)
382
435
  const hopstr = pbstr.pb(HopMessage)
436
+
437
+ this.log.trace('send RESERVE to %p', connection.remotePeer)
383
438
  await hopstr.write({ type: HopMessage.Type.RESERVE }, options)
384
439
 
385
440
  let response: HopMessage
386
441
 
387
442
  try {
443
+ this.log.trace('read response from %p', connection.remotePeer)
388
444
  response = await hopstr.read(options)
389
445
  } catch (err: any) {
390
446
  stream.abort(err)
@@ -395,6 +451,8 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
395
451
  }
396
452
  }
397
453
 
454
+ this.log.trace('read response from %p', response)
455
+
398
456
  if (response.status === Status.OK && response.reservation != null) {
399
457
  // check that the returned relay has the relay address - this can be
400
458
  // omitted when requesting a reservation from a go-libp2p relay we
@@ -432,9 +490,10 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
432
490
  /**
433
491
  * Remove listen relay
434
492
  */
435
- async #removeReservation (peerId: PeerId, reservation: RelayEntry): Promise<void> {
436
- if (!this.reservations.has(peerId)) {
437
- this.log('not removing relay reservation with %p from local store as we do not have a reservation with this peer', peerId)
493
+ async #removeReservation (peerId: PeerId): Promise<void> {
494
+ const reservation = this.reservations.get(peerId)
495
+
496
+ if (reservation == null) {
438
497
  return
439
498
  }
440
499
 
@@ -442,19 +501,42 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
442
501
  clearTimeout(reservation.timeout)
443
502
  this.reservations.delete(peerId)
444
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
+
445
511
  // untag the relay
446
512
  await this.peerStore.merge(peerId, {
447
513
  tags: {
448
- [RELAY_TAG]: undefined,
449
514
  [KEEP_ALIVE_TAG]: undefined
450
515
  }
451
516
  })
452
517
 
453
- this.safeDispatchEvent('relay:removed', { detail: peerId })
518
+ this.safeDispatchEvent('relay:removed', {
519
+ detail: {
520
+ relay: peerId,
521
+ details: reservation
522
+ }
523
+ })
454
524
 
455
- if (this.reservations.size < this.maxDiscoveredRelays) {
456
- this.log('not enough relays %d/%d', this.reservations.size, this.maxDiscoveredRelays)
457
- this.safeDispatchEvent('relay:not-enough-relays', {})
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')
534
+
535
+ return
458
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')
459
541
  }
460
542
  }
@@ -9,7 +9,7 @@ import * as Digest from 'multiformats/hashes/digest'
9
9
  import { CustomProgressEvent } from 'progress-events'
10
10
  import { CIRCUIT_PROTO_CODE, DEFAULT_DISCOVERY_FILTER_ERROR_RATE, DEFAULT_DISCOVERY_FILTER_SIZE, MAX_CONNECTIONS, RELAY_V2_HOP_CODEC, RELAY_V2_STOP_CODEC } from '../constants.js'
11
11
  import { StopMessage, HopMessage, Status } from '../pb/index.js'
12
- import { CircuitListen, LimitTracker } from '../utils.js'
12
+ import { CircuitListen, CircuitSearch, LimitTracker } from '../utils.js'
13
13
  import { RelayDiscovery } from './discovery.js'
14
14
  import { createListener } from './listener.js'
15
15
  import { ReservationStore } from './reservation-store.js'
@@ -17,7 +17,7 @@ import type { CircuitRelayTransportComponents, CircuitRelayTransportInit } from
17
17
  import type { Transport, CreateListenerOptions, Listener, Upgrader, ComponentLogger, Logger, Connection, Stream, ConnectionGater, PeerId, PeerStore, OutboundConnectionUpgradeEvents, DialTransportOptions, OpenConnectionProgressEvents } from '@libp2p/interface'
18
18
  import type { AddressManager, ConnectionManager, IncomingStreamData, Registrar, TransportManager } from '@libp2p/interface-internal'
19
19
  import type { Multiaddr } from '@multiformats/multiaddr'
20
- import type { ProgressEvent, ProgressOptions } from 'progress-events'
20
+ import type { ProgressEvent } from 'progress-events'
21
21
 
22
22
  const isValidStop = (request: StopMessage): request is Required<StopMessage> => {
23
23
  if (request.peer == null) {
@@ -33,16 +33,6 @@ const isValidStop = (request: StopMessage): request is Required<StopMessage> =>
33
33
  return true
34
34
  }
35
35
 
36
- interface ConnectOptions extends ProgressOptions<CircuitRelayDialEvents> {
37
- stream: Stream
38
- connection: Connection
39
- destinationPeer: PeerId
40
- destinationAddr: Multiaddr
41
- relayAddr: Multiaddr
42
- ma: Multiaddr
43
- disconnectOnFailure: boolean
44
- }
45
-
46
36
  const defaults = {
47
37
  maxInboundStopStreams: MAX_CONNECTIONS,
48
38
  maxOutboundStopStreams: MAX_CONNECTIONS,
@@ -91,28 +81,23 @@ export class CircuitRelayTransport implements Transport<CircuitRelayDialEvents>
91
81
  this.maxOutboundStopStreams = init.maxOutboundStopStreams ?? defaults.maxOutboundStopStreams
92
82
  this.stopTimeout = init.stopTimeout ?? defaults.stopTimeout
93
83
 
94
- const discoverRelays = init.discoverRelays ?? 0
95
-
96
- if (discoverRelays > 0) {
97
- this.discovery = new RelayDiscovery(components, {
98
- filter: init.discoveryFilter ?? peerFilter(DEFAULT_DISCOVERY_FILTER_SIZE, DEFAULT_DISCOVERY_FILTER_ERROR_RATE)
99
- })
100
- this.discovery.addEventListener('relay:discover', (evt) => {
101
- this.reservationStore.addRelay(evt.detail, 'discovered')
102
- .catch(err => {
84
+ this.discovery = new RelayDiscovery(components, {
85
+ filter: init.discoveryFilter ?? peerFilter(DEFAULT_DISCOVERY_FILTER_SIZE, DEFAULT_DISCOVERY_FILTER_ERROR_RATE)
86
+ })
87
+ this.discovery.addEventListener('relay:discover', (evt) => {
88
+ this.reservationStore.addRelay(evt.detail, 'discovered')
89
+ .catch(err => {
90
+ if (err.name !== 'HadEnoughRelaysError' && err.name !== 'RelayQueueFullError') {
103
91
  this.log.error('could not add discovered relay %p', evt.detail, err)
104
- })
105
- })
106
- }
107
-
92
+ }
93
+ })
94
+ })
108
95
  this.reservationStore = new ReservationStore(components, init)
109
96
  this.reservationStore.addEventListener('relay:not-enough-relays', () => {
110
97
  this.discovery?.startDiscovery()
111
98
  })
112
- this.reservationStore.addEventListener('relay:created-reservation', () => {
113
- if (this.reservationStore.reservationCount() >= discoverRelays) {
114
- this.discovery?.stopDiscovery()
115
- }
99
+ this.reservationStore.addEventListener('relay:found-enough-relays', () => {
100
+ this.discovery?.stopDiscovery()
116
101
  })
117
102
 
118
103
  this.started = false
@@ -192,7 +177,6 @@ export class CircuitRelayTransport implements Transport<CircuitRelayDialEvents>
192
177
  const relayPeer = peerIdFromString(relayId)
193
178
  const destinationPeer = peerIdFromString(destinationId)
194
179
 
195
- let disconnectOnFailure = false
196
180
  const relayConnections = this.connectionManager.getConnections(relayPeer)
197
181
  let relayConnection = relayConnections[0]
198
182
 
@@ -203,7 +187,6 @@ export class CircuitRelayTransport implements Transport<CircuitRelayDialEvents>
203
187
 
204
188
  options.onProgress?.(new CustomProgressEvent('circuit-relay:open-connection'))
205
189
  relayConnection = await this.connectionManager.openConnection(relayPeer, options)
206
- disconnectOnFailure = true
207
190
  } else {
208
191
  options.onProgress?.(new CustomProgressEvent('circuit-relay:reuse-connection'))
209
192
  }
@@ -212,52 +195,22 @@ export class CircuitRelayTransport implements Transport<CircuitRelayDialEvents>
212
195
 
213
196
  try {
214
197
  options.onProgress?.(new CustomProgressEvent('circuit-relay:open-hop-stream'))
215
- stream = await relayConnection.newStream(RELAY_V2_HOP_CODEC)
216
-
217
- return await this.connectV2({
218
- stream,
219
- connection: relayConnection,
220
- destinationPeer,
221
- destinationAddr,
222
- relayAddr,
223
- ma,
224
- disconnectOnFailure,
225
- onProgress: options.onProgress
226
- })
227
- } catch (err: any) {
228
- this.log.error('circuit relay dial to destination %p via relay %p failed', destinationPeer, relayPeer, err)
198
+ stream = await relayConnection.newStream(RELAY_V2_HOP_CODEC, options)
229
199
 
230
- if (stream != null) {
231
- stream.abort(err)
232
- }
233
- disconnectOnFailure && await relayConnection.close()
234
- throw err
235
- }
236
- }
237
-
238
- async connectV2 (
239
- {
240
- stream, connection, destinationPeer,
241
- destinationAddr, relayAddr, ma,
242
- disconnectOnFailure,
243
- onProgress
244
- }: ConnectOptions
245
- ): Promise<Connection> {
246
- try {
247
200
  const pbstr = pbStream(stream)
248
201
  const hopstr = pbstr.pb(HopMessage)
249
202
 
250
- onProgress?.(new CustomProgressEvent('circuit-relay:write-connect-message'))
203
+ options.onProgress?.(new CustomProgressEvent('circuit-relay:write-connect-message'))
251
204
  await hopstr.write({
252
205
  type: HopMessage.Type.CONNECT,
253
206
  peer: {
254
207
  id: destinationPeer.toMultihash().bytes,
255
208
  addrs: [multiaddr(destinationAddr).bytes]
256
209
  }
257
- })
210
+ }, options)
258
211
 
259
- onProgress?.(new CustomProgressEvent('circuit-relay:read-connect-response'))
260
- const status = await hopstr.read()
212
+ options.onProgress?.(new CustomProgressEvent('circuit-relay:read-connect-response'))
213
+ const status = await hopstr.read(options)
261
214
 
262
215
  if (status.status !== Status.OK) {
263
216
  throw new InvalidMessageError(`failed to connect via relay with status ${status?.status?.toString() ?? 'undefined'}`)
@@ -277,12 +230,13 @@ export class CircuitRelayTransport implements Transport<CircuitRelayDialEvents>
277
230
  this.log('new outbound relayed connection %a', maConn.remoteAddr)
278
231
 
279
232
  return await this.upgrader.upgradeOutbound(maConn, {
280
- limits: limits.getLimits(),
281
- onProgress
233
+ ...options,
234
+ limits: limits.getLimits()
282
235
  })
283
236
  } catch (err: any) {
284
- this.log.error(`circuit relay dial to destination ${destinationPeer.toString()} via relay ${connection.remotePeer.toString()} failed`, err)
285
- disconnectOnFailure && await connection.close()
237
+ this.log.error('circuit relay dial to destination %p via relay %p failed', destinationPeer, relayPeer, err)
238
+ stream?.abort(err)
239
+
286
240
  throw err
287
241
  }
288
242
  }
@@ -305,7 +259,7 @@ export class CircuitRelayTransport implements Transport<CircuitRelayDialEvents>
305
259
  multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs]
306
260
 
307
261
  return multiaddrs.filter((ma) => {
308
- return CircuitListen.exactMatch(ma)
262
+ return CircuitListen.exactMatch(ma) || CircuitSearch.exactMatch(ma)
309
263
  })
310
264
  }
311
265