@libp2p/circuit-relay-v2 2.1.4 → 2.1.5-e6b4158c6

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 (42) hide show
  1. package/dist/index.min.js +2 -2
  2. package/dist/src/constants.d.ts +0 -4
  3. package/dist/src/constants.d.ts.map +1 -1
  4. package/dist/src/constants.js +0 -4
  5. package/dist/src/constants.js.map +1 -1
  6. package/dist/src/index.d.ts +16 -1
  7. package/dist/src/index.d.ts.map +1 -1
  8. package/dist/src/index.js.map +1 -1
  9. package/dist/src/pb/index.d.ts +12 -1
  10. package/dist/src/pb/index.d.ts.map +1 -1
  11. package/dist/src/pb/index.js +78 -2
  12. package/dist/src/pb/index.js.map +1 -1
  13. package/dist/src/server/index.d.ts.map +1 -1
  14. package/dist/src/server/index.js +19 -10
  15. package/dist/src/server/index.js.map +1 -1
  16. package/dist/src/server/reservation-store.d.ts +5 -9
  17. package/dist/src/server/reservation-store.d.ts.map +1 -1
  18. package/dist/src/server/reservation-store.js +32 -33
  19. package/dist/src/server/reservation-store.js.map +1 -1
  20. package/dist/src/server/reservation-voucher.d.ts +1 -1
  21. package/dist/src/transport/reservation-store.d.ts.map +1 -1
  22. package/dist/src/transport/reservation-store.js +36 -11
  23. package/dist/src/transport/reservation-store.js.map +1 -1
  24. package/dist/src/transport/transport.d.ts.map +1 -1
  25. package/dist/src/transport/transport.js +11 -8
  26. package/dist/src/transport/transport.js.map +1 -1
  27. package/dist/src/utils.d.ts +7 -1
  28. package/dist/src/utils.d.ts.map +1 -1
  29. package/dist/src/utils.js +19 -8
  30. package/dist/src/utils.js.map +1 -1
  31. package/package.json +12 -11
  32. package/src/constants.ts +0 -5
  33. package/src/index.ts +19 -1
  34. package/src/pb/index.proto +20 -1
  35. package/src/pb/index.ts +99 -3
  36. package/src/server/index.ts +21 -11
  37. package/src/server/reservation-store.ts +38 -42
  38. package/src/server/reservation-voucher.ts +2 -2
  39. package/src/transport/reservation-store.ts +47 -15
  40. package/src/transport/transport.ts +12 -8
  41. package/src/utils.ts +21 -8
  42. package/dist/typedoc-urls.json +0 -23
