@libp2p/circuit-relay-v2 2.1.3 → 2.1.4-5d199f9b6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. package/dist/index.min.js +2 -2
  2. package/dist/src/constants.d.ts +4 -6
  3. package/dist/src/constants.d.ts.map +1 -1
  4. package/dist/src/constants.js +5 -6
  5. package/dist/src/constants.js.map +1 -1
  6. package/dist/src/index.d.ts +18 -3
  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 +71 -60
  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/discovery.js +1 -1
  22. package/dist/src/transport/discovery.js.map +1 -1
  23. package/dist/src/transport/index.d.ts +2 -2
  24. package/dist/src/transport/index.d.ts.map +1 -1
  25. package/dist/src/transport/listener.d.ts.map +1 -1
  26. package/dist/src/transport/listener.js +23 -22
  27. package/dist/src/transport/listener.js.map +1 -1
  28. package/dist/src/transport/reservation-store.d.ts +4 -3
  29. package/dist/src/transport/reservation-store.d.ts.map +1 -1
  30. package/dist/src/transport/reservation-store.js +111 -47
  31. package/dist/src/transport/reservation-store.js.map +1 -1
  32. package/dist/src/transport/transport.d.ts.map +1 -1
  33. package/dist/src/transport/transport.js +11 -8
  34. package/dist/src/transport/transport.js.map +1 -1
  35. package/dist/src/utils.d.ts +7 -1
  36. package/dist/src/utils.d.ts.map +1 -1
  37. package/dist/src/utils.js +19 -8
  38. package/dist/src/utils.js.map +1 -1
  39. package/package.json +12 -12
  40. package/src/constants.ts +7 -7
  41. package/src/index.ts +21 -3
  42. package/src/pb/index.proto +20 -1
  43. package/src/pb/index.ts +99 -3
  44. package/src/server/index.ts +75 -64
  45. package/src/server/reservation-store.ts +38 -42
  46. package/src/server/reservation-voucher.ts +2 -2
  47. package/src/transport/discovery.ts +1 -1
  48. package/src/transport/index.ts +2 -2
  49. package/src/transport/listener.ts +28 -25
  50. package/src/transport/reservation-store.ts +147 -57
  51. package/src/transport/transport.ts +12 -8
  52. package/src/utils.ts +21 -8
  53. package/dist/typedoc-urls.json +0 -23
@@ -1,13 +1,14 @@
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'
4
5
  import { type Multiaddr, multiaddr } from '@multiformats/multiaddr'
5
6
  import { pbStream, type ProtobufStream } from 'it-protobuf-stream'
6
7
  import * as Digest from 'multiformats/hashes/digest'
7
- import pDefer from 'p-defer'
8
8
  import {
9
9
  CIRCUIT_PROTO_CODE,
10
10
  DEFAULT_HOP_TIMEOUT,
11
+ KEEP_ALIVE_SOURCE_TAG,
11
12
  MAX_CONNECTIONS,
12
13
  RELAY_SOURCE_TAG,
13
14
  RELAY_V2_HOP_CODEC,
@@ -18,7 +19,7 @@ import { createLimitedRelay } from '../utils.js'
18
19
  import { ReservationStore, type ReservationStoreInit } from './reservation-store.js'
19
20
  import { ReservationVoucherRecord } from './reservation-voucher.js'
20
21
  import type { CircuitRelayService, RelayReservation } from '../index.js'
21
- import type { ComponentLogger, Logger, Connection, Stream, ConnectionGater, PeerId, PeerStore, Startable, PrivateKey, Metrics } from '@libp2p/interface'
22
+ import type { ComponentLogger, Logger, Connection, Stream, ConnectionGater, PeerId, PeerStore, Startable, PrivateKey, Metrics, AbortOptions } from '@libp2p/interface'
22
23
  import type { AddressManager, ConnectionManager, IncomingStreamData, Registrar } from '@libp2p/interface-internal'
23
24
  import type { PeerMap } from '@libp2p/peer-collections'
24
25
 
@@ -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
 
@@ -175,17 +174,13 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
175
174
  async onHop ({ connection, stream }: IncomingStreamData): Promise<void> {
176
175
  this.log('received circuit v2 hop protocol stream from %p', connection.remotePeer)
177
176
 
178
- const hopTimeoutPromise = pDefer<HopMessage>()
179
- const timeout = setTimeout(() => {
180
- hopTimeoutPromise.reject('timed out')
181
- }, this.hopTimeout)
177
+ const options = {
178
+ signal: AbortSignal.timeout(this.hopTimeout)
179
+ }
182
180
  const pbstr = pbStream(stream)
183
181
 
184
182
  try {
185
- const request: HopMessage = await Promise.race([
186
- pbstr.pb(HopMessage).read(),
187
- hopTimeoutPromise.promise
188
- ])
183
+ const request: HopMessage = await pbstr.pb(HopMessage).read(options)
189
184
 
190
185
  if (request?.type == null) {
191
186
  throw new Error('request was invalid, could not read from stream')
@@ -193,31 +188,26 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
193
188
 
194
189
  this.log('received', request.type)
195
190
 
196
- await Promise.race([
197
- this.handleHopProtocol({
198
- connection,
199
- stream: pbstr,
200
- request
201
- }),
202
- hopTimeoutPromise.promise
203
- ])
191
+ await this.handleHopProtocol({
192
+ connection,
193
+ stream: pbstr,
194
+ request
195
+ }, options)
204
196
  } catch (err: any) {
205
197
  this.log.error('error while handling hop', err)
206
198
  await pbstr.pb(HopMessage).write({
207
199
  type: HopMessage.Type.STATUS,
208
200
  status: Status.MALFORMED_MESSAGE
209
- })
201
+ }, options)
210
202
  stream.abort(err)
211
- } finally {
212
- clearTimeout(timeout)
213
203
  }
214
204
  }
