@libp2p/circuit-relay-v2 2.1.5 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/index.min.js +4 -4
- package/dist/src/constants.d.ts +0 -1
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +0 -1
- package/dist/src/constants.js.map +1 -1
- package/dist/src/errors.d.ts +22 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +22 -0
- package/dist/src/errors.js.map +1 -1
- package/dist/src/transport/discovery.d.ts +1 -0
- package/dist/src/transport/discovery.d.ts.map +1 -1
- package/dist/src/transport/discovery.js +38 -8
- package/dist/src/transport/discovery.js.map +1 -1
- package/dist/src/transport/index.d.ts +0 -6
- package/dist/src/transport/index.d.ts.map +1 -1
- package/dist/src/transport/index.js.map +1 -1
- package/dist/src/transport/listener.d.ts +3 -0
- package/dist/src/transport/listener.d.ts.map +1 -1
- package/dist/src/transport/listener.js +57 -27
- package/dist/src/transport/listener.js.map +1 -1
- package/dist/src/transport/reservation-store.d.ts +37 -14
- package/dist/src/transport/reservation-store.d.ts.map +1 -1
- package/dist/src/transport/reservation-store.js +125 -80
- package/dist/src/transport/reservation-store.js.map +1 -1
- package/dist/src/transport/transport.d.ts +2 -13
- package/dist/src/transport/transport.d.ts.map +1 -1
- package/dist/src/transport/transport.js +22 -49
- package/dist/src/transport/transport.js.map +1 -1
- package/dist/src/utils.d.ts +5 -2
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +7 -4
- package/dist/src/utils.js.map +1 -1
- package/package.json +10 -9
- package/src/constants.ts +0 -2
- package/src/errors.ts +25 -0
- package/src/transport/discovery.ts +47 -9
- package/src/transport/index.ts +0 -7
- package/src/transport/listener.ts +75 -35
- package/src/transport/reservation-store.ts +184 -102
- package/src/transport/transport.ts +25 -71
- package/src/utils.ts +12 -4
@@ -1,15 +1,18 @@
|
|
1
|
-
import { TypedEventEmitter, setMaxListeners } from '@libp2p/interface'
|
1
|
+
import { ListenError, TypedEventEmitter, setMaxListeners } from '@libp2p/interface'
|
2
2
|
import { PeerMap } from '@libp2p/peer-collections'
|
3
|
-
import {
|
3
|
+
import { createScalableCuckooFilter } from '@libp2p/utils/filters'
|
4
4
|
import { PeerQueue } from '@libp2p/utils/peer-queue'
|
5
5
|
import { multiaddr } from '@multiformats/multiaddr'
|
6
|
+
import { Circuit } from '@multiformats/multiaddr-matcher'
|
6
7
|
import { pbStream } from 'it-protobuf-stream'
|
7
|
-
import {
|
8
|
+
import { nanoid } from 'nanoid'
|
9
|
+
import { DEFAULT_MAX_RESERVATION_QUEUE_LENGTH, DEFAULT_RESERVATION_COMPLETION_TIMEOUT, DEFAULT_RESERVATION_CONCURRENCY, KEEP_ALIVE_TAG, RELAY_V2_HOP_CODEC } from '../constants.js'
|
10
|
+
import { DoubleRelayError, HadEnoughRelaysError, RelayQueueFullError } from '../errors.js'
|
8
11
|
import { HopMessage, Status } from '../pb/index.js'
|
9
12
|
import { getExpirationMilliseconds } from '../utils.js'
|
10
13
|
import type { Reservation } from '../pb/index.js'
|
11
|
-
import type { TypedEventTarget, Libp2pEvents,
|
12
|
-
import type { ConnectionManager
|
14
|
+
import type { AbortOptions, TypedEventTarget, Libp2pEvents, ComponentLogger, Logger, PeerId, PeerStore, Startable, Metrics, Peer, Connection } from '@libp2p/interface'
|
15
|
+
import type { ConnectionManager } from '@libp2p/interface-internal'
|
13
16
|
import type { Filter } from '@libp2p/utils/filters'
|
14
17
|
|
15
18
|
// allow refreshing a relay reservation if it will expire in the next 10 minutes
|
@@ -24,7 +27,6 @@ const REFRESH_TIMEOUT_MIN = 30 * 1000
|
|
24
27
|
export interface ReservationStoreComponents {
|
25
28
|
peerId: PeerId
|
26
29
|
connectionManager: ConnectionManager
|
27
|
-
transportManager: TransportManager
|
28
30
|
peerStore: PeerStore
|
29
31
|
events: TypedEventTarget<Libp2pEvents>
|
30
32
|
logger: ComponentLogger
|
@@ -44,11 +46,6 @@ export interface ReservationStoreInit {
|
|
44
46
|
*/
|
45
47
|
reservationConcurrency?: number
|
46
48
|
|
47
|
-
/**
|
48
|
-
* How many discovered relays to allow in the reservation store
|
49
|
-
*/
|
50
|
-
discoverRelays?: number
|
51
|
-
|
52
49
|
/**
|
53
50
|
* Limit the number of potential relays we will dial
|
54
51
|
*
|
@@ -66,9 +63,25 @@ export interface ReservationStoreInit {
|
|
66
63
|
|
67
64
|
export type RelayType = 'discovered' | 'configured'
|
68
65
|
|
69
|
-
interface
|
66
|
+
export interface DiscoveredRelayEntry {
|
67
|
+
timeout: ReturnType<typeof setTimeout>
|
68
|
+
type: 'discovered'
|
69
|
+
reservation: Reservation
|
70
|
+
|
71
|
+
/**
|
72
|
+
* Stores the id of the connection we have to the relay
|
73
|
+
*/
|
74
|
+
connection: string
|
75
|
+
|
76
|
+
/**
|
77
|
+
* Stores the identifier returned when the reservation was requested
|
78
|
+
*/
|
79
|
+
id: string
|
80
|
+
}
|
81
|
+
|
82
|
+
export interface ConfiguredRelayEntry {
|
70
83
|
timeout: ReturnType<typeof setTimeout>
|
71
|
-
type:
|
84
|
+
type: 'configured'
|
72
85
|
reservation: Reservation
|
73
86
|
|
74
87
|
/**
|
@@ -77,26 +90,33 @@ interface RelayEntry {
|
|
77
90
|
connection: string
|
78
91
|
}
|
79
92
|
|
93
|
+
export type RelayEntry = DiscoveredRelayEntry | ConfiguredRelayEntry
|
94
|
+
|
95
|
+
export interface RelayReservation {
|
96
|
+
relay: PeerId
|
97
|
+
details: RelayEntry
|
98
|
+
}
|
99
|
+
|
80
100
|
export interface ReservationStoreEvents {
|
81
101
|
'relay:not-enough-relays': CustomEvent
|
82
|
-
'relay:
|
83
|
-
'relay:
|
102
|
+
'relay:found-enough-relays': CustomEvent
|
103
|
+
'relay:removed': CustomEvent<RelayReservation>
|
104
|
+
'relay:created-reservation': CustomEvent<RelayReservation>
|
84
105
|
}
|
85
106
|
|
86
107
|
export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents> implements Startable {
|
87
108
|
private readonly peerId: PeerId
|
88
109
|
private readonly connectionManager: ConnectionManager
|
89
|
-
private readonly transportManager: TransportManager
|
90
110
|
private readonly peerStore: PeerStore
|
91
111
|
private readonly events: TypedEventTarget<Libp2pEvents>
|
92
|
-
private readonly reserveQueue: PeerQueue
|
112
|
+
private readonly reserveQueue: PeerQueue<RelayReservation>
|
93
113
|
private readonly reservations: PeerMap<RelayEntry>
|
94
|
-
private readonly
|
114
|
+
private readonly pendingReservations: string[]
|
95
115
|
private readonly maxReservationQueueLength: number
|
96
116
|
private readonly reservationCompletionTimeout: number
|
97
117
|
private started: boolean
|
98
118
|
private readonly log: Logger
|
99
|
-
private
|
119
|
+
private relayFilter: Filter
|
100
120
|
|
101
121
|
constructor (components: ReservationStoreComponents, init?: ReservationStoreInit) {
|
102
122
|
super()
|
@@ -104,15 +124,14 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
|
|
104
124
|
this.log = components.logger.forComponent('libp2p:circuit-relay:transport:reservation-store')
|
105
125
|
this.peerId = components.peerId
|
106
126
|
this.connectionManager = components.connectionManager
|
107
|
-
this.transportManager = components.transportManager
|
108
127
|
this.peerStore = components.peerStore
|
109
128
|
this.events = components.events
|
110
129
|
this.reservations = new PeerMap()
|
111
|
-
this.
|
130
|
+
this.pendingReservations = []
|
112
131
|
this.maxReservationQueueLength = init?.maxReservationQueueLength ?? DEFAULT_MAX_RESERVATION_QUEUE_LENGTH
|
113
132
|
this.reservationCompletionTimeout = init?.reservationCompletionTimeout ?? DEFAULT_RESERVATION_COMPLETION_TIMEOUT
|
114
133
|
this.started = false
|
115
|
-
this.relayFilter =
|
134
|
+
this.relayFilter = createScalableCuckooFilter(100)
|
116
135
|
|
117
136
|
// ensure we don't listen on multiple relays simultaneously
|
118
137
|
this.reserveQueue = new PeerQueue({
|
@@ -132,7 +151,7 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
|
|
132
151
|
return
|
133
152
|
}
|
134
153
|
|
135
|
-
this.#removeReservation(evt.detail.remotePeer
|
154
|
+
this.#removeReservation(evt.detail.remotePeer)
|
136
155
|
.catch(err => {
|
137
156
|
this.log('could not remove relay %p - %e', evt.detail, err)
|
138
157
|
})
|
@@ -153,7 +172,7 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
|
|
153
172
|
.then(async () => {
|
154
173
|
const relayPeers: Peer[] = await this.peerStore.all({
|
155
174
|
filters: [(peer) => {
|
156
|
-
return peer.tags.has(
|
175
|
+
return peer.tags.has(KEEP_ALIVE_TAG)
|
157
176
|
}]
|
158
177
|
})
|
159
178
|
|
@@ -164,17 +183,18 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
|
|
164
183
|
relayPeers.map(async peer => {
|
165
184
|
await this.peerStore.merge(peer.id, {
|
166
185
|
tags: {
|
167
|
-
[RELAY_TAG]: undefined,
|
168
186
|
[KEEP_ALIVE_TAG]: undefined
|
169
187
|
}
|
170
188
|
})
|
171
189
|
})
|
172
190
|
)
|
173
191
|
|
174
|
-
|
175
|
-
|
176
|
-
this.
|
177
|
-
|
192
|
+
this.log('redialing %d old relays', relayPeers.length)
|
193
|
+
await Promise.all(
|
194
|
+
relayPeers.map(async peer => this.addRelay(peer.id, 'discovered'))
|
195
|
+
)
|
196
|
+
|
197
|
+
this.#checkReservationCount()
|
178
198
|
})
|
179
199
|
.catch(err => {
|
180
200
|
this.log.error(err)
|
@@ -190,36 +210,46 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
|
|
190
210
|
this.started = false
|
191
211
|
}
|
192
212
|
|
213
|
+
reserveRelay (): string {
|
214
|
+
const id = nanoid()
|
215
|
+
|
216
|
+
this.pendingReservations.push(id)
|
217
|
+
|
218
|
+
this.#checkReservationCount()
|
219
|
+
|
220
|
+
return id
|
221
|
+
}
|
222
|
+
|
193
223
|
/**
|
194
224
|
* If the number of current relays is beneath the configured `maxReservations`
|
195
225
|
* value, and the passed peer id is not our own, and we have a non-relayed
|
196
226
|
* connection to the remote, and the remote peer speaks the hop protocol, try
|
197
227
|
* to reserve a slot on the remote peer
|
198
228
|
*/
|
199
|
-
async addRelay (peerId: PeerId, type: RelayType): Promise<
|
229
|
+
async addRelay (peerId: PeerId, type: RelayType): Promise<RelayReservation> {
|
200
230
|
if (this.peerId.equals(peerId)) {
|
201
231
|
this.log.trace('not trying to use self as relay')
|
202
|
-
|
232
|
+
throw new ListenError('Cannot use self as relay')
|
203
233
|
}
|
204
234
|
|
205
235
|
if (this.reserveQueue.size > this.maxReservationQueueLength) {
|
206
|
-
|
207
|
-
return
|
236
|
+
throw new RelayQueueFullError('The reservation queue is full')
|
208
237
|
}
|
209
238
|
|
210
|
-
|
239
|
+
const existingJob = this.reserveQueue.find(peerId)
|
240
|
+
|
241
|
+
if (existingJob != null) {
|
211
242
|
this.log.trace('potential relay peer %p is already in the reservation queue', peerId)
|
212
|
-
return
|
243
|
+
return existingJob.join()
|
213
244
|
}
|
214
245
|
|
215
246
|
if (this.relayFilter.has(peerId.toMultihash().bytes)) {
|
216
|
-
|
217
|
-
return
|
247
|
+
throw new ListenError('The relay was previously invalid')
|
218
248
|
}
|
219
249
|
|
220
250
|
this.log.trace('try to reserve relay slot with %p', peerId)
|
221
251
|
|
222
|
-
|
252
|
+
return this.reserveQueue.add(async () => {
|
223
253
|
const start = Date.now()
|
224
254
|
|
225
255
|
try {
|
@@ -241,21 +271,17 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
|
|
241
271
|
|
242
272
|
if (connected && getExpirationMilliseconds(existingReservation.reservation.expire) > REFRESH_WINDOW) {
|
243
273
|
this.log('already have relay reservation with %p but we are still connected and it does not expire soon', peerId)
|
244
|
-
return
|
274
|
+
return {
|
275
|
+
relay: peerId,
|
276
|
+
details: existingReservation
|
277
|
+
} satisfies RelayReservation
|
245
278
|
}
|
246
279
|
|
247
|
-
await this.#removeReservation(peerId
|
280
|
+
await this.#removeReservation(peerId)
|
248
281
|
}
|
249
282
|
|
250
|
-
if (type === 'discovered' &&
|
251
|
-
|
252
|
-
acc++
|
253
|
-
}
|
254
|
-
|
255
|
-
return acc
|
256
|
-
}, 0) >= this.maxDiscoveredRelays) {
|
257
|
-
this.log.trace('already have enough discovered relays')
|
258
|
-
return
|
283
|
+
if (type === 'discovered' && this.pendingReservations.length === 0) {
|
284
|
+
throw new HadEnoughRelaysError('Not making reservation on discovered relay because we do not need any more relays')
|
259
285
|
}
|
260
286
|
|
261
287
|
const signal = AbortSignal.timeout(this.reservationCompletionTimeout)
|
@@ -265,9 +291,8 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
|
|
265
291
|
signal
|
266
292
|
})
|
267
293
|
|
268
|
-
if (connection.remoteAddr
|
269
|
-
|
270
|
-
return
|
294
|
+
if (Circuit.matches(connection.remoteAddr)) {
|
295
|
+
throw new DoubleRelayError('not creating reservation over relayed connection')
|
271
296
|
}
|
272
297
|
|
273
298
|
const reservation = await this.#createReservation(connection, {
|
@@ -288,36 +313,45 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
|
|
288
313
|
this.addRelay(peerId, type)
|
289
314
|
.catch(async err => {
|
290
315
|
this.log.error('could not refresh reservation to relay %p - %e', peerId, err)
|
291
|
-
|
292
|
-
const reservation = this.reservations.get(peerId)
|
293
|
-
|
294
|
-
if (reservation == null) {
|
295
|
-
this.log.error('did not have reservation after refreshing reservation failed %p', peerId)
|
296
|
-
return
|
297
|
-
}
|
298
|
-
|
299
|
-
await this.#removeReservation(peerId, reservation)
|
316
|
+
await this.#removeReservation(peerId)
|
300
317
|
})
|
301
318
|
.catch(err => {
|
302
319
|
this.log.error('could not remove expired reservation to relay %p - %e', peerId, err)
|
303
320
|
})
|
304
321
|
}, timeoutDuration)
|
305
322
|
|
323
|
+
let res: RelayEntry
|
324
|
+
|
325
|
+
// assign a reservation id if one was requested
|
326
|
+
if (type === 'discovered') {
|
327
|
+
const id = this.pendingReservations.pop()
|
328
|
+
|
329
|
+
if (id == null) {
|
330
|
+
throw new HadEnoughRelaysError('Made reservation on relay but did not need any more discovered relays')
|
331
|
+
}
|
332
|
+
|
333
|
+
res = {
|
334
|
+
timeout,
|
335
|
+
reservation,
|
336
|
+
type,
|
337
|
+
connection: connection.id,
|
338
|
+
id
|
339
|
+
}
|
340
|
+
} else {
|
341
|
+
res = {
|
342
|
+
timeout,
|
343
|
+
reservation,
|
344
|
+
type,
|
345
|
+
connection: connection.id
|
346
|
+
}
|
347
|
+
}
|
348
|
+
|
306
349
|
// we've managed to create a reservation successfully
|
307
|
-
this.reservations.set(peerId,
|
308
|
-
timeout,
|
309
|
-
reservation,
|
310
|
-
type,
|
311
|
-
connection: connection.id
|
312
|
-
})
|
350
|
+
this.reservations.set(peerId, res)
|
313
351
|
|
314
352
|
// ensure we don't close the connection to the relay
|
315
353
|
await this.peerStore.merge(peerId, {
|
316
354
|
tags: {
|
317
|
-
[RELAY_TAG]: {
|
318
|
-
value: 1,
|
319
|
-
ttl: expiration
|
320
|
-
},
|
321
355
|
[KEEP_ALIVE_TAG]: {
|
322
356
|
value: 1,
|
323
357
|
ttl: expiration
|
@@ -325,28 +359,37 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
|
|
325
359
|
}
|
326
360
|
})
|
327
361
|
|
328
|
-
//
|
329
|
-
|
362
|
+
// check to see if we have discovered enough relays
|
363
|
+
this.#checkReservationCount()
|
364
|
+
|
365
|
+
const result: RelayReservation = {
|
366
|
+
relay: peerId,
|
367
|
+
details: res
|
368
|
+
}
|
330
369
|
|
331
370
|
this.safeDispatchEvent('relay:created-reservation', {
|
332
|
-
detail:
|
371
|
+
detail: result
|
333
372
|
})
|
334
|
-
} catch (err) {
|
335
|
-
this.log.error('could not reserve slot on %p after %dms', peerId, Date.now() - start, err)
|
336
373
|
|
337
|
-
|
338
|
-
|
374
|
+
return result
|
375
|
+
} catch (err: any) {
|
376
|
+
if (!(type === 'discovered' && err.name === 'HadEnoughRelaysError')) {
|
377
|
+
this.log.error('could not reserve slot on %p after %dms - %e', peerId, Date.now() - start, err)
|
378
|
+
}
|
339
379
|
|
340
|
-
//
|
341
|
-
|
380
|
+
// don't try this peer again if dialing failed or they do not support
|
381
|
+
// the hop protocol
|
382
|
+
if (err.name === 'DialError' || err.name === 'UnsupportedProtocolError') {
|
383
|
+
this.relayFilter.add(peerId.toMultihash().bytes)
|
384
|
+
}
|
342
385
|
|
343
386
|
// if listening failed, remove the reservation
|
344
|
-
|
345
|
-
|
346
|
-
.
|
347
|
-
|
348
|
-
|
349
|
-
|
387
|
+
this.#removeReservation(peerId)
|
388
|
+
.catch(err => {
|
389
|
+
this.log.error('could not remove reservation on %p after reserving slot failed - %e', peerId, err)
|
390
|
+
})
|
391
|
+
|
392
|
+
throw err
|
350
393
|
}
|
351
394
|
}, {
|
352
395
|
peerId
|
@@ -361,16 +404,26 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
|
|
361
404
|
return this.reservations.get(peerId)?.reservation
|
362
405
|
}
|
363
406
|
|
364
|
-
reservationCount (): number {
|
365
|
-
|
407
|
+
reservationCount (type?: RelayType): number {
|
408
|
+
if (type == null) {
|
409
|
+
return this.reservations.size
|
410
|
+
}
|
411
|
+
|
412
|
+
return [...this.reservations.values()].reduce((acc, curr) => {
|
413
|
+
if (curr.type === type) {
|
414
|
+
acc++
|
415
|
+
}
|
416
|
+
|
417
|
+
return acc
|
418
|
+
}, 0)
|
366
419
|
}
|
367
420
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
)
|
421
|
+
cancelReservations (): void {
|
422
|
+
[...this.reservations.values()].forEach(reservation => {
|
423
|
+
clearTimeout(reservation.timeout)
|
424
|
+
})
|
425
|
+
|
426
|
+
this.reservations.clear()
|
374
427
|
}
|
375
428
|
|
376
429
|
async #createReservation (connection: Connection, options: AbortOptions): Promise<Reservation> {
|
@@ -380,11 +433,14 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
|
|
380
433
|
const stream = await connection.newStream(RELAY_V2_HOP_CODEC, options)
|
381
434
|
const pbstr = pbStream(stream)
|
382
435
|
const hopstr = pbstr.pb(HopMessage)
|
436
|
+
|
437
|
+
this.log.trace('send RESERVE to %p', connection.remotePeer)
|
383
438
|
await hopstr.write({ type: HopMessage.Type.RESERVE }, options)
|
384
439
|
|
385
440
|
let response: HopMessage
|
386
441
|
|
387
442
|
try {
|
443
|
+
this.log.trace('read response from %p', connection.remotePeer)
|
388
444
|
response = await hopstr.read(options)
|
389
445
|
} catch (err: any) {
|
390
446
|
stream.abort(err)
|
@@ -395,6 +451,8 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
|
|
395
451
|
}
|
396
452
|
}
|
397
453
|
|
454
|
+
this.log.trace('read response from %p', response)
|
455
|
+
|
398
456
|
if (response.status === Status.OK && response.reservation != null) {
|
399
457
|
// check that the returned relay has the relay address - this can be
|
400
458
|
// omitted when requesting a reservation from a go-libp2p relay we
|
@@ -432,9 +490,10 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
|
|
432
490
|
/**
|
433
491
|
* Remove listen relay
|
434
492
|
*/
|
435
|
-
async #removeReservation (peerId: PeerId
|
436
|
-
|
437
|
-
|
493
|
+
async #removeReservation (peerId: PeerId): Promise<void> {
|
494
|
+
const reservation = this.reservations.get(peerId)
|
495
|
+
|
496
|
+
if (reservation == null) {
|
438
497
|
return
|
439
498
|
}
|
440
499
|
|
@@ -442,19 +501,42 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
|
|
442
501
|
clearTimeout(reservation.timeout)
|
443
502
|
this.reservations.delete(peerId)
|
444
503
|
|
504
|
+
// discover a new relay for this discovery request
|
505
|
+
if (reservation.type === 'discovered') {
|
506
|
+
this.pendingReservations.push(
|
507
|
+
reservation.id
|
508
|
+
)
|
509
|
+
}
|
510
|
+
|
445
511
|
// untag the relay
|
446
512
|
await this.peerStore.merge(peerId, {
|
447
513
|
tags: {
|
448
|
-
[RELAY_TAG]: undefined,
|
449
514
|
[KEEP_ALIVE_TAG]: undefined
|
450
515
|
}
|
451
516
|
})
|
452
517
|
|
453
|
-
this.safeDispatchEvent('relay:removed', {
|
518
|
+
this.safeDispatchEvent('relay:removed', {
|
519
|
+
detail: {
|
520
|
+
relay: peerId,
|
521
|
+
details: reservation
|
522
|
+
}
|
523
|
+
})
|
454
524
|
|
455
|
-
|
456
|
-
|
457
|
-
|
525
|
+
// maybe trigger discovery of new relays
|
526
|
+
this.#checkReservationCount()
|
527
|
+
}
|
528
|
+
|
529
|
+
#checkReservationCount (): void {
|
530
|
+
if (this.pendingReservations.length === 0) {
|
531
|
+
this.log.trace('have discovered enough relays')
|
532
|
+
this.reserveQueue.clear()
|
533
|
+
this.safeDispatchEvent('relay:found-enough-relays')
|
534
|
+
|
535
|
+
return
|
458
536
|
}
|
537
|
+
|
538
|
+
this.relayFilter = createScalableCuckooFilter(100)
|
539
|
+
this.log('not discovered enough relays %d/%d', this.reservations.size, this.pendingReservations.length)
|
540
|
+
this.safeDispatchEvent('relay:not-enough-relays')
|
459
541
|
}
|
460
542
|
}
|
@@ -9,7 +9,7 @@ import * as Digest from 'multiformats/hashes/digest'
|
|
9
9
|
import { CustomProgressEvent } from 'progress-events'
|
10
10
|
import { CIRCUIT_PROTO_CODE, DEFAULT_DISCOVERY_FILTER_ERROR_RATE, DEFAULT_DISCOVERY_FILTER_SIZE, MAX_CONNECTIONS, RELAY_V2_HOP_CODEC, RELAY_V2_STOP_CODEC } from '../constants.js'
|
11
11
|
import { StopMessage, HopMessage, Status } from '../pb/index.js'
|
12
|
-
import { CircuitListen, LimitTracker } from '../utils.js'
|
12
|
+
import { CircuitListen, CircuitSearch, LimitTracker } from '../utils.js'
|
13
13
|
import { RelayDiscovery } from './discovery.js'
|
14
14
|
import { createListener } from './listener.js'
|
15
15
|
import { ReservationStore } from './reservation-store.js'
|
@@ -17,7 +17,7 @@ import type { CircuitRelayTransportComponents, CircuitRelayTransportInit } from
|
|
17
17
|
import type { Transport, CreateListenerOptions, Listener, Upgrader, ComponentLogger, Logger, Connection, Stream, ConnectionGater, PeerId, PeerStore, OutboundConnectionUpgradeEvents, DialTransportOptions, OpenConnectionProgressEvents } from '@libp2p/interface'
|
18
18
|
import type { AddressManager, ConnectionManager, IncomingStreamData, Registrar, TransportManager } from '@libp2p/interface-internal'
|
19
19
|
import type { Multiaddr } from '@multiformats/multiaddr'
|
20
|
-
import type { ProgressEvent
|
20
|
+
import type { ProgressEvent } from 'progress-events'
|
21
21
|
|
22
22
|
const isValidStop = (request: StopMessage): request is Required<StopMessage> => {
|
23
23
|
if (request.peer == null) {
|
@@ -33,16 +33,6 @@ const isValidStop = (request: StopMessage): request is Required<StopMessage> =>
|
|
33
33
|
return true
|
34
34
|
}
|
35
35
|
|
36
|
-
interface ConnectOptions extends ProgressOptions<CircuitRelayDialEvents> {
|
37
|
-
stream: Stream
|
38
|
-
connection: Connection
|
39
|
-
destinationPeer: PeerId
|
40
|
-
destinationAddr: Multiaddr
|
41
|
-
relayAddr: Multiaddr
|
42
|
-
ma: Multiaddr
|
43
|
-
disconnectOnFailure: boolean
|
44
|
-
}
|
45
|
-
|
46
36
|
const defaults = {
|
47
37
|
maxInboundStopStreams: MAX_CONNECTIONS,
|
48
38
|
maxOutboundStopStreams: MAX_CONNECTIONS,
|
@@ -91,28 +81,23 @@ export class CircuitRelayTransport implements Transport<CircuitRelayDialEvents>
|
|
91
81
|
this.maxOutboundStopStreams = init.maxOutboundStopStreams ?? defaults.maxOutboundStopStreams
|
92
82
|
this.stopTimeout = init.stopTimeout ?? defaults.stopTimeout
|
93
83
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
this.reservationStore.addRelay(evt.detail, 'discovered')
|
102
|
-
.catch(err => {
|
84
|
+
this.discovery = new RelayDiscovery(components, {
|
85
|
+
filter: init.discoveryFilter ?? peerFilter(DEFAULT_DISCOVERY_FILTER_SIZE, DEFAULT_DISCOVERY_FILTER_ERROR_RATE)
|
86
|
+
})
|
87
|
+
this.discovery.addEventListener('relay:discover', (evt) => {
|
88
|
+
this.reservationStore.addRelay(evt.detail, 'discovered')
|
89
|
+
.catch(err => {
|
90
|
+
if (err.name !== 'HadEnoughRelaysError' && err.name !== 'RelayQueueFullError') {
|
103
91
|
this.log.error('could not add discovered relay %p', evt.detail, err)
|
104
|
-
}
|
105
|
-
|
106
|
-
}
|
107
|
-
|
92
|
+
}
|
93
|
+
})
|
94
|
+
})
|
108
95
|
this.reservationStore = new ReservationStore(components, init)
|
109
96
|
this.reservationStore.addEventListener('relay:not-enough-relays', () => {
|
110
97
|
this.discovery?.startDiscovery()
|
111
98
|
})
|
112
|
-
this.reservationStore.addEventListener('relay:
|
113
|
-
|
114
|
-
this.discovery?.stopDiscovery()
|
115
|
-
}
|
99
|
+
this.reservationStore.addEventListener('relay:found-enough-relays', () => {
|
100
|
+
this.discovery?.stopDiscovery()
|
116
101
|
})
|
117
102
|
|
118
103
|
this.started = false
|
@@ -192,7 +177,6 @@ export class CircuitRelayTransport implements Transport<CircuitRelayDialEvents>
|
|
192
177
|
const relayPeer = peerIdFromString(relayId)
|
193
178
|
const destinationPeer = peerIdFromString(destinationId)
|
194
179
|
|
195
|
-
let disconnectOnFailure = false
|
196
180
|
const relayConnections = this.connectionManager.getConnections(relayPeer)
|
197
181
|
let relayConnection = relayConnections[0]
|
198
182
|
|
@@ -203,7 +187,6 @@ export class CircuitRelayTransport implements Transport<CircuitRelayDialEvents>
|
|
203
187
|
|
204
188
|
options.onProgress?.(new CustomProgressEvent('circuit-relay:open-connection'))
|
205
189
|
relayConnection = await this.connectionManager.openConnection(relayPeer, options)
|
206
|
-
disconnectOnFailure = true
|
207
190
|
} else {
|
208
191
|
options.onProgress?.(new CustomProgressEvent('circuit-relay:reuse-connection'))
|
209
192
|
}
|
@@ -212,52 +195,22 @@ export class CircuitRelayTransport implements Transport<CircuitRelayDialEvents>
|
|
212
195
|
|
213
196
|
try {
|
214
197
|
options.onProgress?.(new CustomProgressEvent('circuit-relay:open-hop-stream'))
|
215
|
-
stream = await relayConnection.newStream(RELAY_V2_HOP_CODEC)
|
216
|
-
|
217
|
-
return await this.connectV2({
|
218
|
-
stream,
|
219
|
-
connection: relayConnection,
|
220
|
-
destinationPeer,
|
221
|
-
destinationAddr,
|
222
|
-
relayAddr,
|
223
|
-
ma,
|
224
|
-
disconnectOnFailure,
|
225
|
-
onProgress: options.onProgress
|
226
|
-
})
|
227
|
-
} catch (err: any) {
|
228
|
-
this.log.error('circuit relay dial to destination %p via relay %p failed', destinationPeer, relayPeer, err)
|
198
|
+
stream = await relayConnection.newStream(RELAY_V2_HOP_CODEC, options)
|
229
199
|
|
230
|
-
if (stream != null) {
|
231
|
-
stream.abort(err)
|
232
|
-
}
|
233
|
-
disconnectOnFailure && await relayConnection.close()
|
234
|
-
throw err
|
235
|
-
}
|
236
|
-
}
|
237
|
-
|
238
|
-
async connectV2 (
|
239
|
-
{
|
240
|
-
stream, connection, destinationPeer,
|
241
|
-
destinationAddr, relayAddr, ma,
|
242
|
-
disconnectOnFailure,
|
243
|
-
onProgress
|
244
|
-
}: ConnectOptions
|
245
|
-
): Promise<Connection> {
|
246
|
-
try {
|
247
200
|
const pbstr = pbStream(stream)
|
248
201
|
const hopstr = pbstr.pb(HopMessage)
|
249
202
|
|
250
|
-
onProgress?.(new CustomProgressEvent('circuit-relay:write-connect-message'))
|
203
|
+
options.onProgress?.(new CustomProgressEvent('circuit-relay:write-connect-message'))
|
251
204
|
await hopstr.write({
|
252
205
|
type: HopMessage.Type.CONNECT,
|
253
206
|
peer: {
|
254
207
|
id: destinationPeer.toMultihash().bytes,
|
255
208
|
addrs: [multiaddr(destinationAddr).bytes]
|
256
209
|
}
|
257
|
-
})
|
210
|
+
}, options)
|
258
211
|
|
259
|
-
onProgress?.(new CustomProgressEvent('circuit-relay:read-connect-response'))
|
260
|
-
const status = await hopstr.read()
|
212
|
+
options.onProgress?.(new CustomProgressEvent('circuit-relay:read-connect-response'))
|
213
|
+
const status = await hopstr.read(options)
|
261
214
|
|
262
215
|
if (status.status !== Status.OK) {
|
263
216
|
throw new InvalidMessageError(`failed to connect via relay with status ${status?.status?.toString() ?? 'undefined'}`)
|
@@ -277,12 +230,13 @@ export class CircuitRelayTransport implements Transport<CircuitRelayDialEvents>
|
|
277
230
|
this.log('new outbound relayed connection %a', maConn.remoteAddr)
|
278
231
|
|
279
232
|
return await this.upgrader.upgradeOutbound(maConn, {
|
280
|
-
|
281
|
-
|
233
|
+
...options,
|
234
|
+
limits: limits.getLimits()
|
282
235
|
})
|
283
236
|
} catch (err: any) {
|
284
|
-
this.log.error(
|
285
|
-
|
237
|
+
this.log.error('circuit relay dial to destination %p via relay %p failed', destinationPeer, relayPeer, err)
|
238
|
+
stream?.abort(err)
|
239
|
+
|
286
240
|
throw err
|
287
241
|
}
|
288
242
|
}
|
@@ -305,7 +259,7 @@ export class CircuitRelayTransport implements Transport<CircuitRelayDialEvents>
|
|
305
259
|
multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs]
|
306
260
|
|
307
261
|
return multiaddrs.filter((ma) => {
|
308
|
-
return CircuitListen.exactMatch(ma)
|
262
|
+
return CircuitListen.exactMatch(ma) || CircuitSearch.exactMatch(ma)
|
309
263
|
})
|
310
264
|
}
|
311
265
|
|