package/src/pb/index.ts CHANGED
@@ -318,7 +318,7 @@ export namespace Peer {
318
318
  export interface Reservation {
319
319
  expire: bigint
320
320
  addrs: Uint8Array[]
321
- voucher?: Uint8Array
321
+ voucher?: Envelope
322
322
  }
323
323
 
324
324
  export namespace Reservation {
@@ -345,7 +345,7 @@ export namespace Reservation {
345
345
 
346
346
  if (obj.voucher != null) {
347
347
  w.uint32(26)
348
- w.bytes(obj.voucher)
348
+ Envelope.codec().encode(obj.voucher, w)
349
349
  }
350
350
 
351
351
  if (opts.lengthDelimited !== false) {
@@ -376,7 +376,9 @@ export namespace Reservation {
376
376
  break
377
377
  }
378
378
  case 3: {
379
- obj.voucher = reader.bytes()
379
+ obj.voucher = Envelope.codec().decode(reader, reader.uint32(), {
380
+ limits: opts.limits?.voucher
381
+ })
380
382
  break
381
383
  }
382
384
  default: {
@@ -580,3 +582,97 @@ export namespace ReservationVoucher {
580
582
  return decodeMessage(buf, ReservationVoucher.codec(), opts)
581
583
  }
582
584
  }
585
+
586
+ export interface Envelope {
587
+ publicKey: Uint8Array
588
+ payloadType: Uint8Array
589
+ payload?: ReservationVoucher
590
+ signature: Uint8Array
591
+ }
592
+
593
+ export namespace Envelope {
594
+ let _codec: Codec<Envelope>
595
+
596
+ export const codec = (): Codec<Envelope> => {
597
+ if (_codec == null) {
598
+ _codec = message<Envelope>((obj, w, opts = {}) => {
599
+ if (opts.lengthDelimited !== false) {
600
+ w.fork()
601
+ }
602
+
603
+ if ((obj.publicKey != null && obj.publicKey.byteLength > 0)) {
604
+ w.uint32(10)
605
+ w.bytes(obj.publicKey)
606
+ }
607
+
608
+ if ((obj.payloadType != null && obj.payloadType.byteLength > 0)) {
609
+ w.uint32(18)
610
+ w.bytes(obj.payloadType)
611
+ }
612
+
613
+ if (obj.payload != null) {
614
+ w.uint32(26)
615
+ ReservationVoucher.codec().encode(obj.payload, w)
616
+ }
617
+
618
+ if ((obj.signature != null && obj.signature.byteLength > 0)) {
619
+ w.uint32(42)
620
+ w.bytes(obj.signature)
621
+ }
622
+
623
+ if (opts.lengthDelimited !== false) {
624
+ w.ldelim()
625
+ }
626
+ }, (reader, length, opts = {}) => {
627
+ const obj: any = {
628
+ publicKey: uint8ArrayAlloc(0),
629
+ payloadType: uint8ArrayAlloc(0),
630
+ signature: uint8ArrayAlloc(0)
631
+ }
632
+
633
+ const end = length == null ? reader.len : reader.pos + length
634
+
635
+ while (reader.pos < end) {
636
+ const tag = reader.uint32()
637
+
638
+ switch (tag >>> 3) {
639
+ case 1: {
640
+ obj.publicKey = reader.bytes()
641
+ break
642
+ }
643
+ case 2: {
644
+ obj.payloadType = reader.bytes()
645
+ break
646
+ }
647
+ case 3: {
648
+ obj.payload = ReservationVoucher.codec().decode(reader, reader.uint32(), {
649
+ limits: opts.limits?.payload
650
+ })
651
+ break
652
+ }
653
+ case 5: {
654
+ obj.signature = reader.bytes()
655
+ break
656
+ }
657
+ default: {
658
+ reader.skipType(tag & 7)
659
+ break
660
+ }
661
+ }
662
+ }
663
+
664
+ return obj
665
+ })
666
+ }
667
+
668
+ return _codec
669
+ }
670
+
671
+ export const encode = (obj: Partial<Envelope>): Uint8Array => {
672
+ return encodeMessage(obj, Envelope.codec())
673
+ }
674
+
675
+ export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions<Envelope>): Envelope => {
676
+ return decodeMessage(buf, Envelope.codec(), opts)
677
+ }
678
+ }
@@ -1,3 +1,4 @@
1
+ import { publicKeyToProtobuf } from '@libp2p/crypto/keys'
1
2
  import { TypedEventEmitter, setMaxListeners } from '@libp2p/interface'
2
3
  import { peerIdFromMultihash } from '@libp2p/peer-id'
3
4
  import { RecordEnvelope } from '@libp2p/peer-record'
@@ -156,8 +157,6 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
156
157
  runOnLimitedConnection: true
157
158
  })
158
159
 
159
- this.reservationStore.start()
160
-
161
160
  this.started = true
162
161
  }
163
162
 
@@ -165,7 +164,7 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
165
164
  * Stop Relay service
166
165
  */
167
166
  async stop (): Promise<void> {
168
- this.reservationStore.stop()
167
+ this.reservationStore.clear()
169
168
  this.shutdownController.abort()
170
169
  await this.registrar.unhandle(RELAY_V2_HOP_CODEC)
171
170
 
@@ -290,16 +289,25 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
290
289
  addrs.push(relayAddr.bytes)
291
290
  }
