@libp2p/circuit-relay-v2 2.1.5 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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