@libp2p/circuit-relay-v2 0.0.0 → 1.0.0-06e6d235f

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 (55) hide show
  1. package/README.md +1 -1
  2. package/dist/index.min.js +7 -7
  3. package/dist/src/index.d.ts +1 -1
  4. package/dist/src/index.d.ts.map +1 -1
  5. package/dist/src/pb/index.js.map +1 -1
  6. package/dist/src/server/advert-service.d.ts +2 -4
  7. package/dist/src/server/advert-service.d.ts.map +1 -1
  8. package/dist/src/server/advert-service.js +1 -1
  9. package/dist/src/server/advert-service.js.map +1 -1
  10. package/dist/src/server/index.d.ts +2 -8
  11. package/dist/src/server/index.d.ts.map +1 -1
  12. package/dist/src/server/index.js +1 -1
  13. package/dist/src/server/index.js.map +1 -1
  14. package/dist/src/server/reservation-store.d.ts +1 -3
  15. package/dist/src/server/reservation-store.d.ts.map +1 -1
  16. package/dist/src/server/reservation-store.js.map +1 -1
  17. package/dist/src/server/reservation-voucher.d.ts +1 -2
  18. package/dist/src/server/reservation-voucher.d.ts.map +1 -1
  19. package/dist/src/server/reservation-voucher.js.map +1 -1
  20. package/dist/src/transport/discovery.d.ts +3 -9
  21. package/dist/src/transport/discovery.d.ts.map +1 -1
  22. package/dist/src/transport/discovery.js +1 -1
  23. package/dist/src/transport/discovery.js.map +1 -1
  24. package/dist/src/transport/index.d.ts +2 -10
  25. package/dist/src/transport/index.d.ts.map +1 -1
  26. package/dist/src/transport/index.js +4 -273
  27. package/dist/src/transport/index.js.map +1 -1
  28. package/dist/src/transport/listener.d.ts +2 -3
  29. package/dist/src/transport/listener.d.ts.map +1 -1
  30. package/dist/src/transport/listener.js +2 -2
  31. package/dist/src/transport/listener.js.map +1 -1
  32. package/dist/src/transport/reservation-store.d.ts +2 -7
  33. package/dist/src/transport/reservation-store.d.ts.map +1 -1
  34. package/dist/src/transport/reservation-store.js +17 -1
  35. package/dist/src/transport/reservation-store.js.map +1 -1
  36. package/dist/src/transport/transport.d.ts +60 -0
  37. package/dist/src/transport/transport.d.ts.map +1 -0
  38. package/dist/src/transport/transport.js +286 -0
  39. package/dist/src/transport/transport.js.map +1 -0
  40. package/dist/src/utils.d.ts +1 -2
  41. package/dist/src/utils.d.ts.map +1 -1
  42. package/dist/src/utils.js +4 -10
  43. package/dist/src/utils.js.map +1 -1
  44. package/package.json +16 -11
  45. package/src/index.ts +1 -1
  46. package/src/server/advert-service.ts +2 -4
  47. package/src/server/index.ts +3 -10
  48. package/src/server/reservation-store.ts +1 -3
  49. package/src/server/reservation-voucher.ts +1 -2
  50. package/src/transport/discovery.ts +3 -9
  51. package/src/transport/index.ts +5 -343
  52. package/src/transport/listener.ts +4 -6
  53. package/src/transport/reservation-store.ts +21 -8
  54. package/src/transport/transport.ts +346 -0
  55. package/src/utils.ts +6 -13