292
291
 
293
- const voucher = await RecordEnvelope.seal(new ReservationVoucherRecord({
292
+ const envelope = await RecordEnvelope.seal(new ReservationVoucherRecord({
294
293
  peer: remotePeer,
295
294
  relay: this.peerId,
296
- expiration: Number(expire)
295
+ expiration: expire
297
296
  }), this.privateKey)
298
297
 
299
298
  return {
300
299
  addrs,
301
300
  expire,
302
- voucher: voucher.marshal()
301
+ voucher: {
302
+ publicKey: publicKeyToProtobuf(envelope.publicKey),
303
+ payloadType: envelope.payloadType,
304
+ payload: {
305
+ peer: remotePeer.toMultihash().bytes,
306
+ relay: this.peerId.toMultihash().bytes,
307
+ expiration: expire
308
+ },
309
+ signature: envelope.signature
310
+ }
303
311
  }
304
312
  }
305
313
 
@@ -330,7 +338,9 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
330
338
  return
331
339
  }
332
340
 
333
- if (!this.reservationStore.hasReservation(dstPeer)) {
341
+ const reservation = this.reservationStore.get(dstPeer)
342
+
343
+ if (reservation == null) {
334
344
  this.log.error('hop connect denied for destination peer %p not having a reservation for %p with status %s', dstPeer, connection.remotePeer, Status.NO_RESERVATION)
335
345
  await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.NO_RESERVATION }, options)
336
346
  return
@@ -350,7 +360,6 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
350
360
  return
351
361
  }
352
362
 
353
- const limit = this.reservationStore.get(dstPeer)?.limit
354
363
  const destinationConnection = connections[0]
355
364
 
356
365
  const destinationStream = await this.stopHop({
@@ -361,7 +370,7 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
361
370
  id: connection.remotePeer.toMultihash().bytes,
362
371
  addrs: []
363
372
  },
364
- limit
373
+ limit: reservation?.limit
365
374
  }
366
375
  }, options)
367
376
 
@@ -374,13 +383,14 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
374
383
  await hopstr.write({
375
384
  type: HopMessage.Type.STATUS,
376
385
  status: Status.OK,
377
- limit
386
+ limit: reservation?.limit
378
387
  }, options)
379
388
  const sourceStream = stream.unwrap()
380
389
 
381
390
  this.log('connection from %p to %p established - merging streams', connection.remotePeer, dstPeer)
391
+
382
392
  // Short circuit the two streams to create the relayed connection
