@libp2p/circuit-relay-v2 2.1.2 → 2.1.3-0d326d102

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.
@@ -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
 
@@ -4,12 +4,11 @@ import { createBloomFilter } from '@libp2p/utils/filters'
4
4
  import { PeerQueue } from '@libp2p/utils/peer-queue'
5
5
  import { multiaddr } from '@multiformats/multiaddr'
6
6
  import { pbStream } from 'it-protobuf-stream'
7
- import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
8
- import { DEFAULT_MAX_RESERVATION_QUEUE_LENGTH, DEFAULT_RESERVATION_COMPLETION_TIMEOUT, DEFAULT_RESERVATION_CONCURRENCY, RELAY_TAG, RELAY_V2_HOP_CODEC } from '../constants.js'
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'
9
8
  import { HopMessage, Status } from '../pb/index.js'
10
9
  import { getExpirationMilliseconds } from '../utils.js'
11
10
  import type { Reservation } from '../pb/index.js'
12
- import type { TypedEventTarget, Libp2pEvents, AbortOptions, ComponentLogger, Logger, Connection, PeerId, PeerStore, Startable, Metrics } from '@libp2p/interface'
11
+ import type { TypedEventTarget, Libp2pEvents, AbortOptions, ComponentLogger, Logger, Connection, PeerId, PeerStore, Startable, Metrics, Peer } from '@libp2p/interface'
13
12
  import type { ConnectionManager, TransportManager } from '@libp2p/interface-internal'
14
13
  import type { Filter } from '@libp2p/utils/filters'
15
14
 
@@ -22,7 +21,7 @@ const REFRESH_TIMEOUT = (60 * 1000) * 5
22
21
  // minimum duration before which a reservation must not be refreshed
23
22
  const REFRESH_TIMEOUT_MIN = 30 * 1000
24
23
 