@@ -1,40 +1,8 @@
1
- import { CodeError } from '@libp2p/interface/errors'
2
- import { symbol, type Transport, type CreateListenerOptions, type Listener, type Upgrader } from '@libp2p/interface/transport'
3
- import { peerIdFromBytes, peerIdFromString } from '@libp2p/peer-id'
4
- import { streamToMaConnection } from '@libp2p/utils/stream-to-ma-conn'
5
- import * as mafmt from '@multiformats/mafmt'
6
- import { multiaddr } from '@multiformats/multiaddr'
7
- import { pbStream } from 'it-protobuf-stream'
8
- import { CIRCUIT_PROTO_CODE, ERR_HOP_REQUEST_FAILED, ERR_RELAYED_DIAL, MAX_CONNECTIONS, RELAY_V2_HOP_CODEC, RELAY_V2_STOP_CODEC } from '../constants.js'
9
- import { StopMessage, HopMessage, Status } from '../pb/index.js'
10
- import { RelayDiscovery, type RelayDiscoveryComponents } from './discovery.js'
11
- import { createListener } from './listener.js'
12
- import { type RelayStoreInit, ReservationStore } from './reservation-store.js'
13
- import type { Libp2pEvents, AbortOptions, ComponentLogger, Logger } from '@libp2p/interface'
14
- import type { Connection, Stream } from '@libp2p/interface/connection'
15
- import type { ConnectionGater } from '@libp2p/interface/connection-gater'
16
- import type { ContentRouting } from '@libp2p/interface/content-routing'
17
- import type { TypedEventTarget } from '@libp2p/interface/events'
18
- import type { PeerId } from '@libp2p/interface/peer-id'
19
- import type { PeerStore } from '@libp2p/interface/peer-store'
20
- import type { AddressManager } from '@libp2p/interface-internal/address-manager'
21
- import type { ConnectionManager } from '@libp2p/interface-internal/connection-manager'
22
- import type { IncomingStreamData, Registrar } from '@libp2p/interface-internal/registrar'
23
- import type { Multiaddr } from '@multiformats/multiaddr'
24
-
25
- const isValidStop = (request: StopMessage): request is Required<StopMessage> => {
26
- if (request.peer == null) {
27
- return false
28
- }
29
-
30
- try {
31
- request.peer.addrs.forEach(multiaddr)
32
- } catch {
33
- return false
34
- }
35
-
36
- return true
37
- }
1
+ import { type Transport, type Upgrader, type Libp2pEvents, type ComponentLogger, type ConnectionGater, type ContentRouting, type TypedEventTarget, type PeerId, type PeerStore } from '@libp2p/interface'
2
+ import { type RelayDiscoveryComponents } from './discovery.js'
3
+ import { type RelayStoreInit } from './reservation-store.js'
4
+ import { CircuitRelayTransport } from './transport.js'
5
+ import type { AddressManager, ConnectionManager, Registrar } from '@libp2p/interface-internal'
38
6
 
39
7
  export interface CircuitRelayTransportComponents extends RelayDiscoveryComponents {
40
8
  peerId: PeerId
@@ -49,16 +17,6 @@ export interface CircuitRelayTransportComponents extends RelayDiscoveryComponent
49
17
  logger: ComponentLogger
50
18
  }
51
19
 
52
- interface ConnectOptions {
53
- stream: Stream
54
- connection: Connection
55
- destinationPeer: PeerId
56
- destinationAddr: Multiaddr
57
- relayAddr: Multiaddr
58
- ma: Multiaddr
59
- disconnectOnFailure: boolean
60
- }
61
-
62
20
  /**
63
21
  * RelayConfig configures the circuit v2 relay transport.
64
22
  */
@@ -96,302 +54,6 @@ export interface CircuitRelayTransportInit extends RelayStoreInit {
96
54
  reservationCompletionTimeout?: number
97
55
  }
98
56
 
