@libp2p/circuit-relay-v2 2.1.3-e99e8f448 → 2.1.4

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.
@@ -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
 
@@ -1,15 +1,14 @@
1
- import { KEEP_ALIVE, TypedEventEmitter, setMaxListeners } from '@libp2p/interface'
1
+ import { TypedEventEmitter, setMaxListeners } from '@libp2p/interface'
2
2
  import { PeerMap } from '@libp2p/peer-collections'
3
3
  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
@@ -250,7 +292,7 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
250
292
  value: 1,
251
293
  ttl: expiration
252
294
  },
253
- [KEEP_ALIVE]: {
295
+ [KEEP_ALIVE_TAG]: {
254
296
  value: 1,
255
297
  ttl: expiration
256
298
  }
@@ -296,6 +338,14 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
296
338
  return this.reservations.size
297
339
  }
298
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
+
299
349
  async #createReservation (connection: Connection, options: AbortOptions): Promise<Reservation> {
300
350
  options.signal?.throwIfAborted()
301
351
 
@@ -318,24 +368,31 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
318
368
  }
319
369
  }
320
370
 
321
- if (response.status === Status.OK && (response.reservation != null)) {
371
+ if (response.status === Status.OK && response.reservation != null) {
322
372
  // check that the returned relay has the relay address - this can be
323
373
  // omitted when requesting a reservation from a go-libp2p relay we
324
374
  // already have a reservation on
325
- let hasRelayAddress = false
326
- const relayAddressBytes = connection.remoteAddr.bytes
375
+ const addresses = new Set<string>()
376
+ addresses.add(connection.remoteAddr.toString())
327
377
 
328
378
  for (const buf of response.reservation.addrs) {
329
- if (uint8ArrayEquals(relayAddressBytes, buf)) {
330
- hasRelayAddress = true
331
- break
379
+ let ma = multiaddr(buf)
380
+
381
+ if (ma.getPeerId() == null) {
382
+ ma = ma.encapsulate(`/p2p/${connection.remotePeer}`)
332
383
  }
333
- }
334
384
 
335
- if (!hasRelayAddress) {
336
- 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())
337
392
  }
338
393
 
394
+ response.reservation.addrs = [...addresses].map(str => multiaddr(str).bytes)
395
+
339
396
  return response.reservation
340
397
  }
341
398
 
@@ -348,18 +405,19 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
348
405
  /**
349
406
  * Remove listen relay
350
407
  */
351
- #removeRelay (peerId: PeerId): void {
352
- const existingReservation = this.reservations.get(peerId)
353
-
354
- if (existingReservation == null) {
355
- return
356
- }
357
-
358
- this.log('connection to relay %p closed, removing reservation from local store', peerId)
359
-
360
- 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)
361
411
  this.reservations.delete(peerId)
362
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
+
363
421
  this.safeDispatchEvent('relay:removed', { detail: peerId })
364
422
 
365
423
  if (this.reservations.size < this.maxDiscoveredRelays) {