@libp2p/circuit-relay-v2 3.2.23 → 3.2.24-8484de8a2
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.
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +4 -4
- package/dist/src/constants.d.ts +0 -4
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +0 -4
- package/dist/src/constants.js.map +1 -1
- package/dist/src/index.d.ts +170 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +12 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/server/index.d.ts +41 -45
- package/dist/src/server/index.d.ts.map +1 -1
- package/dist/src/server/index.js +30 -47
- package/dist/src/server/index.js.map +1 -1
- package/dist/src/server/reservation-store.d.ts +2 -36
- package/dist/src/server/reservation-store.d.ts.map +1 -1
- package/dist/src/server/reservation-store.js.map +1 -1
- package/dist/src/transport/discovery.d.ts +2 -17
- package/dist/src/transport/discovery.d.ts.map +1 -1
- package/dist/src/transport/discovery.js +2 -2
- package/dist/src/transport/discovery.js.map +1 -1
- package/dist/src/transport/index.d.ts +34 -42
- package/dist/src/transport/index.d.ts.map +1 -1
- package/dist/src/transport/index.js +291 -5
- package/dist/src/transport/index.js.map +1 -1
- package/dist/src/transport/reservation-store.d.ts +3 -37
- package/dist/src/transport/reservation-store.d.ts.map +1 -1
- package/dist/src/transport/reservation-store.js +4 -6
- package/dist/src/transport/reservation-store.js.map +1 -1
- package/dist/src/transport/stream-to-conn.d.ts +19 -0
- package/dist/src/transport/stream-to-conn.d.ts.map +1 -0
- package/dist/src/transport/stream-to-conn.js +60 -0
- package/dist/src/transport/stream-to-conn.js.map +1 -0
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +36 -59
- package/dist/src/utils.js.map +1 -1
- package/package.json +19 -24
- package/src/constants.ts +0 -5
- package/src/index.ts +207 -8
- package/src/server/index.ts +35 -100
- package/src/server/reservation-store.ts +2 -42
- package/src/transport/discovery.ts +4 -22
- package/src/transport/index.ts +338 -46
- package/src/transport/reservation-store.ts +8 -46
- package/src/transport/stream-to-conn.ts +91 -0
- package/src/utils.ts +37 -66
- package/dist/src/transport/transport.d.ts +0 -54
- package/dist/src/transport/transport.d.ts.map +0 -1
- package/dist/src/transport/transport.js +0 -314
- package/dist/src/transport/transport.js.map +0 -1
- package/dist/typedoc-urls.json +0 -23
- package/src/transport/transport.ts +0 -378
package/src/transport/index.ts
CHANGED
@@ -1,65 +1,357 @@
|
|
1
|
-
import {
|
2
|
-
import
|
3
|
-
import
|
4
|
-
import
|
5
|
-
import
|
6
|
-
import
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
1
|
+
import { DialError, InvalidMessageError, serviceCapabilities, serviceDependencies, start, stop, transportSymbol } from '@libp2p/interface'
|
2
|
+
import { peerFilter } from '@libp2p/peer-collections'
|
3
|
+
import { peerIdFromMultihash, peerIdFromString } from '@libp2p/peer-id'
|
4
|
+
import { pbStream } from '@libp2p/utils'
|
5
|
+
import { CODE_P2P, multiaddr } from '@multiformats/multiaddr'
|
6
|
+
import { Circuit } from '@multiformats/multiaddr-matcher'
|
7
|
+
import { setMaxListeners } from 'main-event'
|
8
|
+
import * as Digest from 'multiformats/hashes/digest'
|
9
|
+
import { CustomProgressEvent } from 'progress-events'
|
10
|
+
import { DEFAULT_DISCOVERY_FILTER_ERROR_RATE, DEFAULT_DISCOVERY_FILTER_SIZE, MAX_CONNECTIONS, RELAY_V2_HOP_CODEC, RELAY_V2_STOP_CODEC } from '../constants.js'
|
11
|
+
import { StopMessage, HopMessage, Status } from '../pb/index.js'
|
12
|
+
import { CircuitListen, CircuitSearch, LimitTracker } from '../utils.js'
|
13
|
+
import { RelayDiscovery } from './discovery.js'
|
14
|
+
import { createListener } from './listener.js'
|
15
|
+
import { ReservationStore } from './reservation-store.js'
|
16
|
+
import { streamToMaConnection } from './stream-to-conn.js'
|
17
|
+
import type { CircuitRelayTransportComponents, CircuitRelayTransportInit } from '../index.ts'
|
18
|
+
import type { Transport, CreateListenerOptions, Listener, Logger, Connection, Stream, OutboundConnectionUpgradeEvents, DialTransportOptions, OpenConnectionProgressEvents } from '@libp2p/interface'
|
19
|
+
import type { Multiaddr } from '@multiformats/multiaddr'
|
20
|
+
import type { ProgressEvent } from 'progress-events'
|
21
|
+
|
22
|
+
const isValidStop = (request: StopMessage): request is Required<StopMessage> => {
|
23
|
+
if (request.peer == null) {
|
24
|
+
return false
|
25
|
+
}
|
26
|
+
|
27
|
+
try {
|
28
|
+
request.peer.addrs.forEach(multiaddr)
|
29
|
+
} catch {
|
30
|
+
return false
|
31
|
+
}
|
32
|
+
|
33
|
+
return true
|
15
34
|
}
|
16
35
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
36
|
+
const defaults = {
|
37
|
+
maxInboundStopStreams: MAX_CONNECTIONS,
|
38
|
+
maxOutboundStopStreams: MAX_CONNECTIONS,
|
39
|
+
stopTimeout: 30000
|
40
|
+
}
|
41
|
+
|
42
|
+
export type CircuitRelayDialEvents =
|
43
|
+
OutboundConnectionUpgradeEvents |
|
44
|
+
OpenConnectionProgressEvents |
|
45
|
+
ProgressEvent<'circuit-relay:open-connection'> |
|
46
|
+
ProgressEvent<'circuit-relay:reuse-connection'> |
|
47
|
+
ProgressEvent<'circuit-relay:open-hop-stream'> |
|
48
|
+
ProgressEvent<'circuit-relay:write-connect-message'> |
|
49
|
+
ProgressEvent<'circuit-relay:read-connect-response'>
|
50
|
+
|
51
|
+
export class CircuitRelayTransport implements Transport<CircuitRelayDialEvents> {
|
52
|
+
private readonly components: CircuitRelayTransportComponents
|
53
|
+
private readonly discovery?: RelayDiscovery
|
54
|
+
public readonly reservationStore: ReservationStore
|
55
|
+
private readonly maxInboundStopStreams: number
|
56
|
+
private readonly maxOutboundStopStreams?: number
|
57
|
+
private started: boolean
|
58
|
+
private readonly log: Logger
|
59
|
+
private shutdownController: AbortController
|
60
|
+
|
61
|
+
constructor (components: CircuitRelayTransportComponents, init: CircuitRelayTransportInit = {}) {
|
62
|
+
this.components = components
|
63
|
+
this.log = components.logger.forComponent('libp2p:circuit-relay:transport')
|
64
|
+
this.maxInboundStopStreams = init.maxInboundStopStreams ?? defaults.maxInboundStopStreams
|
65
|
+
this.maxOutboundStopStreams = init.maxOutboundStopStreams ?? defaults.maxOutboundStopStreams
|
66
|
+
this.shutdownController = new AbortController()
|
67
|
+
|
68
|
+
this.discovery = new RelayDiscovery(components, {
|
69
|
+
filter: init.discoveryFilter ?? peerFilter(DEFAULT_DISCOVERY_FILTER_SIZE, DEFAULT_DISCOVERY_FILTER_ERROR_RATE)
|
70
|
+
})
|
71
|
+
this.discovery.addEventListener('relay:discover', (evt) => {
|
72
|
+
this.reservationStore.addRelay(evt.detail, 'discovered')
|
73
|
+
.catch(err => {
|
74
|
+
if (err.name !== 'HadEnoughRelaysError' && err.name !== 'RelayQueueFullError') {
|
75
|
+
this.log.error('could not add discovered relay %p', evt.detail, err)
|
76
|
+
}
|
77
|
+
})
|
78
|
+
})
|
79
|
+
this.reservationStore = new ReservationStore(components, init)
|
80
|
+
this.reservationStore.addEventListener('relay:not-enough-relays', () => {
|
81
|
+
this.discovery?.startDiscovery()
|
82
|
+
})
|
83
|
+
this.reservationStore.addEventListener('relay:found-enough-relays', () => {
|
84
|
+
this.discovery?.stopDiscovery()
|
85
|
+
})
|
86
|
+
|
87
|
+
this.started = false
|
88
|
+
|
89
|
+
this.onStop = this.onStop.bind(this)
|
90
|
+
}
|
91
|
+
|
92
|
+
readonly [Symbol.toStringTag] = '@libp2p/circuit-relay-v2-transport'
|
93
|
+
|
94
|
+
readonly [serviceCapabilities]: string[] = [
|
95
|
+
'@libp2p/transport',
|
96
|
+
'@libp2p/circuit-relay-v2-transport'
|
97
|
+
]
|
98
|
+
|
99
|
+
get [serviceDependencies] (): string[] {
|
100
|
+
// we only need identify if discovery is enabled
|
101
|
+
if (this.discovery != null) {
|
102
|
+
return [
|
103
|
+
'@libp2p/identify'
|
104
|
+
]
|
105
|
+
}
|
106
|
+
|
107
|
+
return []
|
108
|
+
}
|
109
|
+
|
110
|
+
readonly [transportSymbol] = true
|
111
|
+
|
112
|
+
isStarted (): boolean {
|
113
|
+
return this.started
|
114
|
+
}
|
115
|
+
|
116
|
+
async start (): Promise<void> {
|
117
|
+
this.shutdownController = new AbortController()
|
118
|
+
setMaxListeners(Infinity, this.shutdownController.signal)
|
119
|
+
|
120
|
+
await this.components.registrar.handle(RELAY_V2_STOP_CODEC, this.onStop, {
|
121
|
+
maxInboundStreams: this.maxInboundStopStreams,
|
122
|
+
maxOutboundStreams: this.maxOutboundStopStreams,
|
123
|
+
runOnLimitedConnection: true
|
124
|
+
})
|
125
|
+
|
126
|
+
await start(this.discovery, this.reservationStore)
|
127
|
+
|
128
|
+
this.started = true
|
129
|
+
}
|
130
|
+
|
131
|
+
async stop (): Promise<void> {
|
132
|
+
this.shutdownController.abort()
|
133
|
+
await stop(this.discovery, this.reservationStore)
|
134
|
+
await this.components.registrar.unhandle(RELAY_V2_STOP_CODEC)
|
135
|
+
|
136
|
+
this.started = false
|
137
|
+
}
|
138
|
+
|
21
139
|
/**
|
22
|
-
*
|
23
|
-
* slots on the same peer
|
140
|
+
* Dial a peer over a relay
|
24
141
|
*/
|
25
|
-
|
142
|
+
async dial (ma: Multiaddr, options: DialTransportOptions<CircuitRelayDialEvents>): Promise<Connection> {
|
143
|
+
// Check the multiaddr to see if it contains a relay and a destination peer
|
144
|
+
const addrs = ma.toString().split('/p2p-circuit')
|
145
|
+
const relayAddr = multiaddr(addrs[0])
|
146
|
+
const destinationAddr = multiaddr(addrs[addrs.length - 1])
|
147
|
+
const relayId = relayAddr.getComponents().find(c => c.code === CODE_P2P)?.value
|
148
|
+
const destinationId = destinationAddr.getComponents().find(c => c.code === CODE_P2P)?.value
|
149
|
+
|
150
|
+
if (relayId == null || destinationId == null) {
|
151
|
+
const errMsg = `ircuit relay dial to ${ma.toString()} failed as address did not have both relay and destination PeerIDs`
|
152
|
+
this.log.error(`c${errMsg}`)
|
153
|
+
throw new DialError(`C${errMsg}`)
|
154
|
+
}
|
155
|
+
|
156
|
+
const relayPeer = peerIdFromString(relayId)
|
157
|
+
const destinationPeer = peerIdFromString(destinationId)
|
158
|
+
|
159
|
+
const relayConnections = this.components.connectionManager.getConnections(relayPeer)
|
160
|
+
let relayConnection = relayConnections[0]
|
161
|
+
|
162
|
+
if (relayConnection == null) {
|
163
|
+
await this.components.peerStore.merge(relayPeer, {
|
164
|
+
multiaddrs: [relayAddr]
|
165
|
+
})
|
166
|
+
|
167
|
+
options.onProgress?.(new CustomProgressEvent('circuit-relay:open-connection'))
|
168
|
+
relayConnection = await this.components.connectionManager.openConnection(relayPeer, options)
|
169
|
+
} else {
|
170
|
+
options.onProgress?.(new CustomProgressEvent('circuit-relay:reuse-connection'))
|
171
|
+
}
|
172
|
+
|
173
|
+
let stream: Stream | undefined
|
174
|
+
|
175
|
+
try {
|
176
|
+
options.onProgress?.(new CustomProgressEvent('circuit-relay:open-hop-stream'))
|
177
|
+
stream = await relayConnection.newStream(RELAY_V2_HOP_CODEC, options)
|
178
|
+
|
179
|
+
const hopstr = pbStream(stream).pb(HopMessage)
|
180
|
+
|
181
|
+
options.onProgress?.(new CustomProgressEvent('circuit-relay:write-connect-message'))
|
182
|
+
await hopstr.write({
|
183
|
+
type: HopMessage.Type.CONNECT,
|
184
|
+
peer: {
|
185
|
+
id: destinationPeer.toMultihash().bytes,
|
186
|
+
addrs: [multiaddr(destinationAddr).bytes]
|
187
|
+
}
|
188
|
+
}, options)
|
189
|
+
|
190
|
+
options.onProgress?.(new CustomProgressEvent('circuit-relay:read-connect-response'))
|
191
|
+
const status = await hopstr.read(options)
|
192
|
+
|
193
|
+
if (status.status !== Status.OK) {
|
194
|
+
throw new InvalidMessageError(`failed to connect via relay with status ${status?.status?.toString() ?? 'undefined'}`)
|
195
|
+
}
|
196
|
+
|
197
|
+
const limits = new LimitTracker(status.limit)
|
198
|
+
|
199
|
+
const maConn = streamToMaConnection({
|
200
|
+
stream: hopstr.unwrap().unwrap(),
|
201
|
+
remoteAddr: ma,
|
202
|
+
localAddr: relayAddr.encapsulate(`/p2p-circuit/p2p/${this.components.peerId.toString()}`),
|
203
|
+
onDataRead: limits.onData,
|
204
|
+
onDataWrite: limits.onData,
|
205
|
+
log: stream.log.newScope('circuit-relay:connection')
|
206
|
+
})
|
207
|
+
|
208
|
+
const conn = await this.components.upgrader.upgradeOutbound(maConn, {
|
209
|
+
...options,
|
210
|
+
limits: limits.getLimits()
|
211
|
+
})
|
212
|
+
|
213
|
+
conn.log('outbound relayed connection established to %p with limits %o, over connection %s', conn.remotePeer, status.limit ?? 'none', relayConnection.id)
|
214
|
+
|
215
|
+
return conn
|
216
|
+
} catch (err: any) {
|
217
|
+
this.log.error('circuit relay dial to destination %p via relay %p failed', destinationPeer, relayPeer, err)
|
218
|
+
stream?.abort(err)
|
219
|
+
|
220
|
+
throw err
|
221
|
+
}
|
222
|
+
}
|
26
223
|
|
27
224
|
/**
|
28
|
-
*
|
29
|
-
* once - each inbound relayed connection uses a STOP stream
|
30
|
-
*
|
31
|
-
* @default 300
|
225
|
+
* Create a listener
|
32
226
|
*/
|
33
|
-
|
227
|
+
createListener (options: CreateListenerOptions): Listener {
|
228
|
+
return createListener({
|
229
|
+
peerId: this.components.peerId,
|
230
|
+
connectionManager: this.components.connectionManager,
|
231
|
+
addressManager: this.components.addressManager,
|
232
|
+
reservationStore: this.reservationStore,
|
233
|
+
logger: this.components.logger
|
234
|
+
})
|
235
|
+
}
|
34
236
|
|
35
237
|
/**
|
36
|
-
*
|
37
|
-
* at once. If this transport is used along with the relay server these
|
38
|
-
* settings should be set to the same value
|
39
|
-
*
|
40
|
-
* @default 300
|
238
|
+
* Filter check for all Multiaddrs that this transport can listen on
|
41
239
|
*/
|
42
|
-
|
240
|
+
listenFilter (multiaddrs: Multiaddr[]): Multiaddr[] {
|
241
|
+
multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs]
|
242
|
+
|
243
|
+
return multiaddrs.filter((ma) => {
|
244
|
+
return CircuitListen.exactMatch(ma) || CircuitSearch.exactMatch(ma)
|
245
|
+
})
|
246
|
+
}
|
43
247
|
|
44
248
|
/**
|
45
|
-
*
|
46
|
-
* relay) must finish the initial protocol negotiation within this timeout in
|
47
|
-
* ms
|
48
|
-
*
|
49
|
-
* @deprecated Configure `connectionManager.inboundUpgradeTimeout` instead
|
249
|
+
* Filter check for all Multiaddrs that this transport can dial
|
50
250
|
*/
|
51
|
-
|
251
|
+
dialFilter (multiaddrs: Multiaddr[]): Multiaddr[] {
|
252
|
+
multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs]
|
253
|
+
|
254
|
+
return multiaddrs.filter((ma) => {
|
255
|
+
return Circuit.exactMatch(ma)
|
256
|
+
})
|
257
|
+
}
|
52
258
|
|
53
259
|
/**
|
54
|
-
*
|
55
|
-
*
|
56
|
-
* @default 10_000
|
260
|
+
* An incoming STOP request means a remote peer wants to dial us via a relay
|
57
261
|
*/
|
58
|
-
|
59
|
-
|
262
|
+
async onStop (stream: Stream, connection: Connection): Promise<void> {
|
263
|
+
const signal = this.components.upgrader.createInboundAbortSignal(this.shutdownController.signal)
|
264
|
+
|
265
|
+
try {
|
266
|
+
if (!this.reservationStore.hasReservation(connection.remotePeer)) {
|
267
|
+
try {
|
268
|
+
this.log('dialed via relay we did not have a reservation on, start listening on that relay address')
|
269
|
+
await this.components.transportManager.listen([connection.remoteAddr.encapsulate('/p2p-circuit')])
|
270
|
+
} catch (err: any) {
|
271
|
+
// failed to refresh our hitherto unknown relay reservation but allow the connection attempt anyway
|
272
|
+
this.log.error('failed to listen on a relay peer we were dialed via but did not have a reservation on', err)
|
273
|
+
}
|
274
|
+
}
|
275
|
+
|
276
|
+
const stopStream = pbStream(stream).pb(StopMessage)
|
277
|
+
const request = await stopStream.read({
|
278
|
+
signal
|
279
|
+
})
|
280
|
+
|
281
|
+
this.log('new circuit relay v2 stop stream from %p with type %s', connection.remotePeer, request.type)
|
282
|
+
|
283
|
+
if (request?.type === undefined) {
|
284
|
+
this.log.error('type was missing from circuit v2 stop protocol request from %s', connection.remotePeer)
|
285
|
+
await stopStream.write({ type: StopMessage.Type.STATUS, status: Status.MALFORMED_MESSAGE }, {
|
286
|
+
signal
|
287
|
+
})
|
288
|
+
await stream.close({
|
289
|
+
signal
|
290
|
+
})
|
291
|
+
return
|
292
|
+
}
|
293
|
+
|
294
|
+
// Validate the STOP request has the required input
|
295
|
+
if (request.type !== StopMessage.Type.CONNECT) {
|
296
|
+
this.log.error('invalid stop connect request via peer %p', connection.remotePeer)
|
297
|
+
await stopStream.write({ type: StopMessage.Type.STATUS, status: Status.UNEXPECTED_MESSAGE }, {
|
298
|
+
signal
|
299
|
+
})
|
300
|
+
await stream.close({
|
301
|
+
signal
|
302
|
+
})
|
303
|
+
return
|
304
|
+
}
|
305
|
+
|
306
|
+
if (!isValidStop(request)) {
|
307
|
+
this.log.error('invalid stop connect request via peer %p', connection.remotePeer)
|
308
|
+
await stopStream.write({ type: StopMessage.Type.STATUS, status: Status.MALFORMED_MESSAGE }, {
|
309
|
+
signal
|
310
|
+
})
|
311
|
+
await stream.close({
|
312
|
+
signal
|
313
|
+
})
|
314
|
+
return
|
315
|
+
}
|
316
|
+
|
317
|
+
const remotePeerId = peerIdFromMultihash(Digest.decode(request.peer.id))
|
318
|
+
|
319
|
+
if ((await this.components.connectionGater.denyInboundRelayedConnection?.(connection.remotePeer, remotePeerId)) === true) {
|
320
|
+
this.log.error('connection gater denied inbound relayed connection from %p', connection.remotePeer)
|
321
|
+
await stopStream.write({ type: StopMessage.Type.STATUS, status: Status.PERMISSION_DENIED }, {
|
322
|
+
signal
|
323
|
+
})
|
324
|
+
await stream.close({
|
325
|
+
signal
|
326
|
+
})
|
327
|
+
return
|
328
|
+
}
|
329
|
+
|
330
|
+
this.log.trace('sending success response to %p', connection.remotePeer)
|
331
|
+
await stopStream.write({ type: StopMessage.Type.STATUS, status: Status.OK }, {
|
332
|
+
signal
|
333
|
+
})
|
334
|
+
|
335
|
+
const limits = new LimitTracker(request.limit)
|
336
|
+
const remoteAddr = connection.remoteAddr.encapsulate(`/p2p-circuit/p2p/${remotePeerId.toString()}`)
|
337
|
+
const localAddr = this.components.addressManager.getAddresses()[0]
|
338
|
+
const maConn = streamToMaConnection({
|
339
|
+
stream: stopStream.unwrap().unwrap(),
|
340
|
+
remoteAddr,
|
341
|
+
localAddr,
|
342
|
+
onDataRead: limits.onData,
|
343
|
+
onDataWrite: limits.onData,
|
344
|
+
log: stream.log.newScope('circuit-relay:connection')
|
345
|
+
})
|
346
|
+
|
347
|
+
await this.components.upgrader.upgradeInbound(maConn, {
|
348
|
+
limits: limits.getLimits(),
|
349
|
+
signal
|
350
|
+
})
|
60
351
|
|
61
|
-
|
62
|
-
|
63
|
-
|
352
|
+
maConn.log('inbound relayed connection established to %p with limits %o, over connection %s', remotePeerId, request.limit ?? 'none', connection.id)
|
353
|
+
} finally {
|
354
|
+
signal?.clear()
|
355
|
+
}
|
64
356
|
}
|
65
357
|
}
|
@@ -1,20 +1,19 @@
|
|
1
1
|
import { ListenError } from '@libp2p/interface'
|
2
2
|
import { PeerMap } from '@libp2p/peer-collections'
|
3
|
-
import { createScalableCuckooFilter } from '@libp2p/utils
|
4
|
-
import {
|
5
|
-
import { multiaddr } from '@multiformats/multiaddr'
|
3
|
+
import { createScalableCuckooFilter, PeerQueue, pbStream } from '@libp2p/utils'
|
4
|
+
import { CODE_P2P, multiaddr } from '@multiformats/multiaddr'
|
6
5
|
import { Circuit } from '@multiformats/multiaddr-matcher'
|
7
|
-
import { pbStream } from 'it-protobuf-stream'
|
8
6
|
import { TypedEventEmitter, setMaxListeners } from 'main-event'
|
9
7
|
import { nanoid } from 'nanoid'
|
10
8
|
import { DEFAULT_MAX_RESERVATION_QUEUE_LENGTH, DEFAULT_RESERVATION_COMPLETION_TIMEOUT, DEFAULT_RESERVATION_CONCURRENCY, KEEP_ALIVE_TAG, RELAY_V2_HOP_CODEC } from '../constants.js'
|
11
9
|
import { DoubleRelayError, HadEnoughRelaysError, RelayQueueFullError } from '../errors.js'
|
12
10
|
import { HopMessage, Status } from '../pb/index.js'
|
13
11
|
import { getExpirationMilliseconds } from '../utils.js'
|
12
|
+
import type { TransportReservationStoreComponents, TransportReservationStoreInit } from '../index.ts'
|
14
13
|
import type { Reservation } from '../pb/index.js'
|
15
|
-
import type { AbortOptions, Libp2pEvents,
|
14
|
+
import type { AbortOptions, Libp2pEvents, Logger, PeerId, PeerStore, Startable, Peer, Connection } from '@libp2p/interface'
|
16
15
|
import type { ConnectionManager } from '@libp2p/interface-internal'
|
17
|
-
import type { Filter } from '@libp2p/utils
|
16
|
+
import type { Filter } from '@libp2p/utils'
|
18
17
|
import type { TypedEventTarget } from 'main-event'
|
19
18
|
|
20
19
|
// allow refreshing a relay reservation if it will expire in the next 10 minutes
|
@@ -26,43 +25,6 @@ const REFRESH_TIMEOUT = (60 * 1000) * 5
|
|
26
25
|
// minimum duration before which a reservation must not be refreshed
|
27
26
|
const REFRESH_TIMEOUT_MIN = 30 * 1000
|
28
27
|
|
29
|
-
export interface ReservationStoreComponents {
|
30
|
-
peerId: PeerId
|
31
|
-
connectionManager: ConnectionManager
|
32
|
-
peerStore: PeerStore
|
33
|
-
events: TypedEventTarget<Libp2pEvents>
|
34
|
-
logger: ComponentLogger
|
35
|
-
metrics?: Metrics
|
36
|
-
}
|
37
|
-
|
38
|
-
export interface ReservationStoreInit {
|
39
|
-
/**
|
40
|
-
* Multiple relays may be discovered simultaneously - to prevent listening
|
41
|
-
* on too many relays, this value controls how many to attempt to reserve a
|
42
|
-
* slot on at once. If set to more than one, we may end up listening on
|
43
|
-
* more relays than the `maxReservations` value, but on networks with poor
|
44
|
-
* connectivity the user may wish to attempt to reserve on multiple relays
|
45
|
-
* simultaneously.
|
46
|
-
*
|
47
|
-
* @default 1
|
48
|
-
*/
|
49
|
-
reservationConcurrency?: number
|
50
|
-
|
51
|
-
/**
|
52
|
-
* Limit the number of potential relays we will dial
|
53
|
-
*
|
54
|
-
* @default 100
|
55
|
-
*/
|
56
|
-
maxReservationQueueLength?: number
|
57
|
-
|
58
|
-
/**
|
59
|
-
* When creating a reservation it must complete within this number of ms
|
60
|
-
*
|
61
|
-
* @default 5000
|
62
|
-
*/
|
63
|
-
reservationCompletionTimeout?: number
|
64
|
-
}
|
65
|
-
|
66
28
|
export type RelayType = 'discovered' | 'configured'
|
67
29
|
|
68
30
|
export interface DiscoveredRelayEntry {
|
@@ -120,7 +82,7 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
|
|
120
82
|
private readonly log: Logger
|
121
83
|
private relayFilter: Filter
|
122
84
|
|
123
|
-
constructor (components:
|
85
|
+
constructor (components: TransportReservationStoreComponents, init?: TransportReservationStoreInit) {
|
124
86
|
super()
|
125
87
|
|
126
88
|
this.log = components.logger.forComponent('libp2p:circuit-relay:transport:reservation-store')
|
@@ -453,7 +415,7 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
|
|
453
415
|
}
|
454
416
|
}
|
455
417
|
|
456
|
-
this.log.trace('read response %
|
418
|
+
this.log.trace('read response %s', response.status)
|
457
419
|
|
458
420
|
if (response.status === Status.OK && response.reservation != null) {
|
459
421
|
// check that the returned relay has the relay address - this can be
|
@@ -465,7 +427,7 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
|
|
465
427
|
for (const buf of response.reservation.addrs) {
|
466
428
|
let ma = multiaddr(buf)
|
467
429
|
|
468
|
-
if (ma.
|
430
|
+
if (ma.getComponents().find(c => c.code === CODE_P2P) == null) {
|
469
431
|
ma = ma.encapsulate(`/p2p/${connection.remotePeer}`)
|
470
432
|
}
|
471
433
|
|
@@ -0,0 +1,91 @@
|
|
1
|
+
import { AbstractMultiaddrConnection } from '@libp2p/utils'
|
2
|
+
import { Uint8ArrayList } from 'uint8arraylist'
|
3
|
+
import type { AbortOptions, MultiaddrConnection, Stream } from '@libp2p/interface'
|
4
|
+
import type { AbstractMultiaddrConnectionInit, SendResult } from '@libp2p/utils'
|
5
|
+
|
6
|
+
export interface StreamMultiaddrConnectionInit extends Omit<AbstractMultiaddrConnectionInit, 'direction'> {
|
7
|
+
stream: Stream
|
8
|
+
|
9
|
+
/**
|
10
|
+
* A callback invoked when data is read from the stream
|
11
|
+
*/
|
12
|
+
onDataRead?(buf: Uint8ArrayList | Uint8Array): void
|
13
|
+
|
14
|
+
/**
|
15
|
+
* A callback invoked when data is written to the stream
|
16
|
+
*/
|
17
|
+
onDataWrite?(buf: Uint8ArrayList | Uint8Array): void
|
18
|
+
}
|
19
|
+
|
20
|
+
class StreamMultiaddrConnection extends AbstractMultiaddrConnection {
|
21
|
+
private stream: Stream
|
22
|
+
private init: StreamMultiaddrConnectionInit
|
23
|
+
|
24
|
+
constructor (init: StreamMultiaddrConnectionInit) {
|
25
|
+
super({
|
26
|
+
...init,
|
27
|
+
direction: init.stream.direction
|
28
|
+
|
29
|
+
})
|
30
|
+
|
31
|
+
this.init = init
|
32
|
+
this.stream = init.stream
|
33
|
+
|
34
|
+
this.stream.addEventListener('close', (evt) => {
|
35
|
+
this.onTransportClosed(evt.error)
|
36
|
+
})
|
37
|
+
|
38
|
+
this.stream.addEventListener('remoteCloseWrite', (evt) => {
|
39
|
+
this.onRemoteCloseWrite()
|
40
|
+
|
41
|
+
// close our end when the remote closes
|
42
|
+
this.close()
|
43
|
+
.catch(err => {
|
44
|
+
this.abort(err)
|
45
|
+
})
|
46
|
+
})
|
47
|
+
|
48
|
+
// count incoming bytes
|
49
|
+
this.stream.addEventListener('message', (evt) => {
|
50
|
+
init.onDataRead?.(evt.data)
|
51
|
+
this.onData(evt.data)
|
52
|
+
})
|
53
|
+
|
54
|
+
// forward drain events
|
55
|
+
this.stream.addEventListener('drain', () => {
|
56
|
+
this.safeDispatchEvent('drain')
|
57
|
+
})
|
58
|
+
}
|
59
|
+
|
60
|
+
sendData (data: Uint8ArrayList): SendResult {
|
61
|
+
this.init.onDataWrite?.(data)
|
62
|
+
|
63
|
+
return {
|
64
|
+
sentBytes: data.byteLength,
|
65
|
+
canSendMore: this.stream.send(data)
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
async sendClose (options?: AbortOptions): Promise<void> {
|
70
|
+
await this.stream.close(options)
|
71
|
+
}
|
72
|
+
|
73
|
+
sendReset (): void {
|
74
|
+
this.stream.abort(new Error('An error occurred'))
|
75
|
+
}
|
76
|
+
|
77
|
+
sendPause (): void {
|
78
|
+
this.stream.pause()
|
79
|
+
}
|
80
|
+
|
81
|
+
sendResume (): void {
|
82
|
+
this.stream.resume()
|
83
|
+
}
|
84
|
+
}
|
85
|
+
|
86
|
+
/**
|
87
|
+
* Convert a Stream into a MultiaddrConnection.
|
88
|
+
*/
|
89
|
+
export function streamToMaConnection (init: StreamMultiaddrConnectionInit): MultiaddrConnection {
|
90
|
+
return new StreamMultiaddrConnection(init)
|
91
|
+
}
|