@libp2p/circuit-relay-v2 2.1.5 → 3.0.0
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 +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
|
|