25
- export interface RelayStoreComponents {
24
+ export interface ReservationStoreComponents {
26
25
  peerId: PeerId
27
26
  connectionManager: ConnectionManager
28
27
  transportManager: TransportManager
@@ -32,7 +31,7 @@ export interface RelayStoreComponents {
32
31
  metrics?: Metrics
33
32
  }
34
33
 
35
- export interface RelayStoreInit {
34
+ export interface ReservationStoreInit {
36
35
  /**
37
36
  * Multiple relays may be discovered simultaneously - to prevent listening
38
37
  * on too many relays, this value controls how many to attempt to reserve a
@@ -71,6 +70,11 @@ interface RelayEntry {
71
70
  timeout: ReturnType<typeof setTimeout>
72
71
  type: RelayType
73
72
  reservation: Reservation
73
+
74
+ /**
75
+ * Stores the id of the connection we have to the relay
76
+ */
77
+ connection: string
74
78
  }
75
79
 
76
80
  export interface ReservationStoreEvents {
@@ -94,7 +98,7 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
94
98
  private readonly log: Logger
95
99
  private readonly relayFilter: Filter
96
100
 
97
- constructor (components: RelayStoreComponents, init?: RelayStoreInit) {
101
+ constructor (components: ReservationStoreComponents, init?: ReservationStoreInit) {
98
102
  super()
99
103
 
100
104
  this.log = components.logger.forComponent('libp2p:circuit-relay:transport:reservation-store')
@@ -117,11 +121,21 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
117
121
  metrics: components.metrics
118
122
  })
119
123
 
120
- // When a peer disconnects, if we had a reservation on that peer
121
- // remove the reservation and multiaddr and maybe trigger search
122
- // for new relays
123
- this.events.addEventListener('peer:disconnect', (evt) => {
124
- this.#removeRelay(evt.detail)
124
+ // reservations are only valid while we are still connected to the relay.
125
+ // if we had a reservation opened via that connection, remove it and maybe
126
+ // trigger a search for new relays
127
+ this.events.addEventListener('connection:close', (evt) => {
128
+ const reservation = [...this.reservations.values()]
129
+ .find(reservation => reservation.connection === evt.detail.id)
130
+
131
+ if (reservation == null) {
132
+ return
133
+ }
134
+
135
+ this.#removeReservation(evt.detail.remotePeer, reservation)
136
+ .catch(err => {
137
+ this.log('could not remove relay %p - %e', evt.detail, err)
138
+ })
125
139
  })
126
140
  }
127
141
 
@@ -134,10 +148,37 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
134
148
  }
135
149
 
136
150
  afterStart (): void {
137
- if (this.reservations.size < this.maxDiscoveredRelays) {
138
- this.log('not enough relays %d/%d', this.reservations.size, this.maxDiscoveredRelays)
139
- this.safeDispatchEvent('relay:not-enough-relays', {})
140
- }
151
+ // remove old relay tags
152
+ void Promise.resolve()
153
+ .then(async () => {
154
+ const relayPeers: Peer[] = await this.peerStore.all({
155
+ filters: [(peer) => {
156
+ return peer.tags.has(RELAY_TAG)
157
+ }]
158
+ })
159
+
160
+ this.log('removing tag from %d old relays', relayPeers.length)
161
+
162
+ // remove old relay tag and redial
163
+ await Promise.all(
164
+ relayPeers.map(async peer => {
165
+ await this.peerStore.merge(peer.id, {
166
+ tags: {
167
+ [RELAY_TAG]: undefined,
168
+ [KEEP_ALIVE_TAG]: undefined
169
+ }
170
+ })
171
+ })
172
+ )
173
+
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
+ }
178
+ })
179
+ .catch(err => {
180
+ this.log.error(err)
181
+ })
141
182
  }
142
183
 
143
184
  stop (): void {
@@ -157,26 +198,26 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
157
198
  */
158
199
  async addRelay (peerId: PeerId, type: RelayType): Promise<void> {
159
200
  if (this.peerId.equals(peerId)) {
160
- this.log('not trying to use self as relay')
201
+ this.log.trace('not trying to use self as relay')
161
202
  return
162
203
  }
163
204
 
164
205
  if (this.reserveQueue.size > this.maxReservationQueueLength) {
165
- this.log('not adding potential relay peer %p as the queue is full', peerId)
206
+ this.log.trace('not adding potential relay peer %p as the queue is full', peerId)
166
207
  return
167
208
  }
168
209
 
169
210
  if (this.reserveQueue.has(peerId)) {
170
- this.log('potential relay peer %p is already in the reservation queue', peerId)
211
+ this.log.trace('potential relay peer %p is already in the reservation queue', peerId)
171
212
  return
172
213
  }
173
214
 
174
215
  if (this.relayFilter.has(peerId.toMultihash().bytes)) {
175
- this.log('potential relay peer %p has failed previously, not trying again', peerId)
216
+ this.log.trace('potential relay peer %p has failed previously, not trying again', peerId)
176
217
  return
177
218
  }
178
219
 
179
- this.log('try to reserve relay slot with %p', peerId)
220
+ this.log.trace('try to reserve relay slot with %p', peerId)
180
221
 
181
222
  await this.reserveQueue.add(async () => {
182
223
  const start = Date.now()
@@ -186,13 +227,13 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
186
227
  const existingReservation = this.reservations.get(peerId)
187
228
 
188
229
  if (existingReservation != null) {
189
- if (getExpirationMilliseconds(existingReservation.reservation.expire) > REFRESH_WINDOW) {
190
- this.log('already have reservation on relay peer %p and it expires in more than 10 minutes', peerId)
230
+ if (this.connectionManager.getConnections(peerId).map(conn => conn.id).includes(existingReservation.connection) && getExpirationMilliseconds(existingReservation.reservation.expire) > REFRESH_WINDOW) {
231
+ this.log('already have relay reservation with %p but we are still connected and it does not expire soon', peerId)
191
232
  return
192
233
  }
193
234
 
194
- clearTimeout(existingReservation.timeout)
195
- this.reservations.delete(peerId)
235
+ this.log('already have relay reservation with %p but the original connection is no longer open', peerId)
236
+ await this.#removeReservation(peerId, existingReservation)
196
237
  }
197
238
 
198
239
  if (type === 'discovered' && [...this.reservations.values()].reduce((acc, curr) => {
@@ -202,7 +243,7 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
202
243
 
203
244
  return acc
204
245
  }, 0) >= this.maxDiscoveredRelays) {
205
- this.log('already have enough discovered relays')
246
+ this.log.trace('already have enough discovered relays')
206
247
  return
207
248
  }
208
249
 
@@ -240,7 +281,8 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
240
281
  this.reservations.set(peerId, {
241
282
  timeout,
242
283
  reservation,
243
- type
284
+ type,
285
+ connection: connection.id
244
286
  })
245
287
 
246
288
  // ensure we don't close the connection to the relay
@@ -249,6 +291,10 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
249
291
  [RELAY_TAG]: {
250
292
  value: 1,
251
293
  ttl: expiration
294
+ },
295
+ [KEEP_ALIVE_TAG]: {
296
+ value: 1,
297
+ ttl: expiration
252
298
  }
253
299
  }
254
300
  })
@@ -292,6 +338,14 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
292
338
  return this.reservations.size
293
339
  }
294
340
 
341
+ async cancelReservations (): Promise<void> {
342
+ await Promise.all(
343
+ [...this.reservations.entries()].map(async ([peerId, reservation]) => {
344
+ await this.#removeReservation(peerId, reservation)
345
+ })
346
+ )
347
+ }
348
+
295
349
  async #createReservation (connection: Connection, options: AbortOptions): Promise<Reservation> {
296
350
  options.signal?.throwIfAborted()
297
351
 
@@ -314,24 +368,31 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
314
368
  }
315
369
  }
316
370
 
317
- if (response.status === Status.OK && (response.reservation != null)) {
371
+ if (response.status === Status.OK && response.reservation != null) {
318
372
  // check that the returned relay has the relay address - this can be
319
373
  // omitted when requesting a reservation from a go-libp2p relay we
320
374
  // already have a reservation on
321
- let hasRelayAddress = false
322
- const relayAddressBytes = connection.remoteAddr.bytes
375
+ const addresses = new Set<string>()
376
+ addresses.add(connection.remoteAddr.toString())
323
377
 
324
378
  for (const buf of response.reservation.addrs) {
325
- if (uint8ArrayEquals(relayAddressBytes, buf)) {
326
- hasRelayAddress = true
327
- break
379
+ let ma = multiaddr(buf)
380
+
381
+ if (ma.getPeerId() == null) {
382
+ ma = ma.encapsulate(`/p2p/${connection.remotePeer}`)
328
383
  }
329
- }
330
384
 
331
- if (!hasRelayAddress) {
332
- response.reservation.addrs.push(relayAddressBytes)
385
+ // TODO: workaround for https://github.com/libp2p/go-libp2p/issues/3003
386
+ ma = multiaddr(ma.toString().replace(
387
+ `/p2p/${connection.remotePeer}/p2p/${connection.remotePeer}`,
388
+ `/p2p/${connection.remotePeer}`
389
+ ))
390
+
391
+ addresses.add(ma.toString())
333
392
  }
334
393
 
394
+ response.reservation.addrs = [...addresses].map(str => multiaddr(str).bytes)
395
+
335
396
  return response.reservation
336
397
  }
337
398
 
@@ -344,18 +405,19 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
344
405
  /**
345
406
  * Remove listen relay
346
407
  */
347
- #removeRelay (peerId: PeerId): void {
348
- const existingReservation = this.reservations.get(peerId)
349
-
350
- if (existingReservation == null) {
351
- return
352
- }
353
-
354
- this.log('connection to relay %p closed, removing reservation from local store', peerId)
355
-
356
- clearTimeout(existingReservation.timeout)
408
+ async #removeReservation (peerId: PeerId, reservation: RelayEntry): Promise<void> {
409
+ this.log('removing relay reservation with %p from local store', peerId)
410
+ clearTimeout(reservation.timeout)
357
411
  this.reservations.delete(peerId)
358
412
 
413
+ // untag the relay
414
+ await this.peerStore.merge(peerId, {
415
+ tags: {
416
+ [RELAY_TAG]: undefined,
417
+ [KEEP_ALIVE_TAG]: undefined
418
+ }
419
+ })
420
+
359
421
  this.safeDispatchEvent('relay:removed', { detail: peerId })
360
422
 
361
423
  if (this.reservations.size < this.maxDiscoveredRelays) {
@@ -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
- "RelayStoreInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_circuit_relay_v2.RelayStoreInit.html",
18
- "ReservationStoreInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_circuit_relay_v2.ReservationStoreInit.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
- }