383
- createLimitedRelay(sourceStream, destinationStream, this.shutdownController.signal, limit, {
393
+ createLimitedRelay(sourceStream, destinationStream, this.shutdownController.signal, reservation, {
384
394
  log: this.log
385
395
  })
386
396
  }
@@ -1,14 +1,16 @@
1
1
  import { trackedPeerMap } from '@libp2p/peer-collections'
2
- import { DEFAULT_DATA_LIMIT, DEFAULT_DURATION_LIMIT, DEFAULT_MAX_RESERVATION_CLEAR_INTERVAL, DEFAULT_MAX_RESERVATION_STORE_SIZE, DEFAULT_MAX_RESERVATION_TTL } from '../constants.js'
2
+ import { retimeableSignal } from 'retimeable-signal'
3
+ import { DEFAULT_DATA_LIMIT, DEFAULT_DURATION_LIMIT, DEFAULT_MAX_RESERVATION_STORE_SIZE, DEFAULT_MAX_RESERVATION_TTL } from '../constants.js'
3
4
  import { type Limit, Status } from '../pb/index.js'
4
5
  import type { RelayReservation } from '../index.js'
5
- import type { Metrics, PeerId, Startable } from '@libp2p/interface'
6
+ import type { ComponentLogger, Logger, Metrics, PeerId } from '@libp2p/interface'
6
7
  import type { PeerMap } from '@libp2p/peer-collections'
7
8
  import type { Multiaddr } from '@multiformats/multiaddr'
8
9
 
9
10
  export type ReservationStatus = Status.OK | Status.PERMISSION_DENIED | Status.RESERVATION_REFUSED
10
11
 
11
12
  export interface ReservationStoreComponents {
13
+ logger: ComponentLogger
12
14
  metrics?: Metrics
13
15
  }
14
16
 
@@ -52,20 +54,18 @@ export interface ReservationStoreInit {
52
54
  defaultDataLimit?: bigint
53
55
  }
54
56
 
55
- export class ReservationStore implements Startable {
57
+ export class ReservationStore {
56
58
  public readonly reservations: PeerMap<RelayReservation>
57
- private _started = false
58
- private interval: any
59
59
  private readonly maxReservations: number
60
- private readonly reservationClearInterval: number
61
60
  private readonly applyDefaultLimit: boolean
62
61
  private readonly reservationTtl: number
63
62
  private readonly defaultDurationLimit: number
64
63
  private readonly defaultDataLimit: bigint
64
+ private readonly log: Logger
65
65
 
66
66
  constructor (components: ReservationStoreComponents, init: ReservationStoreInit = {}) {
67
+ this.log = components.logger.forComponent('libp2p:circuit-relay:server:reservation-store')
67
68
  this.maxReservations = init.maxReservations ?? DEFAULT_MAX_RESERVATION_STORE_SIZE
68
- this.reservationClearInterval = init.reservationClearInterval ?? DEFAULT_MAX_RESERVATION_CLEAR_INTERVAL
69
69
  this.applyDefaultLimit = init.applyDefaultLimit !== false
70
70
  this.reservationTtl = init.reservationTtl ?? DEFAULT_MAX_RESERVATION_TTL
71
71
  this.defaultDurationLimit = init.defaultDurationLimit ?? DEFAULT_DURATION_LIMIT
@@ -77,59 +77,55 @@ export class ReservationStore implements Startable {
77
77
  })
78
78
  }
79
79
 
80
- isStarted (): boolean {
81
- return this._started
82
- }
83
-
84
- start (): void {
85
- if (this._started) {
86
- return
87
- }
88
- this._started = true
89
- this.interval = setInterval(
90
- () => {
91
- const now = (new Date()).getTime()
92
- this.reservations.forEach((r, k) => {
93
- if (r.expire.getTime() < now) {
94
- this.reservations.delete(k)
95
- }
96
- })
97
- },
98
- this.reservationClearInterval
99
- )
100
- }
101
-
102
- stop (): void {
103
- clearInterval(this.interval)
104
- }
105
-
106
80
  reserve (peer: PeerId, addr: Multiaddr, limit?: Limit): { status: ReservationStatus, expire?: number } {
107
- if (this.reservations.size >= this.maxReservations && !this.reservations.has(peer)) {
81
+ let reservation = this.reservations.get(peer)
82
+
83
+ if (this.reservations.size >= this.maxReservations && reservation == null) {
108
84
  return { status: Status.RESERVATION_REFUSED }
109
85
  }
110
86
 
111
- const expire = new Date(Date.now() + this.reservationTtl)
87
+ const expiry = new Date(Date.now() + this.reservationTtl)
112
88
  let checkedLimit: Limit | undefined
113
89
 
114
90
  if (this.applyDefaultLimit) {
115
- checkedLimit = limit ?? { data: this.defaultDataLimit, duration: this.defaultDurationLimit }
91
+ checkedLimit = limit ?? {
92
+ data: this.defaultDataLimit,
93
+ duration: this.defaultDurationLimit
94
+ }
116
95
  }
117
96
 
118
- this.reservations.set(peer, { addr, expire, limit: checkedLimit })
97
+ if (reservation != null) {
98
+ this.log('refreshing reservation for client %p', peer)
99
+ reservation.signal.reset(this.reservationTtl)
100
+ } else {
101
+ this.log('creating new reservation for client %p', peer)
102
+ reservation = {
103
+ addr,
104
+ expiry,
105
+ limit: checkedLimit,
106
+ signal: retimeableSignal(this.reservationTtl)
107
+ }
108
+ }
109
+
110
+ this.reservations.set(peer, reservation)
111
+
112
+ reservation.signal.addEventListener('abort', () => {
113
+ this.reservations.delete(peer)
114
+ })
119
115
 
120
116
  // return expiry time in seconds
121
- return { status: Status.OK, expire: Math.round(expire.getTime() / 1000) }
117
+ return { status: Status.OK, expire: Math.round(expiry.getTime() / 1000) }
122
118
  }
123
119
 
124
120
  removeReservation (peer: PeerId): void {
125
121
  this.reservations.delete(peer)
126
122
  }
127
123
 
128
- hasReservation (dst: PeerId): boolean {
129
- return this.reservations.has(dst)
130
- }
131
-
132
124
  get (peer: PeerId): RelayReservation | undefined {
133
125
  return this.reservations.get(peer)
134
126
  }
127
+
128
+ clear (): void {
129
+ this.reservations.clear()
130
+ }
135
131
  }
@@ -4,7 +4,7 @@ import type { PeerId, Record } from '@libp2p/interface'
4
4
  export interface ReservationVoucherOptions {
5
5
  relay: PeerId
6
6
  peer: PeerId
7
- expiration: number
7
+ expiration: bigint
8
8
  }
9
9
 
10
10
  export class ReservationVoucherRecord implements Record {
@@ -13,7 +13,7 @@ export class ReservationVoucherRecord implements Record {
13
13
 
14
14
  private readonly relay: PeerId
15
15
  private readonly peer: PeerId
16
- private readonly expiration: number
16
+ private readonly expiration: bigint
17
17
 
18
18
  constructor ({ relay, peer, expiration }: ReservationVoucherOptions) {
19
19
  this.relay = relay
@@ -227,12 +227,23 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
227
227
  const existingReservation = this.reservations.get(peerId)
228
228
 
229
229
  if (existingReservation != null) {
230
- if (this.connectionManager.getConnections(peerId).map(conn => conn.id).includes(existingReservation.connection) && getExpirationMilliseconds(existingReservation.reservation.expire) > REFRESH_WINDOW) {
230
+ const connections = this.connectionManager.getConnections(peerId)
231
+ let connected = false
232
+
233
+ if (connections.length === 0) {
234
+ this.log('already have relay reservation with %p but we are no longer connected', peerId)
235
+ }
236
+
237
+ if (connections.map(conn => conn.id).includes(existingReservation.connection)) {
238
+ this.log('already have relay reservation with %p and the original connection is still open', peerId)
239
+ connected = true
240
+ }
241
+
242
+ if (connected && getExpirationMilliseconds(existingReservation.reservation.expire) > REFRESH_WINDOW) {
231
243
  this.log('already have relay reservation with %p but we are still connected and it does not expire soon', peerId)
232
244
  return
233
245
  }
234
246
 
235
- this.log('already have relay reservation with %p but the original connection is no longer open', peerId)
236
247
  await this.#removeReservation(peerId, existingReservation)
237
248
  }
238
249
 
@@ -263,18 +274,33 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
263
274
  signal
264
275
  })
265
276
 
266
- this.log('created reservation on relay peer %p', peerId)
267
-
268
277
  const expiration = getExpirationMilliseconds(reservation.expire)
269
278
 
279
+ this.log('created reservation on relay peer %p, expiry date is %s', peerId, new Date(Date.now() + expiration).toString())
280
+
270
281
  // sets a lower bound on the timeout, and also don't let it go over
271
282
  // 2^31 - 1 (setTimeout will only accept signed 32 bit integers)
272
283
  const timeoutDuration = Math.min(Math.max(expiration - REFRESH_TIMEOUT, REFRESH_TIMEOUT_MIN), Math.pow(2, 31) - 1)
273
284
 
274
285
  const timeout = setTimeout(() => {
275
- this.addRelay(peerId, type).catch(err => {
276
- this.log.error('could not refresh reservation to relay %p', peerId, err)
277
- })
286
+ this.log('refresh reservation to relay %p', peerId)
287
+
288
+ this.addRelay(peerId, type)
289
+ .catch(async err => {
290
+ 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)
300
+ })
301
+ .catch(err => {
302
+ this.log.error('could not remove expired reservation to relay %p - %e', peerId, err)
303
+ })
278
304
  }, timeoutDuration)
279
305
 
280
306
  // we've managed to create a reservation successfully
@@ -300,7 +326,7 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
300
326
  })
301
327
 
302
328
  // listen on multiaddr that only the circuit transport is listening for
303
- await this.transportManager.listen([multiaddr(`/p2p/${peerId.toString()}/p2p-circuit`)])
329
+ await this.transportManager.listen([multiaddr(`/p2p/${peerId.toString()}/p2p-circuit/p2p/${this.peerId.toString()}`)])
304
330
 
305
331
  this.safeDispatchEvent('relay:created-reservation', {
306
332
  detail: peerId
@@ -308,18 +334,19 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
308
334
  } catch (err) {
309
335
  this.log.error('could not reserve slot on %p after %dms', peerId, Date.now() - start, err)
310
336
 
337
+ // don't try this peer again
338
+ this.relayFilter.add(peerId.toMultihash().bytes)
339
+
311
340
  // cancel the renewal timeout if it's been set
312
341
  const reservation = this.reservations.get(peerId)
313
342
 
343
+ // if listening failed, remove the reservation
314
344
  if (reservation != null) {
315
- clearTimeout(reservation.timeout)
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
+ })
316
349
  }
317
-
318
- // if listening failed, remove the reservation
319
- this.reservations.delete(peerId)
320
-
321
- // don't try this peer again
322
- this.relayFilter.add(peerId.toMultihash().bytes)
323
350
  }
