@libp2p/circuit-relay-v2 2.1.3 → 2.1.4-5d199f9b6
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 +2 -2
- package/dist/src/constants.d.ts +4 -6
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +5 -6
- package/dist/src/constants.js.map +1 -1
- package/dist/src/index.d.ts +18 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/pb/index.d.ts +12 -1
- package/dist/src/pb/index.d.ts.map +1 -1
- package/dist/src/pb/index.js +78 -2
- package/dist/src/pb/index.js.map +1 -1
- package/dist/src/server/index.d.ts.map +1 -1
- package/dist/src/server/index.js +71 -60
- package/dist/src/server/index.js.map +1 -1
- package/dist/src/server/reservation-store.d.ts +5 -9
- package/dist/src/server/reservation-store.d.ts.map +1 -1
- package/dist/src/server/reservation-store.js +32 -33
- package/dist/src/server/reservation-store.js.map +1 -1
- package/dist/src/server/reservation-voucher.d.ts +1 -1
- package/dist/src/transport/discovery.js +1 -1
- package/dist/src/transport/discovery.js.map +1 -1
- package/dist/src/transport/index.d.ts +2 -2
- package/dist/src/transport/index.d.ts.map +1 -1
- package/dist/src/transport/listener.d.ts.map +1 -1
- package/dist/src/transport/listener.js +23 -22
- package/dist/src/transport/listener.js.map +1 -1
- package/dist/src/transport/reservation-store.d.ts +4 -3
- package/dist/src/transport/reservation-store.d.ts.map +1 -1
- package/dist/src/transport/reservation-store.js +111 -47
- package/dist/src/transport/reservation-store.js.map +1 -1
- package/dist/src/transport/transport.d.ts.map +1 -1
- package/dist/src/transport/transport.js +11 -8
- package/dist/src/transport/transport.js.map +1 -1
- package/dist/src/utils.d.ts +7 -1
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +19 -8
- package/dist/src/utils.js.map +1 -1
- package/package.json +12 -12
- package/src/constants.ts +7 -7
- package/src/index.ts +21 -3
- package/src/pb/index.proto +20 -1
- package/src/pb/index.ts +99 -3
- package/src/server/index.ts +75 -64
- package/src/server/reservation-store.ts +38 -42
- package/src/server/reservation-voucher.ts +2 -2
- package/src/transport/discovery.ts +1 -1
- package/src/transport/index.ts +2 -2
- package/src/transport/listener.ts +28 -25
- package/src/transport/reservation-store.ts +147 -57
- package/src/transport/transport.ts +12 -8
- package/src/utils.ts +21 -8
- package/dist/typedoc-urls.json +0 -23
package/src/server/index.ts
CHANGED
@@ -1,13 +1,14 @@
|
|
1
|
+
import { publicKeyToProtobuf } from '@libp2p/crypto/keys'
|
1
2
|
import { TypedEventEmitter, setMaxListeners } from '@libp2p/interface'
|
2
3
|
import { peerIdFromMultihash } from '@libp2p/peer-id'
|
3
4
|
import { RecordEnvelope } from '@libp2p/peer-record'
|
4
5
|
import { type Multiaddr, multiaddr } from '@multiformats/multiaddr'
|
5
6
|
import { pbStream, type ProtobufStream } from 'it-protobuf-stream'
|
6
7
|
import * as Digest from 'multiformats/hashes/digest'
|
7
|
-
import pDefer from 'p-defer'
|
8
8
|
import {
|
9
9
|
CIRCUIT_PROTO_CODE,
|
10
10
|
DEFAULT_HOP_TIMEOUT,
|
11
|
+
KEEP_ALIVE_SOURCE_TAG,
|
11
12
|
MAX_CONNECTIONS,
|
12
13
|
RELAY_SOURCE_TAG,
|
13
14
|
RELAY_V2_HOP_CODEC,
|
@@ -18,7 +19,7 @@ import { createLimitedRelay } from '../utils.js'
|
|
18
19
|
import { ReservationStore, type ReservationStoreInit } from './reservation-store.js'
|
19
20
|
import { ReservationVoucherRecord } from './reservation-voucher.js'
|
20
21
|
import type { CircuitRelayService, RelayReservation } from '../index.js'
|
21
|
-
import type { ComponentLogger, Logger, Connection, Stream, ConnectionGater, PeerId, PeerStore, Startable, PrivateKey, Metrics } from '@libp2p/interface'
|
22
|
+
import type { ComponentLogger, Logger, Connection, Stream, ConnectionGater, PeerId, PeerStore, Startable, PrivateKey, Metrics, AbortOptions } from '@libp2p/interface'
|
22
23
|
import type { AddressManager, ConnectionManager, IncomingStreamData, Registrar } from '@libp2p/interface-internal'
|
23
24
|
import type { PeerMap } from '@libp2p/peer-collections'
|
24
25
|
|
@@ -156,8 +157,6 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
|
|
156
157
|
runOnLimitedConnection: true
|
157
158
|
})
|
158
159
|
|
159
|
-
this.reservationStore.start()
|
160
|
-
|
161
160
|
this.started = true
|
162
161
|
}
|
163
162
|
|
@@ -165,7 +164,7 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
|
|
165
164
|
* Stop Relay service
|
166
165
|
*/
|
167
166
|
async stop (): Promise<void> {
|
168
|
-
this.reservationStore.
|
167
|
+
this.reservationStore.clear()
|
169
168
|
this.shutdownController.abort()
|
170
169
|
await this.registrar.unhandle(RELAY_V2_HOP_CODEC)
|
171
170
|
|
@@ -175,17 +174,13 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
|
|
175
174
|
async onHop ({ connection, stream }: IncomingStreamData): Promise<void> {
|
176
175
|
this.log('received circuit v2 hop protocol stream from %p', connection.remotePeer)
|
177
176
|
|
178
|
-
const
|
179
|
-
|
180
|
-
|
181
|
-
}, this.hopTimeout)
|
177
|
+
const options = {
|
178
|
+
signal: AbortSignal.timeout(this.hopTimeout)
|
179
|
+
}
|
182
180
|
const pbstr = pbStream(stream)
|
183
181
|
|
184
182
|
try {
|
185
|
-
const request: HopMessage = await
|
186
|
-
pbstr.pb(HopMessage).read(),
|
187
|
-
hopTimeoutPromise.promise
|
188
|
-
])
|
183
|
+
const request: HopMessage = await pbstr.pb(HopMessage).read(options)
|
189
184
|
|
190
185
|
if (request?.type == null) {
|
191
186
|
throw new Error('request was invalid, could not read from stream')
|
@@ -193,31 +188,26 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
|
|
193
188
|
|
194
189
|
this.log('received', request.type)
|
195
190
|
|
196
|
-
await
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
}),
|
202
|
-
hopTimeoutPromise.promise
|
203
|
-
])
|
191
|
+
await this.handleHopProtocol({
|
192
|
+
connection,
|
193
|
+
stream: pbstr,
|
194
|
+
request
|
195
|
+
}, options)
|
204
196
|
} catch (err: any) {
|
205
197
|
this.log.error('error while handling hop', err)
|
206
198
|
await pbstr.pb(HopMessage).write({
|
207
199
|
type: HopMessage.Type.STATUS,
|
208
200
|
status: Status.MALFORMED_MESSAGE
|
209
|
-
})
|
201
|
+
}, options)
|
210
202
|
stream.abort(err)
|
211
|
-
} finally {
|
212
|
-
clearTimeout(timeout)
|
213
203
|
}
|
214
204
|
}
|
215
205
|
|
216
|
-
async handleHopProtocol ({ stream, request, connection }: HopProtocolOptions): Promise<void> {
|
206
|
+
async handleHopProtocol ({ stream, request, connection }: HopProtocolOptions, options: AbortOptions): Promise<void> {
|
217
207
|
this.log('received hop message')
|
218
208
|
switch (request.type) {
|
219
|
-
case HopMessage.Type.RESERVE: await this.handleReserve({ stream, request, connection }); break
|
220
|
-
case HopMessage.Type.CONNECT: await this.handleConnect({ stream, request, connection }); break
|
209
|
+
case HopMessage.Type.RESERVE: await this.handleReserve({ stream, request, connection }, options); break
|
210
|
+
case HopMessage.Type.CONNECT: await this.handleConnect({ stream, request, connection }, options); break
|
221
211
|
default: {
|
222
212
|
this.log.error('invalid hop request type %s via peer %p', request.type, connection.remotePeer)
|
223
213
|
await stream.pb(HopMessage).write({ type: HopMessage.Type.STATUS, status: Status.UNEXPECTED_MESSAGE })
|
@@ -225,37 +215,38 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
|
|
225
215
|
}
|
226
216
|
}
|
227
217
|
|
228
|
-
async handleReserve ({ stream,
|
218
|
+
async handleReserve ({ stream, connection }: HopProtocolOptions, options: AbortOptions): Promise<void> {
|
229
219
|
const hopstr = stream.pb(HopMessage)
|
230
220
|
this.log('hop reserve request from %p', connection.remotePeer)
|
231
221
|
|
232
222
|
if (isRelayAddr(connection.remoteAddr)) {
|
233
223
|
this.log.error('relay reservation over circuit connection denied for peer: %p', connection.remotePeer)
|
234
|
-
await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED })
|
224
|
+
await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED }, options)
|
235
225
|
return
|
236
226
|
}
|
237
227
|
|
238
228
|
if ((await this.connectionGater.denyInboundRelayReservation?.(connection.remotePeer)) === true) {
|
239
229
|
this.log.error('reservation for %p denied by connection gater', connection.remotePeer)
|
240
|
-
await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED })
|
230
|
+
await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED }, options)
|
241
231
|
return
|
242
232
|
}
|
243
233
|
|
244
234
|
const result = this.reservationStore.reserve(connection.remotePeer, connection.remoteAddr)
|
245
235
|
|
246
|
-
if (result.status !== Status.OK) {
|
247
|
-
await hopstr.write({ type: HopMessage.Type.STATUS, status: result.status })
|
248
|
-
return
|
249
|
-
}
|
250
|
-
|
251
236
|
try {
|
237
|
+
if (result.status !== Status.OK) {
|
238
|
+
await hopstr.write({ type: HopMessage.Type.STATUS, status: result.status }, options)
|
239
|
+
return
|
240
|
+
}
|
241
|
+
|
252
242
|
// tag relay target peer
|
253
243
|
// result.expire is non-null if `ReservationStore.reserve` returns with status == OK
|
254
244
|
if (result.expire != null) {
|
255
245
|
const ttl = (result.expire * 1000) - Date.now()
|
256
246
|
await this.peerStore.merge(connection.remotePeer, {
|
257
247
|
tags: {
|
258
|
-
[RELAY_SOURCE_TAG]: { value: 1, ttl }
|
248
|
+
[RELAY_SOURCE_TAG]: { value: 1, ttl },
|
249
|
+
[KEEP_ALIVE_SOURCE_TAG]: { value: 1, ttl }
|
259
250
|
}
|
260
251
|
})
|
261
252
|
}
|
@@ -265,11 +256,22 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
|
|
265
256
|
status: Status.OK,
|
266
257
|
reservation: await this.makeReservation(connection.remotePeer, BigInt(result.expire ?? 0)),
|
267
258
|
limit: this.reservationStore.get(connection.remotePeer)?.limit
|
268
|
-
})
|
259
|
+
}, options)
|
269
260
|
this.log('sent confirmation response to %s', connection.remotePeer)
|
270
261
|
} catch (err) {
|
271
|
-
this.log.error('failed to send confirmation response to %p', connection.remotePeer, err)
|
262
|
+
this.log.error('failed to send confirmation response to %p - %e', connection.remotePeer, err)
|
272
263
|
this.reservationStore.removeReservation(connection.remotePeer)
|
264
|
+
|
265
|
+
try {
|
266
|
+
await this.peerStore.merge(connection.remotePeer, {
|
267
|
+
tags: {
|
268
|
+
[RELAY_SOURCE_TAG]: undefined,
|
269
|
+
[KEEP_ALIVE_SOURCE_TAG]: undefined
|
270
|
+
}
|
271
|
+
})
|
272
|
+
} catch (err) {
|
273
|
+
this.log.error('failed to untag relay source peer %p - %e', connection.remotePeer, err)
|
274
|
+
}
|
273
275
|
}
|
274
276
|
}
|
275
277
|
|
@@ -287,25 +289,34 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
|
|
287
289
|
addrs.push(relayAddr.bytes)
|
288
290
|
}
|
289
291
|
|
290
|
-
const
|
292
|
+
const envelope = await RecordEnvelope.seal(new ReservationVoucherRecord({
|
291
293
|
peer: remotePeer,
|
292
294
|
relay: this.peerId,
|
293
|
-
expiration:
|
295
|
+
expiration: expire
|
294
296
|
}), this.privateKey)
|
295
297
|
|
296
298
|
return {
|
297
299
|
addrs,
|
298
300
|
expire,
|
299
|
-
voucher:
|
301
|
+
voucher: {
|
302
|
+
publicKey: publicKeyToProtobuf(envelope.publicKey),
|
303
|
+
payloadType: envelope.payloadType,
|
304
|
+
payload: {
|
305
|
+
peer: remotePeer.toMultihash().bytes,
|
306
|
+
relay: this.peerId.toMultihash().bytes,
|
307
|
+
expiration: expire
|
308
|
+
},
|
309
|
+
signature: envelope.signature
|
310
|
+
}
|
300
311
|
}
|
301
312
|
}
|
302
313
|
|
303
|
-
async handleConnect ({ stream, request, connection }: HopProtocolOptions): Promise<void> {
|
314
|
+
async handleConnect ({ stream, request, connection }: HopProtocolOptions, options: AbortOptions): Promise<void> {
|
304
315
|
const hopstr = stream.pb(HopMessage)
|
305
316
|
|
306
317
|
if (isRelayAddr(connection.remoteAddr)) {
|
307
318
|
this.log.error('relay reservation over circuit connection denied for peer: %p', connection.remotePeer)
|
308
|
-
await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED })
|
319
|
+
await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED }, options)
|
309
320
|
return
|
310
321
|
}
|
311
322
|
|
@@ -323,19 +334,21 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
|
|
323
334
|
dstPeer = peerIdFromMultihash(Digest.decode(request.peer.id))
|
324
335
|
} catch (err) {
|
325
336
|
this.log.error('invalid hop connect request via peer %p %s', connection.remotePeer, err)
|
326
|
-
await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.MALFORMED_MESSAGE })
|
337
|
+
await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.MALFORMED_MESSAGE }, options)
|
327
338
|
return
|
328
339
|
}
|
329
340
|
|
330
|
-
|
341
|
+
const reservation = this.reservationStore.get(dstPeer)
|
342
|
+
|
343
|
+
if (reservation == null) {
|
331
344
|
this.log.error('hop connect denied for destination peer %p not having a reservation for %p with status %s', dstPeer, connection.remotePeer, Status.NO_RESERVATION)
|
332
|
-
await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.NO_RESERVATION })
|
345
|
+
await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.NO_RESERVATION }, options)
|
333
346
|
return
|
334
347
|
}
|
335
348
|
|
336
349
|
if ((await this.connectionGater.denyOutboundRelayedConnection?.(connection.remotePeer, dstPeer)) === true) {
|
337
350
|
this.log.error('hop connect for %p to %p denied by connection gater', connection.remotePeer, dstPeer)
|
338
|
-
await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED })
|
351
|
+
await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.PERMISSION_DENIED }, options)
|
339
352
|
return
|
340
353
|
}
|
341
354
|
|
@@ -343,11 +356,10 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
|
|
343
356
|
|
344
357
|
if (connections.length === 0) {
|
345
358
|
this.log('hop connect denied for destination peer %p not having a connection for %p as there is no destination connection', dstPeer, connection.remotePeer)
|
346
|
-
await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.NO_RESERVATION })
|
359
|
+
await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.NO_RESERVATION }, options)
|
347
360
|
return
|
348
361
|
}
|
349
362
|
|
350
|
-
const limit = this.reservationStore.get(dstPeer)?.limit
|
351
363
|
const destinationConnection = connections[0]
|
352
364
|
|
353
365
|
const destinationStream = await this.stopHop({
|
@@ -358,26 +370,27 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
|
|
358
370
|
id: connection.remotePeer.toMultihash().bytes,
|
359
371
|
addrs: []
|
360
372
|
},
|
361
|
-
limit
|
373
|
+
limit: reservation?.limit
|
362
374
|
}
|
363
|
-
})
|
375
|
+
}, options)
|
364
376
|
|
365
377
|
if (destinationStream == null) {
|
366
378
|
this.log.error('failed to open stream to destination peer %p', destinationConnection?.remotePeer)
|
367
|
-
await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.CONNECTION_FAILED })
|
379
|
+
await hopstr.write({ type: HopMessage.Type.STATUS, status: Status.CONNECTION_FAILED }, options)
|
368
380
|
return
|
369
381
|
}
|
370
382
|
|
371
383
|
await hopstr.write({
|
372
384
|
type: HopMessage.Type.STATUS,
|
373
385
|
status: Status.OK,
|
374
|
-
limit
|
375
|
-
})
|
386
|
+
limit: reservation?.limit
|
387
|
+
}, options)
|
376
388
|
const sourceStream = stream.unwrap()
|
377
389
|
|
378
390
|
this.log('connection from %p to %p established - merging streams', connection.remotePeer, dstPeer)
|
391
|
+
|
379
392
|
// Short circuit the two streams to create the relayed connection
|
380
|
-
createLimitedRelay(sourceStream, destinationStream, this.shutdownController.signal,
|
393
|
+
createLimitedRelay(sourceStream, destinationStream, this.shutdownController.signal, reservation, {
|
381
394
|
log: this.log
|
382
395
|
})
|
383
396
|
}
|
@@ -385,29 +398,27 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
|
|
385
398
|
/**
|
386
399
|
* Send a STOP request to the target peer that the dialing peer wants to contact
|
387
400
|
*/
|
388
|
-
async stopHop ({
|
389
|
-
connection,
|
390
|
-
request
|
391
|
-
}: StopOptions): Promise<Stream | undefined> {
|
401
|
+
async stopHop ({ connection, request }: StopOptions, options: AbortOptions): Promise<Stream | undefined> {
|
392
402
|
this.log('starting circuit relay v2 stop request to %s', connection.remotePeer)
|
393
403
|
const stream = await connection.newStream([RELAY_V2_STOP_CODEC], {
|
394
404
|
maxOutboundStreams: this.maxOutboundStopStreams,
|
395
|
-
runOnLimitedConnection: true
|
405
|
+
runOnLimitedConnection: true,
|
406
|
+
...options
|
396
407
|
})
|
397
408
|
const pbstr = pbStream(stream)
|
398
409
|
const stopstr = pbstr.pb(StopMessage)
|
399
|
-
await stopstr.write(request)
|
410
|
+
await stopstr.write(request, options)
|
400
411
|
let response
|
401
412
|
|
402
413
|
try {
|
403
|
-
response = await stopstr.read()
|
414
|
+
response = await stopstr.read(options)
|
404
415
|
} catch (err) {
|
405
416
|
this.log.error('error parsing stop message response from %p', connection.remotePeer)
|
406
417
|
}
|
407
418
|
|
408
419
|
if (response == null) {
|
409
420
|
this.log.error('could not read response from %p', connection.remotePeer)
|
410
|
-
await stream.close()
|
421
|
+
await stream.close(options)
|
411
422
|
return
|
412
423
|
}
|
413
424
|
|
@@ -417,7 +428,7 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> implements
|
|
417
428
|
}
|
418
429
|
|
419
430
|
this.log('stop request failed with code %d', response.status)
|
420
|
-
await stream.close()
|
431
|
+
await stream.close(options)
|
421
432
|
}
|
422
433
|
|
423
434
|
get reservations (): PeerMap<RelayReservation> {
|
@@ -1,14 +1,16 @@
|
|
1
1
|
import { trackedPeerMap } from '@libp2p/peer-collections'
|
2
|
-
import {
|
2
|
+
import { retimeableSignal } from 'retimeable-signal'
|
3
|
+
import { DEFAULT_DATA_LIMIT, DEFAULT_DURATION_LIMIT, DEFAULT_MAX_RESERVATION_STORE_SIZE, DEFAULT_MAX_RESERVATION_TTL } from '../constants.js'
|
3
4
|
import { type Limit, Status } from '../pb/index.js'
|
4
5
|
import type { RelayReservation } from '../index.js'
|
5
|
-
import type { Metrics, PeerId
|
6
|
+
import type { ComponentLogger, Logger, Metrics, PeerId } from '@libp2p/interface'
|
6
7
|
import type { PeerMap } from '@libp2p/peer-collections'
|
7
8
|
import type { Multiaddr } from '@multiformats/multiaddr'
|
8
9
|
|
9
10
|
export type ReservationStatus = Status.OK | Status.PERMISSION_DENIED | Status.RESERVATION_REFUSED
|
10
11
|
|
11
12
|
export interface ReservationStoreComponents {
|
13
|
+
logger: ComponentLogger
|
12
14
|
metrics?: Metrics
|
13
15
|
}
|
14
16
|
|
@@ -52,20 +54,18 @@ export interface ReservationStoreInit {
|
|
52
54
|
defaultDataLimit?: bigint
|
53
55
|
}
|
54
56
|
|
55
|
-
export class ReservationStore
|
57
|
+
export class ReservationStore {
|
56
58
|
public readonly reservations: PeerMap<RelayReservation>
|
57
|
-
private _started = false
|
58
|
-
private interval: any
|
59
59
|
private readonly maxReservations: number
|
60
|
-
private readonly reservationClearInterval: number
|
61
60
|
private readonly applyDefaultLimit: boolean
|
62
61
|
private readonly reservationTtl: number
|
63
62
|
private readonly defaultDurationLimit: number
|
64
63
|
private readonly defaultDataLimit: bigint
|
64
|
+
private readonly log: Logger
|
65
65
|
|
66
66
|
constructor (components: ReservationStoreComponents, init: ReservationStoreInit = {}) {
|
67
|
+
this.log = components.logger.forComponent('libp2p:circuit-relay:server:reservation-store')
|
67
68
|
this.maxReservations = init.maxReservations ?? DEFAULT_MAX_RESERVATION_STORE_SIZE
|
68
|
-
this.reservationClearInterval = init.reservationClearInterval ?? DEFAULT_MAX_RESERVATION_CLEAR_INTERVAL
|
69
69
|
this.applyDefaultLimit = init.applyDefaultLimit !== false
|
70
70
|
this.reservationTtl = init.reservationTtl ?? DEFAULT_MAX_RESERVATION_TTL
|
71
71
|
this.defaultDurationLimit = init.defaultDurationLimit ?? DEFAULT_DURATION_LIMIT
|
@@ -77,59 +77,55 @@ export class ReservationStore implements Startable {
|
|
77
77
|
})
|
78
78
|
}
|
79
79
|
|
80
|
-
isStarted (): boolean {
|
81
|
-
return this._started
|
82
|
-
}
|
83
|
-
|
84
|
-
start (): void {
|
85
|
-
if (this._started) {
|
86
|
-
return
|
87
|
-
}
|
88
|
-
this._started = true
|
89
|
-
this.interval = setInterval(
|
90
|
-
() => {
|
91
|
-
const now = (new Date()).getTime()
|
92
|
-
this.reservations.forEach((r, k) => {
|
93
|
-
if (r.expire.getTime() < now) {
|
94
|
-
this.reservations.delete(k)
|
95
|
-
}
|
96
|
-
})
|
97
|
-
},
|
98
|
-
this.reservationClearInterval
|
99
|
-
)
|
100
|
-
}
|
101
|
-
|
102
|
-
stop (): void {
|
103
|
-
clearInterval(this.interval)
|
104
|
-
}
|
105
|
-
|
106
80
|
reserve (peer: PeerId, addr: Multiaddr, limit?: Limit): { status: ReservationStatus, expire?: number } {
|
107
|
-
|
81
|
+
let reservation = this.reservations.get(peer)
|
82
|
+
|
83
|
+
if (this.reservations.size >= this.maxReservations && reservation == null) {
|
108
84
|
return { status: Status.RESERVATION_REFUSED }
|
109
85
|
}
|
110
86
|
|
111
|
-
const
|
87
|
+
const expiry = new Date(Date.now() + this.reservationTtl)
|
112
88
|
let checkedLimit: Limit | undefined
|
113
89
|
|
114
90
|
if (this.applyDefaultLimit) {
|
115
|
-
checkedLimit = limit ?? {
|
91
|
+
checkedLimit = limit ?? {
|
92
|
+
data: this.defaultDataLimit,
|
93
|
+
duration: this.defaultDurationLimit
|
94
|
+
}
|
116
95
|
}
|
117
96
|
|
118
|
-
|
97
|
+
if (reservation != null) {
|
98
|
+
this.log('refreshing reservation for client %p', peer)
|
99
|
+
reservation.signal.reset(this.reservationTtl)
|
100
|
+
} else {
|
101
|
+
this.log('creating new reservation for client %p', peer)
|
102
|
+
reservation = {
|
103
|
+
addr,
|
104
|
+
expiry,
|
105
|
+
limit: checkedLimit,
|
106
|
+
signal: retimeableSignal(this.reservationTtl)
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
this.reservations.set(peer, reservation)
|
111
|
+
|
112
|
+
reservation.signal.addEventListener('abort', () => {
|
113
|
+
this.reservations.delete(peer)
|
114
|
+
})
|
119
115
|
|
120
116
|
// return expiry time in seconds
|
121
|
-
return { status: Status.OK, expire: Math.round(
|
117
|
+
return { status: Status.OK, expire: Math.round(expiry.getTime() / 1000) }
|
122
118
|
}
|
123
119
|
|
124
120
|
removeReservation (peer: PeerId): void {
|
125
121
|
this.reservations.delete(peer)
|
126
122
|
}
|
127
123
|
|
128
|
-
hasReservation (dst: PeerId): boolean {
|
129
|
-
return this.reservations.has(dst)
|
130
|
-
}
|
131
|
-
|
132
124
|
get (peer: PeerId): RelayReservation | undefined {
|
133
125
|
return this.reservations.get(peer)
|
134
126
|
}
|
127
|
+
|
128
|
+
clear (): void {
|
129
|
+
this.reservations.clear()
|
130
|
+
}
|
135
131
|
}
|
@@ -4,7 +4,7 @@ import type { PeerId, Record } from '@libp2p/interface'
|
|
4
4
|
export interface ReservationVoucherOptions {
|
5
5
|
relay: PeerId
|
6
6
|
peer: PeerId
|
7
|
-
expiration:
|
7
|
+
expiration: bigint
|
8
8
|
}
|
9
9
|
|
10
10
|
export class ReservationVoucherRecord implements Record {
|
@@ -13,7 +13,7 @@ export class ReservationVoucherRecord implements Record {
|
|
13
13
|
|
14
14
|
private readonly relay: PeerId
|
15
15
|
private readonly peer: PeerId
|
16
|
-
private readonly expiration:
|
16
|
+
private readonly expiration: bigint
|
17
17
|
|
18
18
|
constructor ({ relay, peer, expiration }: ReservationVoucherOptions) {
|
19
19
|
this.relay = relay
|
@@ -66,7 +66,7 @@ export class RelayDiscovery extends TypedEventEmitter<RelayDiscoveryEvents> impl
|
|
66
66
|
this.topologyId = await this.registrar.register(RELAY_V2_HOP_CODEC, {
|
67
67
|
filter: this.filter,
|
68
68
|
onConnect: (peerId) => {
|
69
|
-
this.log('discovered relay %p', peerId)
|
69
|
+
this.log.trace('discovered relay %p', peerId)
|
70
70
|
this.safeDispatchEvent('relay:discover', { detail: peerId })
|
71
71
|
}
|
72
72
|
})
|
package/src/transport/index.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import { CircuitRelayTransport } from './transport.js'
|
2
2
|
import type { RelayDiscoveryComponents } from './discovery.js'
|
3
|
-
import type {
|
3
|
+
import type { ReservationStoreInit } from './reservation-store.js'
|
4
4
|
import type { Transport, Upgrader, Libp2pEvents, ConnectionGater, TypedEventTarget, PeerId, TopologyFilter } from '@libp2p/interface'
|
5
5
|
import type { AddressManager, Registrar } from '@libp2p/interface-internal'
|
6
6
|
|
@@ -16,7 +16,7 @@ export interface CircuitRelayTransportComponents extends RelayDiscoveryComponent
|
|
16
16
|
/**
|
17
17
|
* RelayConfig configures the circuit v2 relay transport.
|
18
18
|
*/
|
19
|
-
export interface CircuitRelayTransportInit extends
|
19
|
+
export interface CircuitRelayTransportInit extends ReservationStoreInit {
|
20
20
|
/**
|
21
21
|
* The number of peers running diable relays to search for and connect to
|
22
22
|
*
|
@@ -14,7 +14,7 @@ export interface CircuitRelayTransportListenerComponents {
|
|
14
14
|
|
15
15
|
class CircuitRelayTransportListener extends TypedEventEmitter<ListenerEvents> implements Listener {
|
16
16
|
private readonly connectionManager: ConnectionManager
|
17
|
-
private readonly
|
17
|
+
private readonly reservationStore: ReservationStore
|
18
18
|
private readonly listeningAddrs: PeerMap<Multiaddr[]>
|
19
19
|
private readonly log: Logger
|
20
20
|
|
@@ -23,15 +23,26 @@ class CircuitRelayTransportListener extends TypedEventEmitter<ListenerEvents> im
|
|
23
23
|
|
24
24
|
this.log = components.logger.forComponent('libp2p:circuit-relay:transport:listener')
|
25
25
|
this.connectionManager = components.connectionManager
|
26
|
-
this.
|
26
|
+
this.reservationStore = components.relayStore
|
27
27
|
this.listeningAddrs = new PeerMap()
|
28
28
|
|
29
29
|
// remove listening addrs when a relay is removed
|
30
|
-
this.
|
30
|
+
this.reservationStore.addEventListener('relay:removed', this._onRemoveRelayPeer)
|
31
31
|
}
|
32
32
|
|
33
33
|
_onRemoveRelayPeer = (evt: CustomEvent<PeerId>): void => {
|
34
|
-
this
|
34
|
+
const had = this.listeningAddrs.has(evt.detail)
|
35
|
+
|
36
|
+
this.log('relay peer removed %p - had reservation', evt.detail, had)
|
37
|
+
|
38
|
+
if (!had) {
|
39
|
+
return
|
40
|
+
}
|
41
|
+
|
42
|
+
this.listeningAddrs.delete(evt.detail)
|
43
|
+
|
44
|
+
// announce listen addresses change
|
45
|
+
this.safeDispatchEvent('listening')
|
35
46
|
}
|
36
47
|
|
37
48
|
async listen (addr: Multiaddr): Promise<void> {
|
@@ -41,14 +52,14 @@ class CircuitRelayTransportListener extends TypedEventEmitter<ListenerEvents> im
|
|
41
52
|
const relayAddr = addr.decapsulate('/p2p-circuit')
|
42
53
|
const relayConn = await this.connectionManager.openConnection(relayAddr)
|
43
54
|
|
44
|
-
if (!this.
|
55
|
+
if (!this.reservationStore.hasReservation(relayConn.remotePeer)) {
|
45
56
|
this.log('making reservation on peer %p', relayConn.remotePeer)
|
46
57
|
// addRelay calls transportManager.listen which calls this listen method
|
47
|
-
await this.
|
58
|
+
await this.reservationStore.addRelay(relayConn.remotePeer, 'configured')
|
48
59
|
return
|
49
60
|
}
|
50
61
|
|
51
|
-
const reservation = this.
|
62
|
+
const reservation = this.reservationStore.getReservation(relayConn.remotePeer)
|
52
63
|
|
53
64
|
if (reservation == null) {
|
54
65
|
throw new ListenError('Did not have reservation after making reservation')
|
@@ -60,11 +71,11 @@ class CircuitRelayTransportListener extends TypedEventEmitter<ListenerEvents> im
|
|
60
71
|
}
|
61
72
|
|
62
73
|
// add all addresses from the relay reservation
|
63
|
-
this.listeningAddrs.set(relayConn.remotePeer, reservation.addrs
|
64
|
-
|
65
|
-
|
74
|
+
this.listeningAddrs.set(relayConn.remotePeer, reservation.addrs
|
75
|
+
.map(buf => multiaddr(buf).encapsulate('/p2p-circuit'))
|
76
|
+
)
|
66
77
|
|
67
|
-
this.safeDispatchEvent('listening'
|
78
|
+
this.safeDispatchEvent('listening')
|
68
79
|
}
|
69
80
|
|
70
81
|
getAddrs (): Multiaddr[] {
|
@@ -72,22 +83,14 @@ class CircuitRelayTransportListener extends TypedEventEmitter<ListenerEvents> im
|
|
72
83
|
}
|
73
84
|
|
74
85
|
async close (): Promise<void> {
|
86
|
+
await this.reservationStore.cancelReservations()
|
87
|
+
this.listeningAddrs.clear()
|
75
88
|
|
76
|
-
|
77
|
-
|
78
|
-
#removeRelayPeer (peerId: PeerId): void {
|
79
|
-
const had = this.listeningAddrs.has(peerId)
|
80
|
-
|
81
|
-
this.log('relay peer removed %p - had reservation', peerId, had)
|
89
|
+
// remove listener
|
90
|
+
this.reservationStore.removeEventListener('relay:removed', this._onRemoveRelayPeer)
|
82
91
|
|
83
|
-
|
84
|
-
|
85
|
-
if (had) {
|
86
|
-
this.log.trace('removing relay event listener for peer %p', peerId)
|
87
|
-
this.relayStore.removeEventListener('relay:removed', this._onRemoveRelayPeer)
|
88
|
-
// Announce listen addresses change
|
89
|
-
this.safeDispatchEvent('close', {})
|
90
|
-
}
|
92
|
+
// announce listen addresses change
|
93
|
+
this.safeDispatchEvent('close')
|
91
94
|
}
|
92
95
|
}
|
93
96
|
|