215
205
 
216
- async handleHopProtocol ({ stream, request, connection }: HopProtocolOptions): Promise<void> {
206
+ async handleHopProtocol ({ stream, request, connection }: HopProtocolOptions, options: AbortOptions): Promise<void> {
217
207
  this.log('received hop message')
218
208
  switch (request.type) {
219
- case HopMessage.Type.RESERVE: await this.handleReserve({ stream, request, connection }); break
220
- case HopMessage.Type.CONNECT: await this.handleConnect({ stream, request, connection }); break
209
+ case HopMessage.Type.RESERVE: await this.handleReserve({ stream, request, connection }, options); break
210
+ case HopMessage.Type.CONNECT: await this.handleConnect({ stream, request, connection }, options); break
221
211
  default: {
222
212
  this.log.error('invalid hop request type %s via peer %p', request.type, connection.remotePeer)
223
213
  await stream.pb(HopMessage).write({ type: HopMessage.Type.STATUS, status: Status.UNEXPECTED_MESSAGE })
@@ -225,37 +215,38 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
225
215
  }
226
216
  }
227
217
 
228
- async handleReserve ({ stream, request, connection }: HopProtocolOptions): Promise<void> {
218
+ async handleReserve ({ stream, connection }: HopProtocolOptions, options: AbortOptions): Promise<void> {
229
219
  const hopstr = stream.pb(HopMessage)
230
220
  this.log('hop reserve request from %p', connection.remotePeer)
231
221
 
232
222
  if (isRelayAddr(connection.remoteAddr)) {
233
223
  this.log.error('relay reservation over circuit connection denied for peer: %p', connection.remotePeer)
234
- await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED })
224
+ await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED }, options)
235
225
  return
236
226
  }
237
227
 
238
228
  if ((await this.connectionGater.denyInboundRelayReservation?.(connection.remotePeer)) === true) {
239
229
  this.log.error('reservation for %p denied by connection gater', connection.remotePeer)
240
- await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED })
230
+ await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED }, options)
241
231
  return
242
232
  }
243
233
 
244
234
  const result = this.reservationStore.reserve(connection.remotePeer, connection.remoteAddr)
245
235
 
246
- if (result.status !== Status.OK) {
247
- await hopstr.write({ type: HopMessage.Type.STATUS, status: result.status })
248
- return
249
- }
250
-
251
236
  try {
237
+ if (result.status !== Status.OK) {
238
+ await hopstr.write({ type: HopMessage.Type.STATUS, status: result.status }, options)
239
+ return
240
+ }
241
+
252
242
  // tag relay target peer
253
243
  // result.expire is non-null if `ReservationStore.reserve` returns with status == OK
254
244
  if (result.expire != null) {
255
245
  const ttl = (result.expire * 1000) - Date.now()
256
246
  await this.peerStore.merge(connection.remotePeer, {
257
247
  tags: {
258
- [RELAY_SOURCE_TAG]: { value: 1, ttl }
248
+ [RELAY_SOURCE_TAG]: { value: 1, ttl },
249
+ [KEEP_ALIVE_SOURCE_TAG]: { value: 1, ttl }
259
250
  }
260
251
  })
261
252
  }
@@ -265,11 +256,22 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
265
256
  status: Status.OK,
266
257
  reservation: await this.makeReservation(connection.remotePeer, BigInt(result.expire ?? 0)),
267
258
  limit: this.reservationStore.get(connection.remotePeer)?.limit
268
- })
259
+ }, options)
269
260
  this.log('sent confirmation response to %s', connection.remotePeer)
