@libp2p/circuit-relay-v2 2.1.3-e99e8f448 → 2.1.4-5d199f9b6
Sign up to get free protection for your applications and to get access to all the features.
- 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/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
|
|