324
351
  }, {
325
352
  peerId
@@ -406,6 +433,11 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
406
433
  * Remove listen relay
407
434
  */
408
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)
438
+ return
439
+ }
440
+
409
441
  this.log('removing relay reservation with %p from local store', peerId)
410
442
  clearTimeout(reservation.timeout)
411
443
  this.reservations.delete(peerId)
@@ -2,14 +2,14 @@ import { DialError, InvalidMessageError, serviceCapabilities, serviceDependencie
2
2
  import { peerFilter } from '@libp2p/peer-collections'
3
3
  import { peerIdFromMultihash, peerIdFromString } from '@libp2p/peer-id'
4
4
  import { streamToMaConnection } from '@libp2p/utils/stream-to-ma-conn'
5
- import * as mafmt from '@multiformats/mafmt'
6
5
  import { multiaddr } from '@multiformats/multiaddr'
6
+ import { Circuit } from '@multiformats/multiaddr-matcher'
7
7
  import { pbStream } from 'it-protobuf-stream'
8
8
  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 { LimitTracker } from '../utils.js'
12
+ import { CircuitListen, 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'
@@ -184,9 +184,9 @@ export class CircuitRelayTransport implements Transport<CircuitRelayDialEvents>
184
184
  const destinationId = destinationAddr.getPeerId()
185
185
 
186
186
  if (relayId == null || destinationId == null) {
187
- const errMsg = `Circuit relay dial to ${ma.toString()} failed as address did not have peer ids`
188
- this.log.error(errMsg)
189
- throw new DialError(errMsg)
187
+ const errMsg = `ircuit relay dial to ${ma.toString()} failed as address did not have both relay and destination PeerIDs`
188
+ this.log.error(`c${errMsg}`)
189
+ throw new DialError(`C${errMsg}`)
190
190
  }
191
191
 
192
192
  const relayPeer = peerIdFromString(relayId)
@@ -281,7 +281,7 @@ export class CircuitRelayTransport implements Transport<CircuitRelayDialEvents>
281
281
  onProgress
282
282
  })
