@libp2p/circuit-relay-v2 0.0.0-05b52d69c

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 (65) hide show
  1. package/LICENSE +4 -0
  2. package/README.md +69 -0
  3. package/dist/index.min.js +45 -0
  4. package/dist/src/constants.d.ts +55 -0
  5. package/dist/src/constants.d.ts.map +1 -0
  6. package/dist/src/constants.js +61 -0
  7. package/dist/src/constants.js.map +1 -0
  8. package/dist/src/index.d.ts +56 -0
  9. package/dist/src/index.d.ts.map +1 -0
  10. package/dist/src/index.js +39 -0
  11. package/dist/src/index.js.map +1 -0
  12. package/dist/src/pb/index.d.ts +93 -0
  13. package/dist/src/pb/index.d.ts.map +1 -0
  14. package/dist/src/pb/index.js +425 -0
  15. package/dist/src/pb/index.js.map +1 -0
  16. package/dist/src/server/advert-service.d.ts +46 -0
  17. package/dist/src/server/advert-service.d.ts.map +1 -0
  18. package/dist/src/server/advert-service.js +72 -0
  19. package/dist/src/server/advert-service.js.map +1 -0
  20. package/dist/src/server/index.d.ts +67 -0
  21. package/dist/src/server/index.d.ts.map +1 -0
  22. package/dist/src/server/index.js +313 -0
  23. package/dist/src/server/index.js.map +1 -0
  24. package/dist/src/server/reservation-store.d.ts +49 -0
  25. package/dist/src/server/reservation-store.d.ts.map +1 -0
  26. package/dist/src/server/reservation-store.js +65 -0
  27. package/dist/src/server/reservation-store.js.map +1 -0
  28. package/dist/src/server/reservation-voucher.d.ts +18 -0
  29. package/dist/src/server/reservation-voucher.d.ts.map +1 -0
  30. package/dist/src/server/reservation-voucher.js +36 -0
  31. package/dist/src/server/reservation-voucher.js.map +1 -0
  32. package/dist/src/transport/discovery.d.ts +48 -0
  33. package/dist/src/transport/discovery.d.ts.map +1 -0
  34. package/dist/src/transport/discovery.js +97 -0
  35. package/dist/src/transport/discovery.js.map +1 -0
  36. package/dist/src/transport/index.d.ts +58 -0
  37. package/dist/src/transport/index.d.ts.map +1 -0
  38. package/dist/src/transport/index.js +279 -0
  39. package/dist/src/transport/index.js.map +1 -0
  40. package/dist/src/transport/listener.d.ts +11 -0
  41. package/dist/src/transport/listener.d.ts.map +1 -0
  42. package/dist/src/transport/listener.js +66 -0
  43. package/dist/src/transport/listener.js.map +1 -0
  44. package/dist/src/transport/reservation-store.d.ts +74 -0
  45. package/dist/src/transport/reservation-store.d.ts.map +1 -0
  46. package/dist/src/transport/reservation-store.js +209 -0
  47. package/dist/src/transport/reservation-store.js.map +1 -0
  48. package/dist/src/utils.d.ts +14 -0
  49. package/dist/src/utils.d.ts.map +1 -0
  50. package/dist/src/utils.js +106 -0
  51. package/dist/src/utils.js.map +1 -0
  52. package/package.json +83 -0
  53. package/src/constants.ts +79 -0
  54. package/src/index.ts +64 -0
  55. package/src/pb/index.proto +67 -0
  56. package/src/pb/index.ts +539 -0
  57. package/src/server/advert-service.ts +109 -0
  58. package/src/server/index.ts +446 -0
  59. package/src/server/reservation-store.ts +116 -0
  60. package/src/server/reservation-voucher.ts +51 -0
  61. package/src/transport/discovery.ts +138 -0
  62. package/src/transport/index.ts +399 -0
  63. package/src/transport/listener.ts +98 -0
  64. package/src/transport/reservation-store.ts +312 -0
  65. package/src/utils.ts +134 -0
