libp2p 0.43.4 → 0.44.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 +30 -30
- package/dist/src/autonat/index.d.ts +1 -2
- package/dist/src/autonat/index.d.ts.map +1 -1
- package/dist/src/autonat/index.js +4 -6
- package/dist/src/autonat/index.js.map +1 -1
- package/dist/src/circuit-relay/transport/reservation-store.d.ts +1 -1
- package/dist/src/circuit-relay/transport/reservation-store.d.ts.map +1 -1
- package/dist/src/circuit-relay/utils.d.ts.map +1 -1
- package/dist/src/circuit-relay/utils.js +3 -1
- package/dist/src/circuit-relay/utils.js.map +1 -1
- package/dist/src/components.d.ts +1 -6
- package/dist/src/components.d.ts.map +1 -1
- package/dist/src/components.js +0 -10
- package/dist/src/components.js.map +1 -1
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +0 -9
- package/dist/src/config.js.map +1 -1
- package/dist/src/connection-manager/auto-dial.d.ts +36 -0
- package/dist/src/connection-manager/auto-dial.d.ts.map +1 -0
- package/dist/src/connection-manager/auto-dial.js +122 -0
- package/dist/src/connection-manager/auto-dial.js.map +1 -0
- package/dist/src/connection-manager/connection-pruner.d.ts +28 -0
- package/dist/src/connection-manager/connection-pruner.d.ts.map +1 -0
- package/dist/src/connection-manager/connection-pruner.js +96 -0
- package/dist/src/connection-manager/connection-pruner.js.map +1 -0
- package/dist/src/connection-manager/constants.d.ts +49 -0
- package/dist/src/connection-manager/constants.d.ts.map +1 -0
- package/dist/src/connection-manager/constants.js +49 -0
- package/dist/src/connection-manager/constants.js.map +1 -0
- package/dist/src/connection-manager/dial-queue.d.ts +77 -0
- package/dist/src/connection-manager/dial-queue.d.ts.map +1 -0
- package/dist/src/connection-manager/dial-queue.js +366 -0
- package/dist/src/connection-manager/dial-queue.js.map +1 -0
- package/dist/src/connection-manager/index.d.ts +65 -69
- package/dist/src/connection-manager/index.d.ts.map +1 -1
- package/dist/src/connection-manager/index.js +116 -234
- package/dist/src/connection-manager/index.js.map +1 -1
- package/dist/src/connection-manager/utils.d.ts +9 -0
- package/dist/src/connection-manager/utils.d.ts.map +1 -0
- package/dist/src/connection-manager/utils.js +66 -0
- package/dist/src/connection-manager/utils.js.map +1 -0
- package/dist/src/fetch/index.js +3 -3
- package/dist/src/fetch/index.js.map +1 -1
- package/dist/src/identify/index.js +5 -5
- package/dist/src/identify/index.js.map +1 -1
- package/dist/src/insecure/index.d.ts.map +1 -1
- package/dist/src/insecure/index.js +3 -1
- package/dist/src/insecure/index.js.map +1 -1
- package/dist/src/libp2p.d.ts +10 -2
- package/dist/src/libp2p.d.ts.map +1 -1
- package/dist/src/libp2p.js +7 -13
- package/dist/src/libp2p.js.map +1 -1
- package/dist/src/upgrader.d.ts +1 -1
- package/dist/src/upgrader.d.ts.map +1 -1
- package/dist/src/upgrader.js +2 -1
- package/dist/src/upgrader.js.map +1 -1
- package/dist/typedoc-urls.json +1 -1
- package/package.json +18 -18
- package/src/autonat/index.ts +6 -9
- package/src/circuit-relay/transport/reservation-store.ts +1 -1
- package/src/circuit-relay/utils.ts +3 -1
- package/src/components.ts +1 -17
- package/src/config.ts +0 -9
- package/src/connection-manager/auto-dial.ts +171 -0
- package/src/connection-manager/connection-pruner.ts +137 -0
- package/src/connection-manager/constants.ts +60 -0
- package/src/connection-manager/dial-queue.ts +504 -0
- package/src/connection-manager/index.ts +190 -323
- package/src/connection-manager/utils.ts +76 -0
- package/src/fetch/index.ts +4 -4
- package/src/identify/index.ts +5 -5
- package/src/insecure/index.ts +5 -1
- package/src/libp2p.ts +17 -15
- package/src/upgrader.ts +3 -2
- package/src/version.ts +1 -1
- package/dist/src/connection-manager/auto-dialler.d.ts +0 -42
- package/dist/src/connection-manager/auto-dialler.d.ts.map +0 -1
- package/dist/src/connection-manager/auto-dialler.js +0 -105
- package/dist/src/connection-manager/auto-dialler.js.map +0 -1
- package/dist/src/connection-manager/dialer/dial-request.d.ts +0 -27
- package/dist/src/connection-manager/dialer/dial-request.d.ts.map +0 -1
- package/dist/src/connection-manager/dialer/dial-request.js +0 -114
- package/dist/src/connection-manager/dialer/dial-request.js.map +0 -1
- package/dist/src/connection-manager/dialer/index.d.ts +0 -121
- package/dist/src/connection-manager/dialer/index.d.ts.map +0 -1
- package/dist/src/connection-manager/dialer/index.js +0 -324
- package/dist/src/connection-manager/dialer/index.js.map +0 -1
- package/dist/src/connection-manager/latency-monitor.d.ts +0 -120
- package/dist/src/connection-manager/latency-monitor.d.ts.map +0 -1
- package/dist/src/connection-manager/latency-monitor.js +0 -220
- package/dist/src/connection-manager/latency-monitor.js.map +0 -1
- package/dist/src/connection-manager/visibility-change-emitter.d.ts +0 -66
- package/dist/src/connection-manager/visibility-change-emitter.d.ts.map +0 -1
- package/dist/src/connection-manager/visibility-change-emitter.js +0 -118
- package/dist/src/connection-manager/visibility-change-emitter.js.map +0 -1
- package/dist/src/constants.d.ts +0 -27
- package/dist/src/constants.d.ts.map +0 -1
- package/dist/src/constants.js +0 -31
- package/dist/src/constants.js.map +0 -1
- package/src/connection-manager/auto-dialler.ts +0 -158
- package/src/connection-manager/dialer/dial-request.ts +0 -144
- package/src/connection-manager/dialer/index.ts +0 -461
- package/src/connection-manager/latency-monitor.ts +0 -319
- package/src/connection-manager/visibility-change-emitter.ts +0 -135
- package/src/constants.ts +0 -36
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import { logger } from '@libp2p/logger'
|
|
2
|
+
import { AbortError, CodeError } from '@libp2p/interfaces/errors'
|
|
3
|
+
import { Multiaddr, Resolver, resolvers } from '@multiformats/multiaddr'
|
|
4
|
+
import { TimeoutController } from 'timeout-abort-controller'
|
|
5
|
+
import { publicAddressesFirst } from '@libp2p/utils/address-sort'
|
|
6
|
+
import { codes } from '../errors.js'
|
|
7
|
+
import {
|
|
8
|
+
DIAL_TIMEOUT,
|
|
9
|
+
MAX_PARALLEL_DIALS_PER_PEER,
|
|
10
|
+
MAX_PARALLEL_DIALS,
|
|
11
|
+
MAX_PEER_ADDRS_TO_DIAL
|
|
12
|
+
} from './constants.js'
|
|
13
|
+
import type { Connection } from '@libp2p/interface-connection'
|
|
14
|
+
import type { AbortOptions } from '@libp2p/interfaces'
|
|
15
|
+
import type { PeerId } from '@libp2p/interface-peer-id'
|
|
16
|
+
import { getPeerAddress } from '../get-peer.js'
|
|
17
|
+
import type { Address, AddressSorter, PeerStore } from '@libp2p/interface-peer-store'
|
|
18
|
+
import type { Metric, Metrics } from '@libp2p/interface-metrics'
|
|
19
|
+
import type { TransportManager } from '@libp2p/interface-transport'
|
|
20
|
+
import type { ConnectionGater } from '@libp2p/interface-connection-gater'
|
|
21
|
+
import PQueue from 'p-queue'
|
|
22
|
+
import { dnsaddrResolver } from '@multiformats/multiaddr/resolvers'
|
|
23
|
+
import { combineSignals, resolveMultiaddrs } from './utils.js'
|
|
24
|
+
import pDefer from 'p-defer'
|
|
25
|
+
import type { ClearableSignal } from 'any-signal'
|
|
26
|
+
|
|
27
|
+
const log = logger('libp2p:connection-manager:dial-queue')
|
|
28
|
+
|
|
29
|
+
export type PendingDialStatus = 'queued' | 'active' | 'error' | 'success'
|
|
30
|
+
|
|
31
|
+
export interface PendingDial {
|
|
32
|
+
id: string
|
|
33
|
+
status: PendingDialStatus
|
|
34
|
+
peerId?: PeerId
|
|
35
|
+
multiaddrs: Multiaddr[]
|
|
36
|
+
promise: Promise<Connection>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface PendingDialTarget {
|
|
40
|
+
resolve: (value: any) => void
|
|
41
|
+
reject: (err: Error) => void
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface DialOptions extends AbortOptions {
|
|
45
|
+
priority?: number
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface DialerInit {
|
|
49
|
+
addressSorter?: AddressSorter
|
|
50
|
+
maxParallelDials?: number
|
|
51
|
+
maxPeerAddrsToDial?: number
|
|
52
|
+
maxParallelDialsPerPeer?: number
|
|
53
|
+
dialTimeout?: number
|
|
54
|
+
resolvers?: Record<string, Resolver>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const defaultOptions = {
|
|
58
|
+
addressSorter: publicAddressesFirst,
|
|
59
|
+
maxParallelDials: MAX_PARALLEL_DIALS,
|
|
60
|
+
maxPeerAddrsToDial: MAX_PEER_ADDRS_TO_DIAL,
|
|
61
|
+
maxParallelDialsPerPeer: MAX_PARALLEL_DIALS_PER_PEER,
|
|
62
|
+
dialTimeout: DIAL_TIMEOUT,
|
|
63
|
+
resolvers: {
|
|
64
|
+
dnsaddr: dnsaddrResolver
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface DialQueueComponents {
|
|
69
|
+
peerId: PeerId
|
|
70
|
+
metrics?: Metrics
|
|
71
|
+
peerStore: PeerStore
|
|
72
|
+
transportManager: TransportManager
|
|
73
|
+
connectionGater: ConnectionGater
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class DialQueue {
|
|
77
|
+
public pendingDials: PendingDial[]
|
|
78
|
+
public queue: PQueue
|
|
79
|
+
private readonly peerId: PeerId
|
|
80
|
+
private readonly peerStore: PeerStore
|
|
81
|
+
private readonly connectionGater: ConnectionGater
|
|
82
|
+
private readonly transportManager: TransportManager
|
|
83
|
+
private readonly addressSorter: AddressSorter
|
|
84
|
+
private readonly maxPeerAddrsToDial: number
|
|
85
|
+
private readonly maxParallelDialsPerPeer: number
|
|
86
|
+
private readonly dialTimeout: number
|
|
87
|
+
private readonly inProgressDialCount?: Metric
|
|
88
|
+
private readonly pendingDialCount?: Metric
|
|
89
|
+
private readonly shutDownController: AbortController
|
|
90
|
+
|
|
91
|
+
constructor (components: DialQueueComponents, init: DialerInit = {}) {
|
|
92
|
+
this.addressSorter = init.addressSorter ?? defaultOptions.addressSorter
|
|
93
|
+
this.maxPeerAddrsToDial = init.maxPeerAddrsToDial ?? defaultOptions.maxPeerAddrsToDial
|
|
94
|
+
this.maxParallelDialsPerPeer = init.maxParallelDialsPerPeer ?? defaultOptions.maxParallelDialsPerPeer
|
|
95
|
+
this.dialTimeout = init.dialTimeout ?? defaultOptions.dialTimeout
|
|
96
|
+
|
|
97
|
+
this.peerId = components.peerId
|
|
98
|
+
this.peerStore = components.peerStore
|
|
99
|
+
this.connectionGater = components.connectionGater
|
|
100
|
+
this.transportManager = components.transportManager
|
|
101
|
+
this.shutDownController = new AbortController()
|
|
102
|
+
|
|
103
|
+
this.pendingDialCount = components.metrics?.registerMetric('libp2p_dialler_pending_dials')
|
|
104
|
+
this.inProgressDialCount = components.metrics?.registerMetric('libp2p_dialler_in_progress_dials')
|
|
105
|
+
this.pendingDials = []
|
|
106
|
+
|
|
107
|
+
for (const [key, value] of Object.entries(init.resolvers ?? {})) {
|
|
108
|
+
resolvers.set(key, value)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// controls dial concurrency
|
|
112
|
+
this.queue = new PQueue({
|
|
113
|
+
concurrency: init.maxParallelDials ?? defaultOptions.maxParallelDials
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
// a job was added to the queue
|
|
117
|
+
this.queue.on('add', () => {
|
|
118
|
+
this.pendingDialCount?.update(this.queue.size)
|
|
119
|
+
this.inProgressDialCount?.update(this.queue.pending)
|
|
120
|
+
})
|
|
121
|
+
// a queued job started
|
|
122
|
+
this.queue.on('active', () => {
|
|
123
|
+
this.pendingDialCount?.update(this.queue.size)
|
|
124
|
+
this.inProgressDialCount?.update(this.queue.pending)
|
|
125
|
+
})
|
|
126
|
+
// a started job completed without error
|
|
127
|
+
this.queue.on('completed', () => {
|
|
128
|
+
this.pendingDialCount?.update(this.queue.size)
|
|
129
|
+
this.inProgressDialCount?.update(this.queue.pending)
|
|
130
|
+
})
|
|
131
|
+
// a started job errored
|
|
132
|
+
this.queue.on('error', (err) => {
|
|
133
|
+
log.error('error in dial queue', err)
|
|
134
|
+
this.pendingDialCount?.update(this.queue.size)
|
|
135
|
+
this.inProgressDialCount?.update(this.queue.pending)
|
|
136
|
+
})
|
|
137
|
+
// all queued jobs have been started
|
|
138
|
+
this.queue.on('empty', () => {
|
|
139
|
+
this.pendingDialCount?.update(this.queue.size)
|
|
140
|
+
this.inProgressDialCount?.update(this.queue.pending)
|
|
141
|
+
})
|
|
142
|
+
// add started jobs have run and the queue is empty
|
|
143
|
+
this.queue.on('idle', () => {
|
|
144
|
+
this.pendingDialCount?.update(this.queue.size)
|
|
145
|
+
this.inProgressDialCount?.update(this.queue.pending)
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Clears any pending dials
|
|
151
|
+
*/
|
|
152
|
+
stop (): void {
|
|
153
|
+
this.shutDownController.abort()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Connects to a given peer, multiaddr or list of multiaddrs.
|
|
158
|
+
*
|
|
159
|
+
* If a peer is passed, all known multiaddrs will be tried. If a multiaddr or
|
|
160
|
+
* multiaddrs are passed only those will be dialled.
|
|
161
|
+
*
|
|
162
|
+
* Where a list of multiaddrs is passed, if any contain a peer id then all
|
|
163
|
+
* multiaddrs in the list must contain the same peer id.
|
|
164
|
+
*
|
|
165
|
+
* The dial to the first address that is successfully able to upgrade a connection
|
|
166
|
+
* will be used, all other dials will be aborted when that happens.
|
|
167
|
+
*/
|
|
168
|
+
async dial (peerIdOrMultiaddr: PeerId | Multiaddr | Multiaddr[], options: DialOptions = {}): Promise<Connection> {
|
|
169
|
+
const { peerId, multiaddrs } = getPeerAddress(peerIdOrMultiaddr)
|
|
170
|
+
|
|
171
|
+
const addrs: Address[] = multiaddrs.map(multiaddr => ({
|
|
172
|
+
multiaddr,
|
|
173
|
+
isCertified: false
|
|
174
|
+
}))
|
|
175
|
+
|
|
176
|
+
// create abort conditions - need to do this before `calculateMultiaddrs` as we may be about to
|
|
177
|
+
// resolve a dns addr which can time out
|
|
178
|
+
const { timeoutController, signal } = this.createDialAbortControllers(options.signal)
|
|
179
|
+
let addrsToDial: Address[]
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
// load addresses from address book, resolve and dnsaddrs, filter undiallables, add peer IDs, etc
|
|
183
|
+
addrsToDial = await this.calculateMultiaddrs(peerId, addrs, {
|
|
184
|
+
...options,
|
|
185
|
+
signal
|
|
186
|
+
})
|
|
187
|
+
} catch (err) {
|
|
188
|
+
signal.clear()
|
|
189
|
+
timeoutController.clear()
|
|
190
|
+
throw err
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ready to dial, all async work finished - make sure we don't have any
|
|
194
|
+
// pending dials in progress for this peer or set of multiaddrs
|
|
195
|
+
const existingDial = this.pendingDials.find(dial => {
|
|
196
|
+
// is the dial for the same peer id?
|
|
197
|
+
if (dial.peerId != null && peerId != null && dial.peerId.equals(peerId)) {
|
|
198
|
+
return true
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// is the dial for the same set of multiaddrs?
|
|
202
|
+
if (addrsToDial.map(({ multiaddr }) => multiaddr.toString()).join() === dial.multiaddrs.map(multiaddr => multiaddr.toString()).join()) {
|
|
203
|
+
return true
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return false
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
if (existingDial != null) {
|
|
210
|
+
log('joining existing dial target for %p', peerId)
|
|
211
|
+
signal.clear()
|
|
212
|
+
timeoutController.clear()
|
|
213
|
+
return await existingDial.promise
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
log('creating dial target for', addrsToDial.map(({ multiaddr }) => multiaddr.toString()))
|
|
217
|
+
// @ts-expect-error .promise property is set below
|
|
218
|
+
const pendingDial: PendingDial = {
|
|
219
|
+
id: randomId(),
|
|
220
|
+
status: 'queued',
|
|
221
|
+
peerId,
|
|
222
|
+
multiaddrs: addrsToDial.map(({ multiaddr }) => multiaddr)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
pendingDial.promise = this.performDial(pendingDial, {
|
|
226
|
+
...options,
|
|
227
|
+
signal
|
|
228
|
+
})
|
|
229
|
+
.finally(() => {
|
|
230
|
+
// remove our pending dial entry
|
|
231
|
+
this.pendingDials = this.pendingDials.filter(p => p.id !== pendingDial.id)
|
|
232
|
+
|
|
233
|
+
// clean up abort signals/controllers
|
|
234
|
+
signal.clear()
|
|
235
|
+
timeoutController.clear()
|
|
236
|
+
})
|
|
237
|
+
.catch(err => {
|
|
238
|
+
log.error('dial failed to %s', addrsToDial.map(({ multiaddr }) => multiaddr.toString()).join(', '), err)
|
|
239
|
+
|
|
240
|
+
// Error is a timeout
|
|
241
|
+
if (timeoutController.signal.aborted) {
|
|
242
|
+
const error = new CodeError(err.message, codes.ERR_TIMEOUT)
|
|
243
|
+
throw error
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
throw err
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
// let other dials join this one
|
|
250
|
+
this.pendingDials.push(pendingDial)
|
|
251
|
+
|
|
252
|
+
return await pendingDial.promise
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private createDialAbortControllers (userSignal?: AbortSignal): { timeoutController: TimeoutController, signal: ClearableSignal } {
|
|
256
|
+
// ensure we throw if the dial takes longer than the dial timeout
|
|
257
|
+
const timeoutController = new TimeoutController(this.dialTimeout)
|
|
258
|
+
|
|
259
|
+
// let any signal abort the dial
|
|
260
|
+
const signal = combineSignals(
|
|
261
|
+
timeoutController.signal,
|
|
262
|
+
this.shutDownController.signal,
|
|
263
|
+
userSignal
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
return { timeoutController, signal }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private async calculateMultiaddrs (peerId?: PeerId, addrs: Address[] = [], options: DialOptions = {}): Promise<Address[]> {
|
|
270
|
+
// if a peer id or multiaddr(s) with a peer id, make sure it isn't our peer id and that we are allowed to dial it
|
|
271
|
+
if (peerId != null) {
|
|
272
|
+
if (this.peerId.equals(peerId)) {
|
|
273
|
+
throw new CodeError('Tried to dial self', codes.ERR_DIALED_SELF)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if ((await this.connectionGater.denyDialPeer?.(peerId)) === true) {
|
|
277
|
+
throw new CodeError('The dial request is blocked by gater.allowDialPeer', codes.ERR_PEER_DIAL_INTERCEPTED)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// if just a peer id was passed, load available multiaddrs for this peer from the address book
|
|
281
|
+
if (addrs.length === 0) {
|
|
282
|
+
log('loading multiaddrs for %p', peerId)
|
|
283
|
+
addrs.push(...(await this.peerStore.addressBook.get(peerId)))
|
|
284
|
+
log('loaded multiaddrs for %p', peerId, addrs.map(({ multiaddr }) => multiaddr.toString()))
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// resolve addresses - this can result in a one-to-many translation when dnsaddrs are resolved
|
|
289
|
+
const resolvedAddresses = (await Promise.all(
|
|
290
|
+
addrs.map(async addr => {
|
|
291
|
+
const result = await resolveMultiaddrs(addr.multiaddr, options)
|
|
292
|
+
|
|
293
|
+
if (result.length === 1 && result[0].equals(addr.multiaddr)) {
|
|
294
|
+
return addr
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return result.map(multiaddr => ({
|
|
298
|
+
multiaddr,
|
|
299
|
+
isCertified: false
|
|
300
|
+
}))
|
|
301
|
+
})
|
|
302
|
+
))
|
|
303
|
+
.flat()
|
|
304
|
+
|
|
305
|
+
// filter out any multiaddrs that we do not have transports for
|
|
306
|
+
const filteredAddrs = resolvedAddresses.filter(addr => Boolean(this.transportManager.transportForMultiaddr(addr.multiaddr)))
|
|
307
|
+
|
|
308
|
+
// deduplicate addresses
|
|
309
|
+
const dedupedAddrs: Map<string, Address> = new Map()
|
|
310
|
+
|
|
311
|
+
for (const addr of filteredAddrs) {
|
|
312
|
+
const maStr = addr.multiaddr.toString()
|
|
313
|
+
const existing = dedupedAddrs.get(maStr)
|
|
314
|
+
|
|
315
|
+
if (existing != null) {
|
|
316
|
+
existing.isCertified = existing.isCertified || addr.isCertified || false
|
|
317
|
+
continue
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
dedupedAddrs.set(maStr, addr)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
let dedupedMultiaddrs = [...dedupedAddrs.values()]
|
|
324
|
+
|
|
325
|
+
if (dedupedMultiaddrs.length === 0 || dedupedMultiaddrs.length > this.maxPeerAddrsToDial) {
|
|
326
|
+
log('addresses for %p before filtering', peerId ?? 'unknown peer', resolvedAddresses.map(({ multiaddr }) => multiaddr.toString()))
|
|
327
|
+
log('addresses for %p after filtering', peerId ?? 'unknown peer', dedupedMultiaddrs.map(({ multiaddr }) => multiaddr.toString()))
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// make sure we actually have some addresses to dial
|
|
331
|
+
if (dedupedMultiaddrs.length === 0) {
|
|
332
|
+
throw new CodeError('The dial request has no valid addresses', codes.ERR_NO_VALID_ADDRESSES)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// make sure we don't have too many addresses to dial
|
|
336
|
+
if (dedupedMultiaddrs.length > this.maxPeerAddrsToDial) {
|
|
337
|
+
throw new CodeError('dial with more addresses than allowed', codes.ERR_TOO_MANY_ADDRESSES)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ensure the peer id is appended to the multiaddr
|
|
341
|
+
if (peerId != null) {
|
|
342
|
+
const peerIdMultiaddr = `/p2p/${peerId.toString()}`
|
|
343
|
+
dedupedMultiaddrs = dedupedMultiaddrs.map(addr => {
|
|
344
|
+
const addressPeerId = addr.multiaddr.getPeerId()
|
|
345
|
+
const lastProto = addr.multiaddr.protos().pop()
|
|
346
|
+
|
|
347
|
+
// do not append peer id to path multiaddrs
|
|
348
|
+
if (lastProto?.path === true) {
|
|
349
|
+
return addr
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// append peer id to multiaddr if it is not already present
|
|
353
|
+
if (addressPeerId !== peerId.toString()) {
|
|
354
|
+
return {
|
|
355
|
+
multiaddr: addr.multiaddr.encapsulate(peerIdMultiaddr),
|
|
356
|
+
isCertified: addr.isCertified
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return addr
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const gatedAdrs: Address[] = []
|
|
365
|
+
|
|
366
|
+
for (const addr of dedupedMultiaddrs) {
|
|
367
|
+
if (this.connectionGater.denyDialMultiaddr != null && await this.connectionGater.denyDialMultiaddr(addr.multiaddr)) {
|
|
368
|
+
continue
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
gatedAdrs.push(addr)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return gatedAdrs.sort(this.addressSorter)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
private async performDial (pendingDial: PendingDial, options: DialOptions = {}): Promise<Connection> {
|
|
378
|
+
const dialAbortControllers: Array<(AbortController | undefined)> = pendingDial.multiaddrs.map(() => new AbortController())
|
|
379
|
+
|
|
380
|
+
try {
|
|
381
|
+
// internal peer dial queue to ensure we only dial the configured number of addresses
|
|
382
|
+
// per peer at the same time to prevent one peer with a lot of addresses swamping
|
|
383
|
+
// the dial queue
|
|
384
|
+
const peerDialQueue = new PQueue({
|
|
385
|
+
concurrency: this.maxParallelDialsPerPeer
|
|
386
|
+
})
|
|
387
|
+
peerDialQueue.on('error', (err) => {
|
|
388
|
+
log.error('error dialling', err)
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
const conn = await Promise.any(pendingDial.multiaddrs.map(async (addr, i) => {
|
|
392
|
+
const controller = dialAbortControllers[i]
|
|
393
|
+
|
|
394
|
+
if (controller == null) {
|
|
395
|
+
throw new CodeError('dialAction did not come with an AbortController', codes.ERR_INVALID_PARAMETERS)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// let any signal abort the dial
|
|
399
|
+
const signal = combineSignals(controller.signal, options.signal)
|
|
400
|
+
signal.addEventListener('abort', () => {
|
|
401
|
+
log('dial to %s aborted', addr)
|
|
402
|
+
})
|
|
403
|
+
const deferred = pDefer<Connection>()
|
|
404
|
+
|
|
405
|
+
await peerDialQueue.add(async () => {
|
|
406
|
+
if (signal.aborted) {
|
|
407
|
+
log('dial to %s was aborted before reaching the head of the peer dial queue', addr)
|
|
408
|
+
deferred.reject(new AbortError())
|
|
409
|
+
return
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// add the individual dial to the dial queue so we don't breach maxConcurrentDials
|
|
413
|
+
await this.queue.add(async () => {
|
|
414
|
+
try {
|
|
415
|
+
if (signal.aborted) {
|
|
416
|
+
log('dial to %s was aborted before reaching the head of the dial queue', addr)
|
|
417
|
+
deferred.reject(new AbortError())
|
|
418
|
+
return
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// update dial status
|
|
422
|
+
pendingDial.status = 'active'
|
|
423
|
+
|
|
424
|
+
const conn = await this.transportManager.dial(addr, {
|
|
425
|
+
...options,
|
|
426
|
+
signal
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
if (controller.signal.aborted) {
|
|
430
|
+
// another dial succeeded faster than this one
|
|
431
|
+
log('multiple dials succeeded, closing superfluous connection')
|
|
432
|
+
|
|
433
|
+
conn.close().catch(err => {
|
|
434
|
+
log.error('error closing superfluous connection', err)
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
deferred.reject(new AbortError())
|
|
438
|
+
return
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// remove the successful AbortController so it is not aborted
|
|
442
|
+
dialAbortControllers[i] = undefined
|
|
443
|
+
|
|
444
|
+
// immediately abort any other dials
|
|
445
|
+
dialAbortControllers.forEach(c => {
|
|
446
|
+
if (c !== undefined) {
|
|
447
|
+
c.abort()
|
|
448
|
+
}
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
log('dial to %s succeeded', addr)
|
|
452
|
+
|
|
453
|
+
// resolve the connection promise
|
|
454
|
+
deferred.resolve(conn)
|
|
455
|
+
} catch (err: any) {
|
|
456
|
+
// something only went wrong if our signal was not aborted
|
|
457
|
+
log.error('error during dial of %s', addr, err)
|
|
458
|
+
deferred.reject(err)
|
|
459
|
+
}
|
|
460
|
+
}, {
|
|
461
|
+
...options,
|
|
462
|
+
signal
|
|
463
|
+
}).catch(err => {
|
|
464
|
+
deferred.reject(err)
|
|
465
|
+
})
|
|
466
|
+
}, {
|
|
467
|
+
signal
|
|
468
|
+
}).catch(err => {
|
|
469
|
+
deferred.reject(err)
|
|
470
|
+
}).finally(() => {
|
|
471
|
+
signal.clear()
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
return await deferred.promise
|
|
475
|
+
}))
|
|
476
|
+
|
|
477
|
+
// dial succeeded or failed
|
|
478
|
+
if (conn == null) {
|
|
479
|
+
throw new CodeError('successful dial led to empty object returned from peer dial queue', codes.ERR_TRANSPORT_DIAL_FAILED)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
pendingDial.status = 'success'
|
|
483
|
+
|
|
484
|
+
return conn
|
|
485
|
+
} catch (err: any) {
|
|
486
|
+
pendingDial.status = 'error'
|
|
487
|
+
|
|
488
|
+
// if we only dialled one address, unwrap the AggregateError to provide more
|
|
489
|
+
// useful feedback to the user
|
|
490
|
+
if (pendingDial.multiaddrs.length === 1 && err.name === 'AggregateError') {
|
|
491
|
+
throw err.errors[0]
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
throw err
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Returns a random string
|
|
501
|
+
*/
|
|
502
|
+
function randomId (): string {
|
|
503
|
+
return `${(parseInt(String(Math.random() * 1e9), 10)).toString()}${Date.now()}`
|
|
504
|
+
}
|