99
- const defaults = {
100
- maxInboundStopStreams: MAX_CONNECTIONS,
101
- maxOutboundStopStreams: MAX_CONNECTIONS,
102
- stopTimeout: 30000
103
- }
104
-
105
- class CircuitRelayTransport implements Transport {
106
- private readonly discovery?: RelayDiscovery
107
- private readonly registrar: Registrar
108
- private readonly peerStore: PeerStore
109
- private readonly connectionManager: ConnectionManager
110
- private readonly peerId: PeerId
111
- private readonly upgrader: Upgrader
112
- private readonly addressManager: AddressManager
113
- private readonly connectionGater: ConnectionGater
114
- private readonly reservationStore: ReservationStore
115
- private readonly logger: ComponentLogger
116
- private readonly maxInboundStopStreams: number
117
- private readonly maxOutboundStopStreams?: number
118
- private readonly stopTimeout: number
119
- private started: boolean
120
- private readonly log: Logger
121
-
122
- constructor (components: CircuitRelayTransportComponents, init: CircuitRelayTransportInit) {
123
- this.log = components.logger.forComponent('libp2p:circuit-relay:transport')
124
- this.registrar = components.registrar
125
- this.peerStore = components.peerStore
126
- this.connectionManager = components.connectionManager
127
- this.logger = components.logger
128
- this.peerId = components.peerId
129
- this.upgrader = components.upgrader
130
- this.addressManager = components.addressManager
131
- this.connectionGater = components.connectionGater
132
- this.maxInboundStopStreams = init.maxInboundStopStreams ?? defaults.maxInboundStopStreams
133
- this.maxOutboundStopStreams = init.maxOutboundStopStreams ?? defaults.maxOutboundStopStreams
134
- this.stopTimeout = init.stopTimeout ?? defaults.stopTimeout
135
-
136
- if (init.discoverRelays != null && init.discoverRelays > 0) {
137
- this.discovery = new RelayDiscovery(components)
138
- this.discovery.addEventListener('relay:discover', (evt) => {
139
- this.reservationStore.addRelay(evt.detail, 'discovered')
140
- .catch(err => {
141
- this.log.error('could not add discovered relay %p', evt.detail, err)
142
- })
143
- })
144
- }
145
-
146
- this.reservationStore = new ReservationStore(components, init)
147
- this.reservationStore.addEventListener('relay:not-enough-relays', () => {
148
- this.discovery?.discover()
149
- .catch(err => {
150
- this.log.error('could not discover relays', err)
151
- })
152
- })
153
-
154
- this.started = false
155
- }
156
-
157
- isStarted (): boolean {
158
- return this.started
159
- }
160
-
161
- async start (): Promise<void> {
162
- await this.reservationStore.start()
163
- await this.discovery?.start()
164
-
165
- await this.registrar.handle(RELAY_V2_STOP_CODEC, (data) => {
166
- void this.onStop(data).catch(err => {
167
- this.log.error('error while handling STOP protocol', err)
168
- data.stream.abort(err)
169
- })
170
- }, {
171
- maxInboundStreams: this.maxInboundStopStreams,
172
- maxOutboundStreams: this.maxOutboundStopStreams,
173
- runOnTransientConnection: true
174
- })
175
-
176
- this.started = true
177
- }
178
-
179
- async stop (): Promise<void> {
180
- this.discovery?.stop()
181
- await this.reservationStore.stop()
182
- await this.registrar.unhandle(RELAY_V2_STOP_CODEC)
183
-
184
- this.started = false
185
- }
186
-
187
- readonly [symbol] = true
188
-
189
- readonly [Symbol.toStringTag] = 'libp2p/circuit-relay-v2'
190
-
191
- /**
192
- * Dial a peer over a relay
193
- */
194
- async dial (ma: Multiaddr, options: AbortOptions = {}): Promise<Connection> {
195
- if (ma.protoCodes().filter(code => code === CIRCUIT_PROTO_CODE).length !== 1) {
196
- const errMsg = 'Invalid circuit relay address'
197
- this.log.error(errMsg, ma)
198
- throw new CodeError(errMsg, ERR_RELAYED_DIAL)
199
- }
200
-
201
- // Check the multiaddr to see if it contains a relay and a destination peer
202
- const addrs = ma.toString().split('/p2p-circuit')
203
- const relayAddr = multiaddr(addrs[0])
204
- const destinationAddr = multiaddr(addrs[addrs.length - 1])
205
- const relayId = relayAddr.getPeerId()
206
- const destinationId = destinationAddr.getPeerId()
207
-
208
- if (relayId == null || destinationId == null) {
209
- const errMsg = `Circuit relay dial to ${ma.toString()} failed as address did not have peer ids`
210
- this.log.error(errMsg)
211
- throw new CodeError(errMsg, ERR_RELAYED_DIAL)
212
- }
213
-
214
- const relayPeer = peerIdFromString(relayId)
215
- const destinationPeer = peerIdFromString(destinationId)
216
-
217
- let disconnectOnFailure = false
218
- const relayConnections = this.connectionManager.getConnections(relayPeer)
219
- let relayConnection = relayConnections[0]
220
-
221
- if (relayConnection == null) {
222
- await this.peerStore.merge(relayPeer, {
223
- multiaddrs: [relayAddr]
224
- })
225
- relayConnection = await this.connectionManager.openConnection(relayPeer, options)
226
- disconnectOnFailure = true
227
- }
228
-
229
- let stream: Stream | undefined
230
-
231
- try {
232
- stream = await relayConnection.newStream([RELAY_V2_HOP_CODEC])
233
-
234
- return await this.connectV2({
235
- stream,
236
- connection: relayConnection,
237
- destinationPeer,
238
- destinationAddr,
239
- relayAddr,
240
- ma,
241
- disconnectOnFailure
242
- })
243
- } catch (err: any) {
244
- this.log.error('circuit relay dial to destination %p via relay %p failed', destinationPeer, relayPeer, err)
245
-
246
- if (stream != null) {
247
- stream.abort(err)
248
- }
249
-
250
- disconnectOnFailure && await relayConnection.close()
251
- throw err
252
- }
253
- }
254
-
255
- async connectV2 (
256
- {
257
- stream, connection, destinationPeer,
258
- destinationAddr, relayAddr, ma,
259
- disconnectOnFailure
260
- }: ConnectOptions
261
- ): Promise<Connection> {
262
- try {
263
- const pbstr = pbStream(stream)
264
- const hopstr = pbstr.pb(HopMessage)
265
- await hopstr.write({
266
- type: HopMessage.Type.CONNECT,
267
- peer: {
268
- id: destinationPeer.toBytes(),
269
- addrs: [multiaddr(destinationAddr).bytes]
270
- }
271
- })
272
-
273
- const status = await hopstr.read()
274
-
275
- if (status.status !== Status.OK) {
276
- throw new CodeError(`failed to connect via relay with status ${status?.status?.toString() ?? 'undefined'}`, ERR_HOP_REQUEST_FAILED)
277
- }
278
-
279
- const maConn = streamToMaConnection({
280
- stream: pbstr.unwrap(),
281
- remoteAddr: ma,
282
- localAddr: relayAddr.encapsulate(`/p2p-circuit/p2p/${this.peerId.toString()}`),
283
- logger: this.logger
284
- })
285
-
286
- this.log('new outbound transient connection %a', maConn.remoteAddr)
287
- return await this.upgrader.upgradeOutbound(maConn, {
288
- transient: true
289
- })
290
- } catch (err) {
291
- this.log.error(`Circuit relay dial to destination ${destinationPeer.toString()} via relay ${connection.remotePeer.toString()} failed`, err)
292
- disconnectOnFailure && await connection.close()
293
- throw err
294
- }
295
- }
296
-
297
- /**
298
- * Create a listener
299
- */
300
- createListener (options: CreateListenerOptions): Listener {
301
- return createListener({
302
- connectionManager: this.connectionManager,
303
- relayStore: this.reservationStore,
304
- logger: this.logger
305
- })
306
- }
307
-
308
- /**
309
- * Filter check for all Multiaddrs that this transport can dial on
310
- *
311
- * @param {Multiaddr[]} multiaddrs
312
- * @returns {Multiaddr[]}
313
- */
314
- filter (multiaddrs: Multiaddr[]): Multiaddr[] {
315
- multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs]
316
-
317
- return multiaddrs.filter((ma) => {
318
- return mafmt.Circuit.matches(ma)
319
- })
320
- }
321
-
322
- /**
323
- * An incoming STOP request means a remote peer wants to dial us via a relay
324
- */
325
- async onStop ({ connection, stream }: IncomingStreamData): Promise<void> {
326
- const signal = AbortSignal.timeout(this.stopTimeout)
327
- const pbstr = pbStream(stream).pb(StopMessage)
328
- const request = await pbstr.read({
329
- signal
330
- })
331
-
332
- this.log('new circuit relay v2 stop stream from %p with type %s', connection.remotePeer, request.type)
333
-
334
- if (request?.type === undefined) {
335
- this.log.error('type was missing from circuit v2 stop protocol request from %s', connection.remotePeer)
336
- await pbstr.write({ type: StopMessage.Type.STATUS, status: Status.MALFORMED_MESSAGE }, {
337
- signal
338
- })
339
- await stream.close()
340
- return
341
- }
342
-
343
- // Validate the STOP request has the required input
344
- if (request.type !== StopMessage.Type.CONNECT) {
345
- this.log.error('invalid stop connect request via peer %p', connection.remotePeer)
346
- await pbstr.write({ type: StopMessage.Type.STATUS, status: Status.UNEXPECTED_MESSAGE }, {
347
- signal
348
- })
349
- await stream.close()
350
- return
351
- }
352
-
353
- if (!isValidStop(request)) {
354
- this.log.error('invalid stop connect request via peer %p', connection.remotePeer)
355
- await pbstr.write({ type: StopMessage.Type.STATUS, status: Status.MALFORMED_MESSAGE }, {
356
- signal
357
- })
358
- await stream.close()
359
- return
360
- }
361
-
362
- const remotePeerId = peerIdFromBytes(request.peer.id)
363
-
364
- if ((await this.connectionGater.denyInboundRelayedConnection?.(connection.remotePeer, remotePeerId)) === true) {
365
- this.log.error('connection gater denied inbound relayed connection from %p', connection.remotePeer)
366
- await pbstr.write({ type: StopMessage.Type.STATUS, status: Status.PERMISSION_DENIED }, {
367
- signal
368
- })
369
- await stream.close()
370
- return
371
- }
372
-
373
- this.log.trace('sending success response to %p', connection.remotePeer)
374
- await pbstr.write({ type: StopMessage.Type.STATUS, status: Status.OK }, {
375
- signal
376
- })
377
-
378
- const remoteAddr = connection.remoteAddr.encapsulate(`/p2p-circuit/p2p/${remotePeerId.toString()}`)
379
- const localAddr = this.addressManager.getAddresses()[0]
380
- const maConn = streamToMaConnection({
381
- stream: pbstr.unwrap().unwrap(),
382
- remoteAddr,
383
- localAddr,
384
- logger: this.logger
385
- })
386
-
387
- this.log('new inbound transient connection %a', maConn.remoteAddr)
388
- await this.upgrader.upgradeInbound(maConn, {
389
- transient: true
390
- })
391
- this.log('%s connection %a upgraded', 'inbound', maConn.remoteAddr)
392
- }
393
- }
394
-
395
57
  export function circuitRelayTransport (init: CircuitRelayTransportInit = {}): (components: CircuitRelayTransportComponents) => Transport {
396
58
  return (components) => {
397
59
  return new CircuitRelayTransport(components, init)
@@ -1,12 +1,9 @@
1
- import { CodeError } from '@libp2p/interface/errors'
2
- import { TypedEventEmitter } from '@libp2p/interface/events'
1
+ import { CodeError, TypedEventEmitter } from '@libp2p/interface'
3
2
  import { PeerMap } from '@libp2p/peer-collections'
4
3
  import { multiaddr } from '@multiformats/multiaddr'
5
4
  import type { ReservationStore } from './reservation-store.js'
6
- import type { ComponentLogger, Logger } from '@libp2p/interface'
7
- import type { PeerId } from '@libp2p/interface/peer-id'
8
- import type { Listener, ListenerEvents } from '@libp2p/interface/transport'
9
- import type { ConnectionManager } from '@libp2p/interface-internal/connection-manager'
5
+ import type { ComponentLogger, Logger, PeerId, Listener, ListenerEvents } from '@libp2p/interface'
6
+ import type { ConnectionManager } from '@libp2p/interface-internal'
10
7
  import type { Multiaddr } from '@multiformats/multiaddr'
11
8
 
12
9
  export interface CircuitRelayTransportListenerComponents {
@@ -45,6 +42,7 @@ class CircuitRelayTransportListener extends TypedEventEmitter<ListenerEvents> im
45
42
  const relayConn = await this.connectionManager.openConnection(relayAddr)
46
43
 
47
44
  if (!this.relayStore.hasReservation(relayConn.remotePeer)) {
45
+ this.log('making reservation on peer %p', relayConn.remotePeer)
48
46
  // addRelay calls transportManager.listen which calls this listen method
49
47
  await this.relayStore.addRelay(relayConn.remotePeer, 'configured')
50
48
  return
@@ -1,19 +1,14 @@
1
- import { TypedEventEmitter, type TypedEventTarget } from '@libp2p/interface/events'
1
+ import { TypedEventEmitter, type TypedEventTarget, type Libp2pEvents, type AbortOptions, type ComponentLogger, type Logger, type Connection, type PeerId, type PeerStore, type Startable } from '@libp2p/interface'
2
2
  import { PeerMap } from '@libp2p/peer-collections'
3
3
  import { PeerJobQueue } from '@libp2p/utils/peer-job-queue'
4
4
  import { multiaddr } from '@multiformats/multiaddr'
5
5
  import { pbStream } from 'it-protobuf-stream'
6
+ import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
6
7
  import { DEFAULT_RESERVATION_CONCURRENCY, RELAY_TAG, RELAY_V2_HOP_CODEC } from '../constants.js'
7
8
  import { HopMessage, Status } from '../pb/index.js'
8
9
  import { getExpirationMilliseconds } from '../utils.js'
9
10
  import type { Reservation } from '../pb/index.js'
10
- import type { Libp2pEvents, AbortOptions, ComponentLogger, Logger } from '@libp2p/interface'
11
- import type { Connection } from '@libp2p/interface/connection'
12
- import type { PeerId } from '@libp2p/interface/peer-id'
13
- import type { PeerStore } from '@libp2p/interface/peer-store'
14
- import type { Startable } from '@libp2p/interface/startable'
15
- import type { ConnectionManager } from '@libp2p/interface-internal/connection-manager'
16
- import type { TransportManager } from '@libp2p/interface-internal/transport-manager'
11
+ import type { ConnectionManager, TransportManager } from '@libp2p/interface-internal'
17
12
 
18
13
  // allow refreshing a relay reservation if it will expire in the next 10 minutes
19
14
  const REFRESH_WINDOW = (60 * 1000) * 10
@@ -272,12 +267,30 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
272
267
  response = await hopstr.read(options)
273
268
  } catch (err: any) {
274
269
  this.log.error('error parsing reserve message response from %p because', connection.remotePeer, err)
270
+ stream.abort(err)
275
271
  throw err
276
272
  } finally {
277
273
  await stream.close()
278
274
  }
279
275
 
280
276
  if (response.status === Status.OK && (response.reservation != null)) {
277
+ // check that the returned relay has the relay address - this can be
278
+ // omitted when requesting a reservation from a go-libp2p relay we
279
+ // already have a reservation on
280
+ let hasRelayAddress = false
281
+ const relayAddressBytes = connection.remoteAddr.bytes
282
+
283
+ for (const buf of response.reservation.addrs) {
284
+ if (uint8ArrayEquals(relayAddressBytes, buf)) {
285
+ hasRelayAddress = true
286
+ break
287
+ }
288
+ }
289
+
290
+ if (!hasRelayAddress) {
291
+ response.reservation.addrs.push(relayAddressBytes)
292
+ }
293
+
281
294
  return response.reservation
282
295
  }
283
296