@@ -0,0 +1,138 @@
1
+ import { TypedEventEmitter } from '@libp2p/interface/events'
2
+ import {
3
+ RELAY_RENDEZVOUS_NS,
4
+ RELAY_V2_HOP_CODEC
5
+ } from '../constants.js'
6
+ import { namespaceToCid } from '../utils.js'
7
+ import type { ComponentLogger, Logger } from '@libp2p/interface'
8
+ import type { ContentRouting } from '@libp2p/interface/content-routing'
9
+ import type { PeerId } from '@libp2p/interface/peer-id'
10
+ import type { PeerStore } from '@libp2p/interface/peer-store'
11
+ import type { Startable } from '@libp2p/interface/startable'
12
+ import type { ConnectionManager } from '@libp2p/interface-internal/connection-manager'
13
+ import type { Registrar } from '@libp2p/interface-internal/registrar'
14
+ import type { TransportManager } from '@libp2p/interface-internal/transport-manager'
15
+
16
+ export interface RelayDiscoveryEvents {
17
+ 'relay:discover': CustomEvent<PeerId>
18
+ }
19
+
20
+ export interface RelayDiscoveryComponents {
21
+ peerId: PeerId
22
+ peerStore: PeerStore
23
+ connectionManager: ConnectionManager
24
+ transportManager: TransportManager
25
+ contentRouting: ContentRouting
26
+ registrar: Registrar
27
+ logger: ComponentLogger
28
+ }
29
+
30
+ /**
31
+ * ReservationManager automatically makes a circuit v2 reservation on any connected
32
+ * peers that support the circuit v2 HOP protocol.
33
+ */
34
+ export class RelayDiscovery extends TypedEventEmitter<RelayDiscoveryEvents> implements Startable {
35
+ private readonly peerId: PeerId
36
+ private readonly peerStore: PeerStore
37
+ private readonly contentRouting: ContentRouting
38
+ private readonly registrar: Registrar
39
+ private started: boolean
40
+ private topologyId?: string
41
+ private readonly log: Logger
42
+
43
+ constructor (components: RelayDiscoveryComponents) {
44
+ super()
45
+
46
+ this.log = components.logger.forComponent('libp2p:circuit-relay:discover-relays')
47
+ this.started = false
48
+ this.peerId = components.peerId
49
+ this.peerStore = components.peerStore
50
+ this.contentRouting = components.contentRouting
51
+ this.registrar = components.registrar
52
+ }
53
+
54
+ isStarted (): boolean {
55
+ return this.started
56
+ }
57
+
58
+ async start (): Promise<void> {
59
+ // register a topology listener for when new peers are encountered
60
+ // that support the hop protocol
61
+ this.topologyId = await this.registrar.register(RELAY_V2_HOP_CODEC, {
62
+ notifyOnTransient: true,
63
+ onConnect: (peerId) => {
64
+ this.safeDispatchEvent('relay:discover', { detail: peerId })
65
+ }
66
+ })
67
+
68
+ void this.discover()
69
+ .catch(err => {
70
+ this.log.error('error listening on relays', err)
71
+ })
72
+
73
+ this.started = true
74
+ }
75
+
76
+ stop (): void {
77
+ if (this.topologyId != null) {
78
+ this.registrar.unregister(this.topologyId)
79
+ }
80
+
81
+ this.started = false
82
+ }
83
+
84
+ /**
85
+ * Try to listen on available hop relay connections.
86
+ * The following order will happen while we do not have enough relays:
87
+ *
88
+ * 1. Check the metadata store for known relays, try to listen on the ones we are already connected
89
+ * 2. Dial and try to listen on the peers we know that support hop but are not connected
90
+ * 3. Search the network
91
+ */
92
+ async discover (): Promise<void> {
93
+ this.log('searching peer store for relays')
94
+ const peers = (await this.peerStore.all({
95
+ filters: [
96
+ // filter by a list of peers supporting RELAY_V2_HOP and ones we are not listening on
97
+ (peer) => {
98
+ return peer.protocols.includes(RELAY_V2_HOP_CODEC)
99
+ }
100
+ ],
101
+ orders: [
102
+ () => Math.random() < 0.5 ? 1 : -1
103
+ ]
104
+ }))
105
+
106
+ for (const peer of peers) {
107
+ this.log('found relay peer %p in content peer store', peer.id)
108
+ this.safeDispatchEvent('relay:discover', { detail: peer.id })
109
+ }
110
+
111
+ this.log('found %d relay peers in peer store', peers.length)
112
+
113
+ try {
114
+ this.log('searching content routing for relays')
115
+ const cid = await namespaceToCid(RELAY_RENDEZVOUS_NS)
116
+
117
+ let found = 0
118
+
119
+ for await (const provider of this.contentRouting.findProviders(cid)) {
120
+ if (provider.multiaddrs.length > 0 && !provider.id.equals(this.peerId)) {
121
+ const peerId = provider.id
122
+
123
+ found++
124
+ await this.peerStore.merge(peerId, {
125
+ multiaddrs: provider.multiaddrs
126
+ })
127
+
128
+ this.log('found relay peer %p in content routing', peerId)
129
+ this.safeDispatchEvent('relay:discover', { detail: peerId })
130
+ }
131
+ }
132
+
133
+ this.log('found %d relay peers in content routing', found)
134
+ } catch (err: any) {
135
+ this.log.error('failed when finding relays on the network', err)
136
+ }
137
+ }
138
+ }
@@ -0,0 +1,399 @@
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
+ }
38
+
39
+ export interface CircuitRelayTransportComponents extends RelayDiscoveryComponents {
40
+ peerId: PeerId
41
+ peerStore: PeerStore
42
+ registrar: Registrar
43
+ connectionManager: ConnectionManager
44
+ upgrader: Upgrader
45
+ addressManager: AddressManager
46
+ contentRouting: ContentRouting
47
+ connectionGater: ConnectionGater
48
+ events: TypedEventTarget<Libp2pEvents>
49
+ logger: ComponentLogger
50
+ }
51
+
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
+ /**
63
+ * RelayConfig configures the circuit v2 relay transport.
64
+ */
65
+ export interface CircuitRelayTransportInit extends RelayStoreInit {
66
+ /**
67
+ * The number of peers running diable relays to search for and
68
+ * connect to. (default: 0)
69
+ */
70
+ discoverRelays?: number
71
+
72
+ /**
73
+ * The maximum number of simultaneous STOP inbound streams that can be open at
74
+ * once - each inbound relayed connection uses a STOP stream (default: 300)
75
+ */
76
+ maxInboundStopStreams?: number
77
+
78
+ /**
79
+ * The maximum number of simultaneous STOP outbound streams that can be open at
80
+ * once. If this transport is used along with the relay server these settings
81
+ * should be set to the same value (default: 300)
82
+ */
83
+ maxOutboundStopStreams?: number
84
+
85
+ /**
86
+ * Incoming STOP requests (e.g. when a remote peer wants to dial us via a relay)
87
+ * must finish the initial protocol negotiation within this timeout in ms
88
+ * (default: 30000)
89
+ */
90
+ stopTimeout?: number
91
+
92
+ /**
93
+ * When creating a reservation it must complete within this number of ms
94
+ * (default: 10000)
95
+ */
96
+ reservationCompletionTimeout?: number
97
+ }
98
+
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
+ export function circuitRelayTransport (init: CircuitRelayTransportInit = {}): (components: CircuitRelayTransportComponents) => Transport {
396
+ return (components) => {
397
+ return new CircuitRelayTransport(components, init)
398
+ }
399
+ }
@@ -0,0 +1,98 @@
1
+ import { CodeError } from '@libp2p/interface/errors'
2
+ import { TypedEventEmitter } from '@libp2p/interface/events'
3
+ import { PeerMap } from '@libp2p/peer-collections'
4
+ import { multiaddr } from '@multiformats/multiaddr'
5
+ 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'
10
+ import type { Multiaddr } from '@multiformats/multiaddr'
11
+
12
+ export interface CircuitRelayTransportListenerComponents {
13
+ connectionManager: ConnectionManager
14
+ relayStore: ReservationStore
15
+ logger: ComponentLogger
16
+ }
17
+
18
+ class CircuitRelayTransportListener extends TypedEventEmitter<ListenerEvents> implements Listener {
19
+ private readonly connectionManager: ConnectionManager
20
+ private readonly relayStore: ReservationStore
21
+ private readonly listeningAddrs: PeerMap<Multiaddr[]>
22
+ private readonly log: Logger
23
+
24
+ constructor (components: CircuitRelayTransportListenerComponents) {
25
+ super()
26
+
27
+ this.log = components.logger.forComponent('libp2p:circuit-relay:transport:listener')
28
+ this.connectionManager = components.connectionManager
29
+ this.relayStore = components.relayStore
30
+ this.listeningAddrs = new PeerMap()
31
+
32
+ // remove listening addrs when a relay is removed
33
+ this.relayStore.addEventListener('relay:removed', this._onRemoveRelayPeer)
34
+ }
35
+
36
+ _onRemoveRelayPeer = (evt: CustomEvent<PeerId>): void => {
37
+ this.#removeRelayPeer(evt.detail)
38
+ }
39
+
40
+ async listen (addr: Multiaddr): Promise<void> {
41
+ this.log('listen on %a', addr)
42
+
43
+ // remove the circuit part to get the peer id of the relay
44
+ const relayAddr = addr.decapsulate('/p2p-circuit')
45
+ const relayConn = await this.connectionManager.openConnection(relayAddr)
46
+
47
+ if (!this.relayStore.hasReservation(relayConn.remotePeer)) {
48
+ // addRelay calls transportManager.listen which calls this listen method
49
+ await this.relayStore.addRelay(relayConn.remotePeer, 'configured')
50
+ return
51
+ }
52
+
53
+ const reservation = this.relayStore.getReservation(relayConn.remotePeer)
54
+
55
+ if (reservation == null) {
56
+ throw new CodeError('Did not have reservation after making reservation', 'ERR_NO_RESERVATION')
57
+ }
58
+
59
+ if (this.listeningAddrs.has(relayConn.remotePeer)) {
60
+ this.log('already listening on relay %p', relayConn.remotePeer)
61
+ return
62
+ }
63
+
64
+ // add all addresses from the relay reservation
65
+ this.listeningAddrs.set(relayConn.remotePeer, reservation.addrs.map(buf => {
66
+ return multiaddr(buf).encapsulate('/p2p-circuit')
67
+ }))
68
+
69
+ this.safeDispatchEvent('listening', {})
70
+ }
71
+
72
+ getAddrs (): Multiaddr[] {
73
+ return [...this.listeningAddrs.values()].flat()
74
+ }
75
+
76
+ async close (): Promise<void> {
77
+
78
+ }
79
+
80
+ #removeRelayPeer (peerId: PeerId): void {
81
+ const had = this.listeningAddrs.has(peerId)
82
+
83
+ this.log('relay peer removed %p - had reservation', peerId, had)
84
+
85
+ this.listeningAddrs.delete(peerId)
86
+
87
+ if (had) {
88
+ this.log.trace('removing relay event listener for peer %p', peerId)
89
+ this.relayStore.removeEventListener('relay:removed', this._onRemoveRelayPeer)
90
+ // Announce listen addresses change
91
+ this.safeDispatchEvent('close', {})
92
+ }
93
+ }
94
+ }
95
+
96
+ export function createListener (options: CircuitRelayTransportListenerComponents): Listener {
97
+ return new CircuitRelayTransportListener(options)
98
+ }