@libp2p/circuit-relay-v2 3.2.24-8484de8a2 → 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 +4 -0
  4. package/dist/src/constants.d.ts.map +1 -1
  5. package/dist/src/constants.js +4 -0
  6. package/dist/src/constants.js.map +1 -1
  7. package/dist/src/index.d.ts +7 -170
  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 +47 -30
  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 +5 -0
  39. package/src/index.ts +8 -207
  40. package/src/server/index.ts +100 -35
  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,12 +1,12 @@
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
11
  KEEP_ALIVE_SOURCE_TAG,
12
12
  MAX_CONNECTIONS,
@@ -18,11 +18,49 @@ import { HopMessage, Status, StopMessage } from '../pb/index.js'
18
18
  import { createLimitedRelay } from '../utils.js'
19
19
  import { ReservationStore } from './reservation-store.js'
20
20
  import { ReservationVoucherRecord } from './reservation-voucher.js'
21
- 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'
22
23
  import type { Reservation } from '../pb/index.js'
23
- 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'
24
26
  import type { PeerMap } from '@libp2p/peer-collections'
25
- 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
+ }
26
64
 
27
65
  export interface HopProtocolOptions {
28
66
  connection: Connection
@@ -35,6 +73,18 @@ export interface StopOptions {
35
73
  request: StopMessage
36
74
  }
37
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
+
38
88
  export interface RelayServerEvents {
39
89
  'relay:reservation': CustomEvent<RelayReservation>
40
90
  'relay:advert:success': CustomEvent<unknown>
@@ -45,8 +95,14 @@ const defaults = {
45
95
  maxOutboundStopStreams: MAX_CONNECTIONS
46
96
  }
47
97
 
48
- export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements Startable, CircuitRelayService {
49
- 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
50
106
  private readonly reservationStore: ReservationStore
51
107
  private started: boolean
52
108
  private readonly hopTimeout: number
@@ -63,7 +119,13 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
63
119
  super()
64
120
 
65
121
  this.log = components.logger.forComponent('libp2p:circuit-relay:server')
66
- 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
67
129
  this.started = false
68
130
  this.hopTimeout = init?.hopTimeout ?? DEFAULT_HOP_TIMEOUT
69
131
  this.maxInboundHopStreams = init.maxInboundHopStreams
@@ -73,8 +135,6 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
73
135
 
74
136
  this.shutdownController = new AbortController()
75
137
  setMaxListeners(Infinity, this.shutdownController.signal)
76
-
77
- this.onHop = this.onHop.bind(this)
78
138
  }
79
139
 
80
140
  readonly [Symbol.toStringTag] = '@libp2p/circuit-relay-v2-server'
@@ -91,7 +151,11 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
91
151
  return
92
152
  }
93
153
 
94
- 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
+ }, {
95
159
  maxInboundStreams: this.maxInboundHopStreams,
96
160
  maxOutboundStreams: this.maxOutboundHopStreams,
97
161
  runOnLimitedConnection: true
@@ -106,19 +170,16 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
106
170
  async stop (): Promise<void> {
107
171
  this.reservationStore.clear()
108
172
  this.shutdownController.abort()
109
- await this.components.registrar.unhandle(RELAY_V2_HOP_CODEC)
173
+ await this.registrar.unhandle(RELAY_V2_HOP_CODEC)
110
174
 
111
175
  this.started = false
112
176
  }
113
177
 
114
- async onHop (stream: Stream, connection: Connection): Promise<void> {
178
+ async onHop ({ connection, stream }: IncomingStreamData): Promise<void> {
115
179
  this.log('received circuit v2 hop protocol stream from %p', connection.remotePeer)
116
180
 
117
- const signal = AbortSignal.timeout(this.hopTimeout)
118
- setMaxListeners(Infinity, signal)
119
-
120
181
  const options = {
121
- signal
182
+ signal: AbortSignal.timeout(this.hopTimeout)
122
183
  }
123
184
  const pbstr = pbStream(stream)
124
185
 
@@ -162,13 +223,13 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
162
223
  const hopstr = stream.pb(HopMessage)
163
224
  this.log('hop reserve request from %p', connection.remotePeer)
164
225
 
165
- if (Circuit.exactMatch(connection.remoteAddr)) {
226
+ if (isRelayAddr(connection.remoteAddr)) {
166
227
  this.log.error('relay reservation over circuit connection denied for peer: %p', connection.remotePeer)
167
228
  await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED }, options)
168
229
  return
169
230
  }
170
231
 
171
- if ((await this.components.connectionGater.denyInboundRelayReservation?.(connection.remotePeer)) === true) {
232
+ if ((await this.connectionGater.denyInboundRelayReservation?.(connection.remotePeer)) === true) {
172
233
  this.log.error('reservation for %p denied by connection gater', connection.remotePeer)
173
234
  await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED }, options)
174
235
  return
@@ -186,12 +247,12 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
186
247
  // result.expire is non-null if `ReservationStore.reserve` returns with status == OK
187
248
  if (result.expire != null) {
188
249
  const ttl = (result.expire * 1000) - Date.now()
189
- await this.components.peerStore.merge(connection.remotePeer, {
250
+ await this.peerStore.merge(connection.remotePeer, {
190
251
  tags: {
191
252
  [RELAY_SOURCE_TAG]: { value: 1, ttl },
192
253
  [KEEP_ALIVE_SOURCE_TAG]: { value: 1, ttl }
193
254
  }
194
- }, options)
255
+ })
195
256
  }
196
257
 
197
258
  await hopstr.write({
@@ -201,20 +262,17 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
201
262
  limit: this.reservationStore.get(connection.remotePeer)?.limit
202
263
  }, options)
203
264
  this.log('sent confirmation response to %s', connection.remotePeer)
204
-
205
- // close writable end of stream
206
- await hopstr.unwrap().unwrap().close(options)
207
265
  } catch (err) {
208
266
  this.log.error('failed to send confirmation response to %p - %e', connection.remotePeer, err)
209
267
  this.reservationStore.removeReservation(connection.remotePeer)
210
268
 
211
269
  try {
212
- await this.components.peerStore.merge(connection.remotePeer, {
270
+ await this.peerStore.merge(connection.remotePeer, {
213
271
  tags: {
214
272
  [RELAY_SOURCE_TAG]: undefined,
215
273
  [KEEP_ALIVE_SOURCE_TAG]: undefined
216
274
  }
217
- }, options)
275
+ })
218
276
  } catch (err) {
219
277
  this.log.error('failed to untag relay source peer %p - %e', connection.remotePeer, err)
220
278
  }
@@ -227,7 +285,7 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
227
285
  ): Promise<Reservation> {
228
286
  const addrs = []
229
287
 
230
- for (const relayAddr of this.components.addressManager.getAddresses()) {
288
+ for (const relayAddr of this.addressManager.getAddresses()) {
231
289
  if (relayAddr.toString().includes('/p2p-circuit')) {
232
290
  continue
233
291
  }
@@ -237,9 +295,9 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
237
295
 
238
296
  const envelope = await RecordEnvelope.seal(new ReservationVoucherRecord({
239
297
  peer: remotePeer,
240
- relay: this.components.peerId,
298
+ relay: this.peerId,
241
299
  expiration: expire
242
- }), this.components.privateKey)
300
+ }), this.privateKey)
243
301
 
244
302
  return {
245
303
  addrs,
@@ -249,7 +307,7 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
249
307
  payloadType: envelope.payloadType,
250
308
  payload: {
251
309
  peer: remotePeer.toMultihash().bytes,
252
- relay: this.components.peerId.toMultihash().bytes,
310
+ relay: this.peerId.toMultihash().bytes,
253
311
  expiration: expire
254
312
  },
255
313
  signature: envelope.signature
@@ -260,7 +318,7 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
260
318
  async handleConnect ({ stream, request, connection }: HopProtocolOptions, options: AbortOptions): Promise<void> {
261
319
  const hopstr = stream.pb(HopMessage)
262
320
 
263
- if (Circuit.matches(connection.remoteAddr)) {
321
+ if (isRelayAddr(connection.remoteAddr)) {
264
322
  this.log.error('relay reservation over circuit connection denied for peer: %p', connection.remotePeer)
265
323
  await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED }, options)
266
324
  return
@@ -292,13 +350,13 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
292
350
  return
293
351
  }
294
352
 
295
- if ((await this.components.connectionGater.denyOutboundRelayedConnection?.(connection.remotePeer, dstPeer)) === true) {
353
+ if ((await this.connectionGater.denyOutboundRelayedConnection?.(connection.remotePeer, dstPeer)) === true) {
296
354
  this.log.error('hop connect for %p to %p denied by connection gater', connection.remotePeer, dstPeer)
297
355
  await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED }, options)
298
356
  return
299
357
  }
300
358
 
301
- const connections = this.components.connectionManager.getConnections(dstPeer)
359
+ const connections = this.connectionManager.getConnections(dstPeer)
302
360
 
303
361
  if (connections.length === 0) {
304
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)
@@ -331,11 +389,12 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
331
389
  status: Status.OK,
332
390
  limit: reservation?.limit
333
391
  }, options)
392
+ const sourceStream = stream.unwrap()
334
393
 
335
394
  this.log('connection from %p to %p established - merging streams', connection.remotePeer, dstPeer)
336
395
 
337
396
  // Short circuit the two streams to create the relayed connection
338
- createLimitedRelay(stream.unwrap(), destinationStream, this.shutdownController.signal, reservation, {
397
+ createLimitedRelay(sourceStream, destinationStream, this.shutdownController.signal, reservation, {
339
398
  log: this.log
340
399
  })
341
400
  }
@@ -345,7 +404,7 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
345
404
  */
346
405
  async stopHop ({ connection, request }: StopOptions, options: AbortOptions): Promise<Stream | undefined> {
347
406
  this.log('starting circuit relay v2 stop request to %s', connection.remotePeer)
348
- const stream = await connection.newStream(RELAY_V2_STOP_CODEC, {
407
+ const stream = await connection.newStream([RELAY_V2_STOP_CODEC], {
349
408
  maxOutboundStreams: this.maxOutboundStopStreams,
350
409
  runOnLimitedConnection: true,
351
410
  ...options
@@ -380,3 +439,9 @@ export class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> imp
380
439
  return this.reservationStore.reservations
381
440
  }
382
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 => {