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

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 (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