@libp2p/circuit-relay-v2 3.2.24-6059227cb → 3.2.24-87bc8d4fb

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 (51) hide show
  1. package/dist/index.min.js +1 -1
  2. package/dist/index.min.js.map +4 -4
  3. package/dist/src/constants.d.ts +6 -1
  4. package/dist/src/constants.d.ts.map +1 -1
  5. package/dist/src/constants.js +6 -1
  6. package/dist/src/constants.js.map +1 -1
  7. package/dist/src/index.d.ts +7 -162
  8. package/dist/src/index.d.ts.map +1 -1
  9. package/dist/src/index.js +2 -12
  10. package/dist/src/index.js.map +1 -1
  11. package/dist/src/server/index.d.ts +45 -41
  12. package/dist/src/server/index.d.ts.map +1 -1
  13. package/dist/src/server/index.js +51 -32
  14. package/dist/src/server/index.js.map +1 -1
  15. package/dist/src/server/reservation-store.d.ts +36 -2
  16. package/dist/src/server/reservation-store.d.ts.map +1 -1
  17. package/dist/src/server/reservation-store.js.map +1 -1
  18. package/dist/src/transport/discovery.d.ts +17 -2
  19. package/dist/src/transport/discovery.d.ts.map +1 -1
  20. package/dist/src/transport/discovery.js +2 -2
  21. package/dist/src/transport/discovery.js.map +1 -1
  22. package/dist/src/transport/index.d.ts +42 -34
  23. package/dist/src/transport/index.d.ts.map +1 -1
  24. package/dist/src/transport/index.js +5 -291
  25. package/dist/src/transport/index.js.map +1 -1
  26. package/dist/src/transport/reservation-store.d.ts +37 -3
  27. package/dist/src/transport/reservation-store.d.ts.map +1 -1
  28. package/dist/src/transport/reservation-store.js +6 -4
  29. package/dist/src/transport/reservation-store.js.map +1 -1
  30. package/dist/src/transport/transport.d.ts +54 -0
  31. package/dist/src/transport/transport.d.ts.map +1 -0
  32. package/dist/src/transport/transport.js +314 -0
  33. package/dist/src/transport/transport.js.map +1 -0
  34. package/dist/src/utils.d.ts.map +1 -1
  35. package/dist/src/utils.js +59 -36
  36. package/dist/src/utils.js.map +1 -1
  37. package/package.json +24 -19
  38. package/src/constants.ts +7 -1
  39. package/src/index.ts +8 -198
  40. package/src/server/index.ts +105 -37
  41. package/src/server/reservation-store.ts +42 -2
  42. package/src/transport/discovery.ts +22 -4
  43. package/src/transport/index.ts +46 -338
  44. package/src/transport/reservation-store.ts +46 -8
  45. package/src/transport/transport.ts +380 -0
  46. package/src/utils.ts +66 -37
  47. package/dist/src/transport/stream-to-conn.d.ts +0 -19
  48. package/dist/src/transport/stream-to-conn.d.ts.map +0 -1
  49. package/dist/src/transport/stream-to-conn.js +0 -60
  50. package/dist/src/transport/stream-to-conn.js.map +0 -1
  51. package/src/transport/stream-to-conn.ts +0 -91
@@ -1,13 +1,14 @@
1
1
  import { publicKeyToProtobuf } from '@libp2p/crypto/keys'
2
2
  import { peerIdFromMultihash } from '@libp2p/peer-id'
3
3
  import { RecordEnvelope } from '@libp2p/peer-record'
4
- import { pbStream } from '@libp2p/utils'
5
4
  import { multiaddr } from '@multiformats/multiaddr'
6
- import { Circuit } from '@multiformats/multiaddr-matcher'
5
+ import { pbStream } from 'it-protobuf-stream'
7
6
  import { TypedEventEmitter, setMaxListeners } from 'main-event'
8
7
  import * as Digest from 'multiformats/hashes/digest'