270
261
  } catch (err) {
271
- this.log.error('failed to send confirmation response to %p', connection.remotePeer, err)
262
+ this.log.error('failed to send confirmation response to %p - %e', connection.remotePeer, err)
272
263
  this.reservationStore.removeReservation(connection.remotePeer)
264
+
265
+ try {
266
+ await this.peerStore.merge(connection.remotePeer, {
267
+ tags: {
268
+ [RELAY_SOURCE_TAG]: undefined,
269
+ [KEEP_ALIVE_SOURCE_TAG]: undefined
270
+ }
271
+ })
272
+ } catch (err) {
273
+ this.log.error('failed to untag relay source peer %p - %e', connection.remotePeer, err)
274
+ }
273
275
  }
274
276
  }
275
277
 
@@ -287,25 +289,34 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
287
289
  addrs.push(relayAddr.bytes)
288
290
  }
289
291
 
290
- const voucher = await RecordEnvelope.seal(new ReservationVoucherRecord({
292
+ const envelope = await RecordEnvelope.seal(new ReservationVoucherRecord({
291
293
  peer: remotePeer,
292
294
  relay: this.peerId,
293
- expiration: Number(expire)
295
+ expiration: expire
294
296
  }), this.privateKey)
295
297
 
296
298
  return {
297
299
  addrs,
298
300
  expire,
299
- 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
+ }
300
311
  }
301
312
  }
302
313
 
303
- async handleConnect ({ stream, request, connection }: HopProtocolOptions): Promise<void> {
314
+ async handleConnect ({ stream, request, connection }: HopProtocolOptions, options: AbortOptions): Promise<void> {
304
315
  const hopstr = stream.pb(HopMessage)
305
316
 
306
317
  if (isRelayAddr(connection.remoteAddr)) {
307
318
  this.log.error('relay reservation over circuit connection denied for peer: %p', connection.remotePeer)
308
- await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED })
319
+ await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED }, options)
309
320
  return
310
321
  }
311
322
 
@@ -323,19 +334,21 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
323
334
  dstPeer = peerIdFromMultihash(Digest.decode(request.peer.id))
324
335
  } catch (err) {
325
336
  this.log.error('invalid hop connect request via peer %p %s', connection.remotePeer, err)
326
- await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.MALFORMED_MESSAGE })
337
+ await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.MALFORMED_MESSAGE }, options)
327
338
  return
328
339
  }
329
340
 
330
- if (!this.reservationStore.hasReservation(dstPeer)) {
341
+ const reservation = this.reservationStore.get(dstPeer)
342
+
343
+ if (reservation == null) {
331
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)
332
- await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.NO_RESERVATION })
345
+ await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.NO_RESERVATION }, options)
333
346
  return
334
347
  }
335
348
 
336
349
  if ((await this.connectionGater.denyOutboundRelayedConnection?.(connection.remotePeer, dstPeer)) === true) {
337
350
  this.log.error('hop connect for %p to %p denied by connection gater', connection.remotePeer, dstPeer)
338
- await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED })
351
+ await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED }, options)
339
352
  return
340
353
  }
341
354
 
@@ -343,11 +356,10 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
343
356
 
344
357
  if (connections.length === 0) {
345
358
  this.log('hop connect denied for destination peer %p not having a connection for %p as there is no destination connection', dstPeer, connection.remotePeer)
346
- await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.NO_RESERVATION })
359
+ await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.NO_RESERVATION }, options)
347
360
  return
348
361
  }
349
362
 
350
- const limit = this.reservationStore.get(dstPeer)?.limit
351
363
  const destinationConnection = connections[0]
352
364
 
353
365
  const destinationStream = await this.stopHop({
@@ -358,26 +370,27 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
358
370
  id: connection.remotePeer.toMultihash().bytes,
359
371
  addrs: []
360
372
  },
361
- limit
373
+ limit: reservation?.limit
362
374
  }
363
- })
375
+ }, options)
364
376
 
365
377
  if (destinationStream == null) {
366
378
  this.log.error('failed to open stream to destination peer %p', destinationConnection?.remotePeer)
367
- await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.CONNECTION_FAILED })
379
+ await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.CONNECTION_FAILED }, options)
368
380
  return