283
283
  } catch (err: any) {
284
- this.log.error(`Circuit relay dial to destination ${destinationPeer.toString()} via relay ${connection.remotePeer.toString()} failed`, err)
284
+ this.log.error(`circuit relay dial to destination ${destinationPeer.toString()} via relay ${connection.remotePeer.toString()} failed`, err)
285
285
  disconnectOnFailure && await connection.close()
286
286
  throw err
287
287
  }
@@ -305,7 +305,7 @@ export class CircuitRelayTransport implements Transport<CircuitRelayDialEvents>
305
305
  multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs]
306
306
 
307
307
  return multiaddrs.filter((ma) => {
308
- return mafmt.Circuit.matches(ma)
308
+ return CircuitListen.exactMatch(ma)
309
309
  })
310
310
  }
311
311
 
@@ -313,7 +313,11 @@ export class CircuitRelayTransport implements Transport<CircuitRelayDialEvents>
313
313
  * Filter check for all Multiaddrs that this transport can dial
314
314
  */
315
315
  dialFilter (multiaddrs: Multiaddr[]): Multiaddr[] {
316
- return this.listenFilter(multiaddrs)
316
+ multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs]
317
+
318
+ return multiaddrs.filter((ma) => {
319
+ return Circuit.exactMatch(ma)
320
+ })
317
321
  }