9
8
  import {
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,
@@ -17,11 +18,49 @@ import { HopMessage, Status, StopMessage } from '../pb/index.js'
17
18
  import { createLimitedRelay } from '../utils.js'
18
19
  import { ReservationStore } from './reservation-store.js'
19
20
  import { ReservationVoucherRecord } from './reservation-voucher.js'
20
- import type { CircuitRelayServerComponents, CircuitRelayServerInit, CircuitRelayService, RelayReservation } from '../index.js'
21
+ import type { ReservationStoreInit } from './reservation-store.js'
22
+ import type { CircuitRelayService, RelayReservation } from '../index.js'
21
23
  import type { Reservation } from '../pb/index.js'
22
- import type { Logger, Connection, Stream, PeerId, Startable, AbortOptions } from '@libp2p/interface'
24
+ import type { ComponentLogger, Logger, Connection, Stream, ConnectionGater, PeerId, PeerStore, Startable, PrivateKey, Metrics, AbortOptions, IncomingStreamData } from '@libp2p/interface'
25
+ import type { AddressManager, ConnectionManager, Registrar } from '@libp2p/interface-internal'
23
26
  import type { PeerMap } from '@libp2p/peer-collections'
24
- import type { ProtobufStream } from '@libp2p/utils'
27
+ import type { Multiaddr } from '@multiformats/multiaddr'
28
+ import type { ProtobufStream } from 'it-protobuf-stream'
29
+
30
+ const isRelayAddr = (ma: Multiaddr): boolean => ma.protoCodes().includes(CIRCUIT_PROTO_CODE)
31
+
32
+ export interface CircuitRelayServerInit {
33
+ /**
34
+ * Incoming hop requests must complete within this time in ms otherwise
35
+ * the stream will be reset
36
+ *
37
+ * @default 30000
38
+ */
39
+ hopTimeout?: number
40
+
41
+ /**
42
+ * Configuration of reservations
43
+ */
44
+ reservations?: ReservationStoreInit
45
+
46
+ /**
47
+ * The maximum number of simultaneous HOP inbound streams that can be open at once
48
+ */
49
+ maxInboundHopStreams?: number
50
+
51
+ /**
52
+ * The maximum number of simultaneous HOP outbound streams that can be open at once
53
+ */
54
+ maxOutboundHopStreams?: number
55
+
56
+ /**
57
+ * The maximum number of simultaneous STOP outbound streams that can be open at
58
+ * once.
59
+ *
60
+ * @default 300
61
+ */
62
+ maxOutboundStopStreams?: number
63
+ }
25
64
 
26
65
  export interface HopProtocolOptions {
27
66
  connection: Connection
@@ -34,6 +73,18 @@ export interface StopOptions {
34
73
  request: StopMessage
35
74
  }
36
75
 
76
+ export interface CircuitRelayServerComponents {
77
+ registrar: Registrar
78
+ peerStore: PeerStore
79
+ addressManager: AddressManager
80
+ peerId: PeerId
81
+ privateKey: PrivateKey
82
+ connectionManager: ConnectionManager
83
+ connectionGater: ConnectionGater
84
+ logger: ComponentLogger
85
+ metrics?: Metrics
86
+ }
87
+
37
88
  export interface RelayServerEvents {
38
89
  'relay:reservation': CustomEvent<RelayReservation>
39
90
  'relay:advert:success': CustomEvent<unknown>
@@ -44,8 +95,14 @@ const defaults = {
44
95
  maxOutboundStopStreams: MAX_CONNECTIONS
45
96
  }
46
97
 
47
- export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements Startable, CircuitRelayService {
48
- private readonly components: CircuitRelayServerComponents
98
+ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements Startable, CircuitRelayService {
99
+ private readonly registrar: Registrar
100
+ private readonly peerStore: PeerStore
101
+ private readonly addressManager: AddressManager
102
+ private readonly peerId: PeerId
103
+ private readonly privateKey: PrivateKey
104
+ private readonly connectionManager: ConnectionManager
105
+ private readonly connectionGater: ConnectionGater
49
106
  private readonly reservationStore: ReservationStore
50
107
  private started: boolean
51
108
  private readonly hopTimeout: number
@@ -62,7 +119,13 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
62
119
  super()
63
120
 
64
121
  this.log = components.logger.forComponent('libp2p:circuit-relay:server')
65
- this.components = components
122
+ this.registrar = components.registrar
123
+ this.peerStore = components.peerStore
124
+ this.addressManager = components.addressManager
125
+ this.peerId = components.peerId
126
+ this.privateKey = components.privateKey
127
+ this.connectionManager = components.connectionManager
128
+ this.connectionGater = components.connectionGater
66
129
  this.started = false
67
130
  this.hopTimeout = init?.hopTimeout ?? DEFAULT_HOP_TIMEOUT
68
131
  this.maxInboundHopStreams = init.maxInboundHopStreams
@@ -72,8 +135,6 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
72
135
 
73
136
  this.shutdownController = new AbortController()
74
137
  setMaxListeners(Infinity, this.shutdownController.signal)
75
-
76
- this.onHop = this.onHop.bind(this)
77
138
  }
78
139
 
79
140
  readonly [Symbol.toStringTag] = '@libp2p/circuit-relay-v2-server'
@@ -90,7 +151,11 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
90
151
  return
91
152
  }
92
153
 
93
- await this.components.registrar.handle(RELAY_V2_HOP_CODEC, this.onHop, {
154
+ await this.registrar.handle(RELAY_V2_HOP_CODEC, (data) => {
155
+ void this.onHop(data).catch(err => {
156
+ this.log.error(err)
157
+ })
158
+ }, {
94
159
  maxInboundStreams: this.maxInboundHopStreams,
95
160
  maxOutboundStreams: this.maxOutboundHopStreams,
96
161
  runOnLimitedConnection: true
@@ -105,19 +170,16 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
105
170
  async stop (): Promise<void> {
106
171
  this.reservationStore.clear()
107
172
  this.shutdownController.abort()
108
- await this.components.registrar.unhandle(RELAY_V2_HOP_CODEC)
173
+ await this.registrar.unhandle(RELAY_V2_HOP_CODEC)
109
174
 
110
175
  this.started = false
111
176
  }
112
177
 
113
- async onHop (stream: Stream, connection: Connection): Promise<void> {
178
+ async onHop ({ connection, stream }: IncomingStreamData): Promise<void> {
114
179
  this.log('received circuit v2 hop protocol stream from %p', connection.remotePeer)
115
180
 
116
- const signal = AbortSignal.timeout(this.hopTimeout)
117
- setMaxListeners(Infinity, signal)
118
-
119
181
  const options = {
120
- signal
182
+ signal: AbortSignal.timeout(this.hopTimeout)
121
183
  }
122
184
  const pbstr = pbStream(stream)
123
185
 
@@ -161,13 +223,13 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
161
223
  const hopstr = stream.pb(HopMessage)
162
224
  this.log('hop reserve request from %p', connection.remotePeer)
163
225
 
164
- if (Circuit.exactMatch(connection.remoteAddr)) {
226
+ if (isRelayAddr(connection.remoteAddr)) {
165
227
  this.log.error('relay reservation over circuit connection denied for peer: %p', connection.remotePeer)
166
228
  await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED }, options)
167
229
  return
168
230
  }
169
231
 
170
- if ((await this.components.connectionGater.denyInboundRelayReservation?.(connection.remotePeer)) === true) {
232
+ if ((await this.connectionGater.denyInboundRelayReservation?.(connection.remotePeer)) === true) {
171
233
  this.log.error('reservation for %p denied by connection gater', connection.remotePeer)
172
234
  await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED }, options)
173
235
  return
@@ -185,11 +247,12 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
185
247
  // result.expire is non-null if `ReservationStore.reserve` returns with status == OK
186
248
  if (result.expire != null) {
187
249
  const ttl = (result.expire * 1000) - Date.now()
188
- await this.components.peerStore.merge(connection.remotePeer, {
250
+ await this.peerStore.merge(connection.remotePeer, {
189
251
  tags: {
190
- [RELAY_SOURCE_TAG]: { value: 1, ttl }
252
+ [RELAY_SOURCE_TAG]: { value: 1, ttl },
253
+ [KEEP_ALIVE_SOURCE_TAG]: { value: 1, ttl }
191
254
  }
192
- }, options)
255
+ })
193
256
  }
194
257
 
195
258
  await hopstr.write({
@@ -199,19 +262,17 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
199
262
  limit: this.reservationStore.get(connection.remotePeer)?.limit
200
263
  }, options)
201
264
  this.log('sent confirmation response to %s', connection.remotePeer)
202
-
203
- // close writable end of stream
204
- await hopstr.unwrap().unwrap().close(options)
205
265
  } catch (err) {
206
266
  this.log.error('failed to send confirmation response to %p - %e', connection.remotePeer, err)
207
267
  this.reservationStore.removeReservation(connection.remotePeer)
208
268
 
209
269
  try {
210
- await this.components.peerStore.merge(connection.remotePeer, {
270
+ await this.peerStore.merge(connection.remotePeer, {
211
271
  tags: {
212
- [RELAY_SOURCE_TAG]: undefined
272
+ [RELAY_SOURCE_TAG]: undefined,
273
+ [KEEP_ALIVE_SOURCE_TAG]: undefined
213
274
  }
214
- }, options)
275
+ })
215
276
  } catch (err) {
216
277
  this.log.error('failed to untag relay source peer %p - %e', connection.remotePeer, err)
217
278
  }
@@ -224,7 +285,7 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
224
285
  ): Promise<Reservation> {
225
286
  const addrs = []
226
287
 
227
- for (const relayAddr of this.components.addressManager.getAddresses()) {
288
+ for (const relayAddr of this.addressManager.getAddresses()) {
228
289
  if (relayAddr.toString().includes('/p2p-circuit')) {
229
290
  continue
230
291
  }
@@ -234,9 +295,9 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
234
295
 
235
296
  const envelope = await RecordEnvelope.seal(new ReservationVoucherRecord({
236
297
  peer: remotePeer,
237
- relay: this.components.peerId,
298
+ relay: this.peerId,
238
299
  expiration: expire
239
- }), this.components.privateKey)
300
+ }), this.privateKey)
240
301
 
241
302
  return {
242
303
  addrs,
@@ -246,7 +307,7 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
246
307
  payloadType: envelope.payloadType,
247
308
  payload: {
248
309
  peer: remotePeer.toMultihash().bytes,
249
- relay: this.components.peerId.toMultihash().bytes,
310
+ relay: this.peerId.toMultihash().bytes,
250
311
  expiration: expire
251
312
  },
252
313
  signature: envelope.signature
@@ -257,7 +318,7 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
257
318
  async handleConnect ({ stream, request, connection }: HopProtocolOptions, options: AbortOptions): Promise<void> {
258
319
  const hopstr = stream.pb(HopMessage)
259
320
 
260
- if (Circuit.matches(connection.remoteAddr)) {
321
+ if (isRelayAddr(connection.remoteAddr)) {
261
322
  this.log.error('relay reservation over circuit connection denied for peer: %p', connection.remotePeer)
262
323
  await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED }, options)
263
324
  return
@@ -289,13 +350,13 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
289
350
  return
290
351
  }
291
352
 
292
- if ((await this.components.connectionGater.denyOutboundRelayedConnection?.(connection.remotePeer, dstPeer)) === true) {
353
+ if ((await this.connectionGater.denyOutboundRelayedConnection?.(connection.remotePeer, dstPeer)) === true) {
293
354
  this.log.error('hop connect for %p to %p denied by connection gater', connection.remotePeer, dstPeer)
294
355
  await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED }, options)
295
356
  return
296
357
  }
297
358
 
298
- const connections = this.components.connectionManager.getConnections(dstPeer)
359
+ const connections = this.connectionManager.getConnections(dstPeer)
299
360
 
300
361
  if (connections.length === 0) {
301
362
  this.log('hop connect denied for destination peer %p not having a connection for %p as there is no destination connection', dstPeer, connection.remotePeer)
@@ -328,11 +389,12 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
328
389
  status: Status.OK,
329
390
  limit: reservation?.limit
330
391
  }, options)
392
+ const sourceStream = stream.unwrap()
331
393
 
332
394
  this.log('connection from %p to %p established - merging streams', connection.remotePeer, dstPeer)
333
395
 
334
396
  // Short circuit the two streams to create the relayed connection
335
- createLimitedRelay(stream.unwrap(), destinationStream, this.shutdownController.signal, reservation, {
397
+ createLimitedRelay(sourceStream, destinationStream, this.shutdownController.signal, reservation, {
336
398
  log: this.log
337
399
  })
338
400
  }
@@ -342,7 +404,7 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
342
404
  */
343
405
  async stopHop ({ connection, request }: StopOptions, options: AbortOptions): Promise<Stream | undefined> {
344
406
  this.log('starting circuit relay v2 stop request to %s', connection.remotePeer)
345
- const stream = await connection.newStream(RELAY_V2_STOP_CODEC, {
407
+ const stream = await connection.newStream([RELAY_V2_STOP_CODEC], {
346
408
  maxOutboundStreams: this.maxOutboundStopStreams,
347
409
  runOnLimitedConnection: true,
348
410
  ...options
@@ -377,3 +439,9 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
377
439
  return this.reservationStore.reservations
378
440
  }
379
441
  }
442
+
443
+ export function circuitRelayServer (init: CircuitRelayServerInit = {}): (components: CircuitRelayServerComponents) => CircuitRelayService {
444
+ return (components) => {
445
+ return new CircuitRelayServer(components, init)
446
+ }
447
+ }
@@ -2,7 +2,7 @@ import { trackedPeerMap } from '@libp2p/peer-collections'
2
2
  import { retimeableSignal } from 'retimeable-signal'
3
3
  import { DEFAULT_DATA_LIMIT, DEFAULT_DURATION_LIMIT, DEFAULT_MAX_RESERVATION_STORE_SIZE, DEFAULT_MAX_RESERVATION_TTL } from '../constants.js'
4
4
  import { Status } from '../pb/index.js'
5
- import type { RelayReservation, ServerReservationStoreInit } from '../index.js'
5
+ import type { RelayReservation } from '../index.js'
6
6
  import type { Limit } from '../pb/index.js'
7
7
  import type { ComponentLogger, Logger, Metrics, PeerId } from '@libp2p/interface'
8
8
  import type { PeerMap } from '@libp2p/peer-collections'
@@ -15,6 +15,46 @@ export interface ReservationStoreComponents {
15
15
  metrics?: Metrics
16
16
  }
17
17
 
18
+ export interface ReservationStoreInit {
19
+ /**
20
+ * maximum number of reservations allowed
21
+ *
22
+ * @default 15
23
+ */
24
+ maxReservations?: number
25
+
26
+ /**
27
+ * interval after which stale reservations are cleared
28
+ *
29
+ * @default 300000
30
+ */
31
+ reservationClearInterval?: number
32
+
33
+ /**
34
+ * apply default relay limits to a new reservation
35
+ *
36
+ * @default true
37
+ */
38
+ applyDefaultLimit?: boolean
39
+
40
+ /**
41
+ * reservation ttl
42
+ *
43
+ * @default 7200000
44
+ */
45
+ reservationTtl?: number
46
+
47
+ /**
48
+ * The maximum time a relayed connection can be open for
49
+ */
50
+ defaultDurationLimit?: number
51
+
52
+ /**
53
+ * The maximum amount of data allowed to be transferred over a relayed connection
54
+ */
55
+ defaultDataLimit?: bigint
56
+ }
57
+
18
58
  export class ReservationStore {
19
59
  public readonly reservations: PeerMap<RelayReservation>
20
60
  private readonly maxReservations: number
@@ -24,7 +64,7 @@ export class ReservationStore {
24
64
  private readonly defaultDataLimit: bigint
25
65
  private readonly log: Logger
26
66
 
27
- constructor (components: ReservationStoreComponents, init: ServerReservationStoreInit = {}) {
67
+ constructor (components: ReservationStoreComponents, init: ReservationStoreInit = {}) {
28
68
  this.log = components.logger.forComponent('libp2p:circuit-relay:server:reservation-store')
29
69
  this.maxReservations = init.maxReservations ?? DEFAULT_MAX_RESERVATION_STORE_SIZE
30
70
  this.applyDefaultLimit = init.applyDefaultLimit !== false
@@ -1,12 +1,30 @@
1
- import { PeerQueue } from '@libp2p/utils'
1
+ import { PeerQueue } from '@libp2p/utils/peer-queue'
2
2
  import { anySignal } from 'any-signal'
3
3
  import { TypedEventEmitter, setMaxListeners } from 'main-event'
4
4
  import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
5
5
  import {
6
6
  RELAY_V2_HOP_CODEC
7
7
  } from '../constants.js'
8
- import type { RelayDiscoveryComponents, RelayDiscoveryEvents, RelayDiscoveryInit } from '../index.ts'
9
- import type { Logger, Peer, PeerId, PeerInfo, Startable, TopologyFilter } from '@libp2p/interface'
8
+ import type { ComponentLogger, Libp2pEvents, Logger, Peer, PeerId, PeerInfo, PeerStore, Startable, TopologyFilter, TypedEventTarget } from '@libp2p/interface'
9
+ import type { ConnectionManager, RandomWalk, Registrar, TransportManager } from '@libp2p/interface-internal'
10
+
11
+ export interface RelayDiscoveryEvents {
12
+ 'relay:discover': CustomEvent<PeerId>
13
+ }
14
+
15
+ export interface RelayDiscoveryComponents {
16
+ peerStore: PeerStore
17
+ connectionManager: ConnectionManager
18
+ transportManager: TransportManager
19
+ registrar: Registrar
20
+ logger: ComponentLogger
21
+ randomWalk: RandomWalk
22
+ events: TypedEventTarget<Libp2pEvents>
23
+ }
24
+
25
+ export interface RelayDiscoveryInit {
26
+ filter?: TopologyFilter
27
+ }
10
28
 
11
29
  /**
12
30
  * ReservationManager automatically makes a circuit v2 reservation on any connected
@@ -203,7 +221,7 @@ export class RelayDiscovery extends TypedEventEmitter<RelayDiscoveryEvents> impl
203
221
  }
204
222
 
205
223
  onPeer (evt: CustomEvent<PeerInfo>): void {
206
- this.log.trace('maybe dialing discovered peer %p', evt.detail.id)
224
+ this.log.trace('maybe dialing discovered peer %p - %e', evt.detail.id)
207
225
 
208
226
  this.maybeDialPeer(evt)
209
227
  .catch(err => {