369
381
  }
370
382
 
371
383
  await hopstr.write({
372
384
  type: HopMessage.Type.STATUS,
373
385
  status: Status.OK,
374
- limit
375
- })
386
+ limit: reservation?.limit
387
+ }, options)
376
388
  const sourceStream = stream.unwrap()
377
389
 
378
390
  this.log('connection from %p to %p established - merging streams', connection.remotePeer, dstPeer)
391
+
379
392
  // Short circuit the two streams to create the relayed connection
380
- createLimitedRelay(sourceStream, destinationStream, this.shutdownController.signal, limit, {
393
+ createLimitedRelay(sourceStream, destinationStream, this.shutdownController.signal, reservation, {
381
394
  log: this.log
382
395
  })
383
396
  }
@@ -385,29 +398,27 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
385
398
  /**
386
399
  * Send a STOP request to the target peer that the dialing peer wants to contact
387
400
  */
388
- async stopHop ({
389
- connection,
390
- request
391
- }: StopOptions): Promise<Stream | undefined> {
401
+ async stopHop ({ connection, request }: StopOptions, options: AbortOptions): Promise<Stream | undefined> {
392
402
  this.log('starting circuit relay v2 stop request to %s', connection.remotePeer)
393
403
  const stream = await connection.newStream([RELAY_V2_STOP_CODEC], {
394
404
  maxOutboundStreams: this.maxOutboundStopStreams,
395
- runOnLimitedConnection: true
405
+ runOnLimitedConnection: true,
406
+ ...options
396
407
  })
397
408
  const pbstr = pbStream(stream)
398
409
  const stopstr = pbstr.pb(StopMessage)
399
- await stopstr.write(request)
410
+ await stopstr.write(request, options)
400
411
  let response
401
412
 
402
413
  try {
403
- response = await stopstr.read()
414
+ response = await stopstr.read(options)
404
415
  } catch (err) {
405
416
  this.log.error('error parsing stop message response from %p', connection.remotePeer)
406
417
  }
407
418
 
408
419
  if (response == null) {
409
420
  this.log.error('could not read response from %p', connection.remotePeer)
410
- await stream.close()
421
+ await stream.close(options)
411
422
  return
412
423
  }
413
424
 
@@ -417,7 +428,7 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
417
428
  }
418
429
 
419
430
  this.log('stop request failed with code %d', response.status)
420
- await stream.close()
431
+ await stream.close(options)
421
432
  }
422
433
 
423
434
  get reservations (): PeerMap<RelayReservation> {
@@ -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
@@ -66,7 +66,7 @@ export class RelayDiscovery extends TypedEventEmitter<RelayDiscoveryEvents> impl
66
66
  this.topologyId = await this.registrar.register(RELAY_V2_HOP_CODEC, {
67
67
  filter: this.filter,
68
68
  onConnect: (peerId) => {
69
- this.log('discovered relay %p', peerId)
69
+ this.log.trace('discovered relay %p', peerId)
70
70
  this.safeDispatchEvent('relay:discover', { detail: peerId })
71
71
  }
72
72
  })
@@ -1,6 +1,6 @@
1
1
  import { CircuitRelayTransport } from './transport.js'
2
2
  import type { RelayDiscoveryComponents } from './discovery.js'
3
- import type { RelayStoreInit } from './reservation-store.js'
3
+ import type { ReservationStoreInit } from './reservation-store.js'
4
4
  import type { Transport, Upgrader, Libp2pEvents, ConnectionGater, TypedEventTarget, PeerId, TopologyFilter } from '@libp2p/interface'
5
5
  import type { AddressManager, Registrar } from '@libp2p/interface-internal'
6
6
 
@@ -16,7 +16,7 @@ export interface CircuitRelayTransportComponents extends RelayDiscoveryComponent
16
16
  /**
17
17
  * RelayConfig configures the circuit v2 relay transport.
18
18
  */
19
- export interface CircuitRelayTransportInit extends RelayStoreInit {
19
+ export interface CircuitRelayTransportInit extends ReservationStoreInit {
20
20
  /**
21
21
  * The number of peers running diable relays to search for and connect to
22
22
  *
@@ -14,7 +14,7 @@ export interface CircuitRelayTransportListenerComponents {
14
14
 
15
15
  class CircuitRelayTransportListener extends TypedEventEmitter<ListenerEvents> implements Listener {
16
16
  private readonly connectionManager: ConnectionManager
17
- private readonly relayStore: ReservationStore
17
+ private readonly reservationStore: ReservationStore
18
18
  private readonly listeningAddrs: PeerMap<Multiaddr[]>
19
19
  private readonly log: Logger
20
20
 
@@ -23,15 +23,26 @@ class CircuitRelayTransportListener extends TypedEventEmitter<ListenerEvents> im
23
23
 
24
24
  this.log = components.logger.forComponent('libp2p:circuit-relay:transport:listener')
25
25
  this.connectionManager = components.connectionManager
26
- this.relayStore = components.relayStore
26
+ this.reservationStore = components.relayStore
27
27
  this.listeningAddrs = new PeerMap()
28
28
 
29
29
  // remove listening addrs when a relay is removed
30
- this.relayStore.addEventListener('relay:removed', this._onRemoveRelayPeer)
30
+ this.reservationStore.addEventListener('relay:removed', this._onRemoveRelayPeer)
31
31
  }
32
32
 
33
33
  _onRemoveRelayPeer = (evt: CustomEvent<PeerId>): void => {
34
- this.#removeRelayPeer(evt.detail)
34
+ const had = this.listeningAddrs.has(evt.detail)
35
+
36
+ this.log('relay peer removed %p - had reservation', evt.detail, had)
37
+
38
+ if (!had) {
39
+ return
40
+ }
41
+
42
+ this.listeningAddrs.delete(evt.detail)
43
+
44
+ // announce listen addresses change
45
+ this.safeDispatchEvent('listening')
35
46
  }
36
47
 
37
48
  async listen (addr: Multiaddr): Promise<void> {
@@ -41,14 +52,14 @@ class CircuitRelayTransportListener extends TypedEventEmitter<ListenerEvents> im
41
52
  const relayAddr = addr.decapsulate('/p2p-circuit')
42
53
  const relayConn = await this.connectionManager.openConnection(relayAddr)
43
54
 
44
- if (!this.relayStore.hasReservation(relayConn.remotePeer)) {
55
+ if (!this.reservationStore.hasReservation(relayConn.remotePeer)) {
45
56
  this.log('making reservation on peer %p', relayConn.remotePeer)
46
57
  // addRelay calls transportManager.listen which calls this listen method
47
- await this.relayStore.addRelay(relayConn.remotePeer, 'configured')
58
+ await this.reservationStore.addRelay(relayConn.remotePeer, 'configured')
48
59
  return
49
60
  }
50
61
 
51
- const reservation = this.relayStore.getReservation(relayConn.remotePeer)
62
+ const reservation = this.reservationStore.getReservation(relayConn.remotePeer)
52
63
 
53
64
  if (reservation == null) {
54
65
  throw new ListenError('Did not have reservation after making reservation')
@@ -60,11 +71,11 @@ class CircuitRelayTransportListener extends TypedEventEmitter<ListenerEvents> im
60
71
  }
61
72
 
62
73
  // add all addresses from the relay reservation
63
- this.listeningAddrs.set(relayConn.remotePeer, reservation.addrs.map(buf => {
64
- return multiaddr(buf).encapsulate('/p2p-circuit')
65
- }))
74
+ this.listeningAddrs.set(relayConn.remotePeer, reservation.addrs
75
+ .map(buf => multiaddr(buf).encapsulate('/p2p-circuit'))
76
+ )
66
77
 
67
- this.safeDispatchEvent('listening', {})
78
+ this.safeDispatchEvent('listening')
68
79
  }
69
80
 
70
81
  getAddrs (): Multiaddr[] {
@@ -72,22 +83,14 @@ class CircuitRelayTransportListener extends TypedEventEmitter<ListenerEvents> im
72
83
  }
73
84
 
74
85
  async close (): Promise<void> {
86
+ await this.reservationStore.cancelReservations()
87
+ this.listeningAddrs.clear()
75
88
 
76
- }
77
-
78
- #removeRelayPeer (peerId: PeerId): void {
79
- const had = this.listeningAddrs.has(peerId)
80
-
81
- this.log('relay peer removed %p - had reservation', peerId, had)
89
+ // remove listener
90
+ this.reservationStore.removeEventListener('relay:removed', this._onRemoveRelayPeer)
82
91
 
83
- this.listeningAddrs.delete(peerId)
84
-
85
- if (had) {
86
- this.log.trace('removing relay event listener for peer %p', peerId)
87
- this.relayStore.removeEventListener('relay:removed', this._onRemoveRelayPeer)
88
- // Announce listen addresses change
89
- this.safeDispatchEvent('close', {})
90
- }
92
+ // announce listen addresses change
93
+ this.safeDispatchEvent('close')
91
94
  }
92
95
  }
93
96