318
322
 
319
323
  /**
package/src/utils.ts CHANGED
@@ -1,7 +1,10 @@
1
+ import { P2P } from '@multiformats/multiaddr-matcher'
2
+ import { fmt, peerId, literal, optional, and } from '@multiformats/multiaddr-matcher/utils'
1
3
  import { anySignal } from 'any-signal'
2
4
  import { CID } from 'multiformats/cid'
3
5
  import { sha256 } from 'multiformats/hashes/sha2'
4
6
  import { DurationLimitError, TransferLimitError } from './errors.js'
7
+ import type { RelayReservation } from './index.js'
5
8
  import type { Limit } from './pb/index.js'
6
9
  import type { ConnectionLimits, LoggerOptions, Stream } from '@libp2p/interface'
7
10
  import type { Source } from 'it-stream-types'
@@ -34,16 +37,18 @@ async function * countStreamBytes (source: Source<Uint8Array | Uint8ArrayList>,
34
37
  }
35
38
  }
36
39
 
37
- export function createLimitedRelay (src: Stream, dst: Stream, abortSignal: AbortSignal, limit: Limit | undefined, options: LoggerOptions): void {
40
+ export function createLimitedRelay (src: Stream, dst: Stream, abortSignal: AbortSignal, reservation: RelayReservation, options: LoggerOptions): void {
38
41
  function abortStreams (err: Error): void {
39
42
  src.abort(err)
40
43
  dst.abort(err)
41
44
  }
42
45
 
43
- const signals = [abortSignal]
46
+ // combine shutdown signal and reservation expiry signal
47
+ const signals = [abortSignal, reservation.signal]
44
48
 
45
- if (limit?.duration != null) {
46
- signals.push(AbortSignal.timeout(limit.duration))
49
+ if (reservation.limit?.duration != null) {
50
+ options.log('limiting relayed connection duration to %dms', reservation.limit.duration)
51
+ signals.push(AbortSignal.timeout(reservation.limit.duration))
47
52
  }
48
53
 
49
54
  const signal = anySignal(signals)
@@ -53,15 +58,16 @@ export function createLimitedRelay (src: Stream, dst: Stream, abortSignal: Abort
53
58
 
54
59
  let dataLimit: { remaining: bigint } | undefined
55
60
 
56
- if (limit?.data != null) {
61
+ if (reservation.limit?.data != null) {
57
62
  dataLimit = {
58
- remaining: limit.data
63
+ remaining: reservation.limit.data
59
64
  }
60
65
  }
61
66
 
62
67
  queueMicrotask(() => {
63
68
  const onAbort = (): void => {
64
- dst.abort(new DurationLimitError(`duration limit of ${limit?.duration} ms exceeded`))
69
+ options.log('relayed connection reached time limit')
70
+ dst.abort(new DurationLimitError(`duration limit of ${reservation.limit?.duration} ms exceeded`))
65
71
  }
66
72
 
67
73
  signal.addEventListener('abort', onAbort, { once: true })
@@ -83,7 +89,8 @@ export function createLimitedRelay (src: Stream, dst: Stream, abortSignal: Abort
83
89
 
84
90
  queueMicrotask(() => {
85
91
  const onAbort = (): void => {
86
- src.abort(new DurationLimitError(`duration limit of ${limit?.duration} ms exceeded`))
92
+ options.log('relayed connection reached time limit')
93
+ src.abort(new DurationLimitError(`duration limit of ${reservation.limit?.duration} ms exceeded`))
87
94
  }
88
95
 
89
96
  signal.addEventListener('abort', onAbort, { once: true })
@@ -185,3 +192,9 @@ export class LimitTracker {
185
192
  return output
186
193
  }
187
194
  }
195
+
196
+ /**
197
+ * A custom matcher that makes the `/p2p/peer-id` part of circuit relay
198
+ * addresses optional
199
+ */
200
+ export const CircuitListen = fmt(and(P2P.matchers[0], literal('p2p-circuit'), optional(peerId())))
@@ -1,23 +0,0 @@
1
- {
2
- "codec": "https://libp2p.github.io/js-libp2p/functions/_libp2p_circuit_relay_v2.Limit.codec.html",
3
- "decode": "https://libp2p.github.io/js-libp2p/functions/_libp2p_circuit_relay_v2.Limit.decode.html",
4
- "encode": "https://libp2p.github.io/js-libp2p/functions/_libp2p_circuit_relay_v2.Limit.encode.html",
5
- "CircuitRelayServerComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_circuit_relay_v2.CircuitRelayServerComponents.html",
6
- "CircuitRelayServerInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_circuit_relay_v2.CircuitRelayServerInit.html",
7
- "CircuitRelayService": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_circuit_relay_v2.CircuitRelayService.html",
8
- ".:CircuitRelayService": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_circuit_relay_v2.CircuitRelayService.html",
9
- "CircuitRelayServiceEvents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_circuit_relay_v2.CircuitRelayServiceEvents.html",
10
- ".:CircuitRelayServiceEvents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_circuit_relay_v2.CircuitRelayServiceEvents.html",
11
- "CircuitRelayTransportComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_circuit_relay_v2.CircuitRelayTransportComponents.html",
12
- "CircuitRelayTransportInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_circuit_relay_v2.CircuitRelayTransportInit.html",
13
- "Limit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_circuit_relay_v2.Limit-1.html",
14
- "RelayDiscoveryComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_circuit_relay_v2.RelayDiscoveryComponents.html",
15
- "RelayReservation": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_circuit_relay_v2.RelayReservation.html",
16
- ".:RelayReservation": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_circuit_relay_v2.RelayReservation.html",
17
- "ServerReservationStoreInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_circuit_relay_v2.ServerReservationStoreInit.html",
18
- "TransportReservationStoreInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_circuit_relay_v2.TransportReservationStoreInit.html",
19
- "RELAY_V2_HOP_CODEC": "https://libp2p.github.io/js-libp2p/variables/_libp2p_circuit_relay_v2.RELAY_V2_HOP_CODEC.html",
20
- "RELAY_V2_STOP_CODEC": "https://libp2p.github.io/js-libp2p/variables/_libp2p_circuit_relay_v2.RELAY_V2_STOP_CODEC.html",
21
- "circuitRelayServer": "https://libp2p.github.io/js-libp2p/functions/_libp2p_circuit_relay_v2.circuitRelayServer.html",
22
- "circuitRelayTransport": "https://libp2p.github.io/js-libp2p/functions/_libp2p_circuit_relay_v2.circuitRelayTransport.html"
23
- }