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

Sign up to get free protection for your applications and to get access to all the features.
@@ -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) {