libp2p 2.7.3 → 2.7.4-0555339ba

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.
@@ -203,7 +203,7 @@ export class DialQueue {
203
203
 
204
204
  options.onProgress?.(new CustomProgressEvent('dial-queue:add-to-dial-queue'))
205
205
  return this.queue.add(async (options) => {
206
- options?.onProgress?.(new CustomProgressEvent('dial-queue:start-dial'))
206
+ options.onProgress?.(new CustomProgressEvent('dial-queue:start-dial'))
207
207
  // create abort conditions - need to do this before `calculateMultiaddrs` as
208
208
  // we may be about to resolve a dns addr which can time out
209
209
  const signal = anySignal([
@@ -212,103 +212,145 @@ export class DialQueue {
212
212
  ])
213
213
  setMaxListeners(Infinity, signal)
214
214
 
215
- let addrsToDial: Address[]
216
-
217
215
  try {
218
- // load addresses from address book, resolve and dnsaddrs, filter
219
- // undialables, add peer IDs, etc
220
- addrsToDial = await this.calculateMultiaddrs(peerId, options?.multiaddrs, {
221
- ...options,
222
- signal
223
- })
216
+ return await this.dialPeer(options, signal)
217
+ } finally {
218
+ // clean up abort signals/controllers
219
+ signal.clear()
220
+ }
221
+ }, {
222
+ peerId,
223
+ priority: options.priority ?? DEFAULT_DIAL_PRIORITY,
224
+ multiaddrs: new Set(multiaddrs.map(ma => ma.toString())),
225
+ signal: options.signal ?? AbortSignal.timeout(this.dialTimeout),
226
+ onProgress: options.onProgress
227
+ })
228
+ }
224
229
 
225
- options?.onProgress?.(new CustomProgressEvent<Address[]>('dial-queue:calculated-addresses', addrsToDial))
230
+ private async dialPeer (options: DialQueueJobOptions, signal: AbortSignal): Promise<Connection> {
231
+ const peerId = options.peerId
232
+ const multiaddrs = options.multiaddrs
233
+ const failedMultiaddrs = new Set<string>()
226
234
 
227
- addrsToDial.map(({ multiaddr }) => multiaddr.toString()).forEach(addr => {
228
- options?.multiaddrs.add(addr)
229
- })
230
- } catch (err) {
231
- signal.clear()
232
- throw err
235
+ // if we have no multiaddrs, only a peer id, set a flag so we will look the
236
+ // peer up in the peer routing to obtain multiaddrs
237
+ let forcePeerLookup = options.multiaddrs.size === 0
238
+
239
+ let dialed = 0
240
+ let dialIteration = 0
241
+ const errors: Error[] = []
242
+
243
+ this.log('starting dial to %p', peerId)
244
+
245
+ // repeat this operation in case addresses are added to the dial while we
246
+ // resolve multiaddrs, etc
247
+ while (forcePeerLookup || multiaddrs.size > 0) {
248
+ dialIteration++
249
+
250
+ // only perform peer lookup once
251
+ forcePeerLookup = false
252
+
253
+ // the addresses we will dial
254
+ const addrsToDial: Address[] = []
255
+
256
+ // copy the addresses into a new set
257
+ const addrs = new Set(options.multiaddrs)
258
+
259
+ // empty the old set - subsequent dial attempts for the same peer id may
260
+ // add more addresses to try
261
+ multiaddrs.clear()
262
+
263
+ this.log('calculating addrs to dial %p from %s', peerId, [...addrs])
264
+
265
+ // load addresses from address book, resolve and dnsaddrs, filter
266
+ // undialables, add peer IDs, etc
267
+ const calculatedAddrs = await this.calculateMultiaddrs(peerId, addrs, {
268
+ ...options,
269
+ signal
270
+ })
271
+
272
+ for (const addr of calculatedAddrs) {
273
+ // skip any addresses we have previously failed to dial
274
+ if (failedMultiaddrs.has(addr.multiaddr.toString())) {
275
+ this.log.trace('skipping previously failed multiaddr %a while dialing %p', addr.multiaddr, peerId)
276
+ continue
277
+ }
278
+
279
+ addrsToDial.push(addr)
233
280
  }
234
281
 
235
- try {
236
- let dialed = 0
237
- const errors: Error[] = []
282
+ this.log('%s dial to %p with %s', dialIteration === 1 ? 'starting' : 'continuing', peerId, addrsToDial.map(ma => ma.multiaddr.toString()))
238
283
 
239
- for (const address of addrsToDial) {
240
- if (dialed === this.maxPeerAddrsToDial) {
241
- this.log('dialed maxPeerAddrsToDial (%d) addresses for %p, not trying any others', dialed, peerId)
284
+ options?.onProgress?.(new CustomProgressEvent<Address[]>('dial-queue:calculated-addresses', addrsToDial))
242
285
 
243
- throw new DialError('Peer had more than maxPeerAddrsToDial')
244
- }
286
+ for (const address of addrsToDial) {
287
+ if (dialed === this.maxPeerAddrsToDial) {
288
+ this.log('dialed maxPeerAddrsToDial (%d) addresses for %p, not trying any others', dialed, options.peerId)
245
289
 
246
- dialed++
290
+ throw new DialError('Peer had more than maxPeerAddrsToDial')
291
+ }
247
292
 
293
+ dialed++
294
+
295
+ try {
296
+ // try to dial the address
297
+ const conn = await this.components.transportManager.dial(address.multiaddr, {
298
+ ...options,
299
+ signal
300
+ })
301
+
302
+ this.log('dial to %a succeeded', address.multiaddr)
303
+
304
+ // record the successful dial and the address
248
305
  try {
249
- const conn = await this.components.transportManager.dial(address.multiaddr, {
250
- ...options,
251
- signal
306
+ await this.components.peerStore.merge(conn.remotePeer, {
307
+ multiaddrs: [
308
+ conn.remoteAddr
309
+ ],
310
+ metadata: {
311
+ [LAST_DIAL_SUCCESS_KEY]: uint8ArrayFromString(Date.now().toString())
312
+ }
252
313
  })
314
+ } catch (err: any) {
315
+ this.log.error('could not update last dial failure key for %p', peerId, err)
316
+ }
317
+
318
+ // dial successful, return the connection
319
+ return conn
320
+ } catch (err: any) {
321
+ this.log.error('dial failed to %a', address.multiaddr, err)
253
322
 
254
- this.log('dial to %a succeeded', address.multiaddr)
323
+ // ensure we don't dial it again in this attempt
324
+ failedMultiaddrs.add(address.multiaddr.toString())
255
325
 
256
- // record the successful dial and the address
326
+ if (peerId != null) {
327
+ // record the failed dial
257
328
  try {
258
- await this.components.peerStore.merge(conn.remotePeer, {
259
- multiaddrs: [
260
- conn.remoteAddr
261
- ],
329
+ await this.components.peerStore.merge(peerId, {
262
330
  metadata: {
263
- [LAST_DIAL_SUCCESS_KEY]: uint8ArrayFromString(Date.now().toString())
331
+ [LAST_DIAL_FAILURE_KEY]: uint8ArrayFromString(Date.now().toString())
264
332
  }
265
333
  })
266
334
  } catch (err: any) {
267
335
  this.log.error('could not update last dial failure key for %p', peerId, err)
268
336
  }
337
+ }
269
338
 
270
- return conn
271
- } catch (err: any) {
272
- this.log.error('dial failed to %a', address.multiaddr, err)
273
-
274
- if (peerId != null) {
275
- // record the failed dial
276
- try {
277
- await this.components.peerStore.merge(peerId, {
278
- metadata: {
279
- [LAST_DIAL_FAILURE_KEY]: uint8ArrayFromString(Date.now().toString())
280
- }
281
- })
282
- } catch (err: any) {
283
- this.log.error('could not update last dial failure key for %p', peerId, err)
284
- }
285
- }
286
-
287
- // the user/dial timeout/shutdown controller signal aborted
288
- if (signal.aborted) {
289
- throw new TimeoutError(err.message)
290
- }
291
-
292
- errors.push(err)
339
+ // the user/dial timeout/shutdown controller signal aborted
340
+ if (signal.aborted) {
341
+ throw new TimeoutError(err.message)
293
342
  }
294
- }
295
343
 
296
- if (errors.length === 1) {
297
- throw errors[0]
344
+ errors.push(err)
298
345
  }
299
-
300
- throw new AggregateError(errors, 'All multiaddr dials failed')
301
- } finally {
302
- // clean up abort signals/controllers
303
- signal.clear()
304
346
  }
305
- }, {
306
- peerId,
307
- priority: options.priority ?? DEFAULT_DIAL_PRIORITY,
308
- multiaddrs: new Set(multiaddrs.map(ma => ma.toString())),
309
- signal: options.signal ?? AbortSignal.timeout(this.dialTimeout),
310
- onProgress: options.onProgress
311
- })
347
+ }
348
+
349
+ if (errors.length === 1) {
350
+ throw errors[0]
351
+ }
352
+
353
+ throw new AggregateError(errors, 'All multiaddr dials failed')
312
354
  }
313
355
 
314
356
  // eslint-disable-next-line complexity
@@ -358,8 +400,10 @@ export class DialQueue {
358
400
  isCertified: false
359
401
  })))
360
402
  } catch (err: any) {
361
- if (err.name !== 'NoPeerRoutersError') {
362
- this.log.error('looking up multiaddrs for %p in the peer routing failed', peerId, err)
403
+ if (err.name === 'NoPeerRoutersError') {
404
+ this.log('no peer routers configured', peerId)
405
+ } else {
406
+ this.log.error('looking up multiaddrs for %p in the peer routing failed - %e', peerId, err)
363
407
  }
364
408
  }
365
409
  }
package/src/errors.ts CHANGED
@@ -59,17 +59,24 @@ export class DialDeniedError extends Error {
59
59
  }
60
60
  }
61
61
 
62
- export class NoValidAddressesError extends Error {
63
- constructor (message = 'No valid addresses') {
62
+ export class UnsupportedListenAddressError extends Error {
63
+ constructor (message = 'No transport was configured to listen on this address') {
64
64
  super(message)
65
- this.name = 'NoValidAddressesError'
65
+ this.name = 'UnsupportedListenAddressError'
66
+ }
67
+ }
68
+
69
+ export class UnsupportedListenAddressesError extends Error {
70
+ constructor (message = 'Configured listen addresses could not be listened on') {
71
+ super(message)
72
+ this.name = 'UnsupportedListenAddressesError'
66
73
  }
67
74
  }
68
75
 
69
- export class NoSupportedAddressesError extends Error {
70
- constructor (message = 'No supported addresses') {
76
+ export class NoValidAddressesError extends Error {
77
+ constructor (message = 'No valid addresses') {
71
78
  super(message)
72
- this.name = 'NoSupportedAddressesError'
79
+ this.name = 'NoValidAddressesError'
73
80
  }
74
81
  }
75
82
 
@@ -1,7 +1,8 @@
1
1
  import { FaultTolerance, InvalidParametersError, NotStartedError } from '@libp2p/interface'
2
2
  import { trackedMap } from '@libp2p/utils/tracked-map'
3
+ import { IP4, IP6 } from '@multiformats/multiaddr-matcher'
3
4
  import { CustomProgressEvent } from 'progress-events'
4
- import { NoValidAddressesError, TransportUnavailableError } from './errors.js'
5
+ import { TransportUnavailableError, UnsupportedListenAddressError, UnsupportedListenAddressesError } from './errors.js'
5
6
  import type { Libp2pEvents, ComponentLogger, Logger, Connection, TypedEventTarget, Metrics, Startable, Listener, Transport, Upgrader } from '@libp2p/interface'
6
7
  import type { AddressManager, TransportManager, TransportManagerDialOptions } from '@libp2p/interface-internal'
7
8
  import type { Multiaddr } from '@multiformats/multiaddr'
@@ -18,6 +19,17 @@ export interface DefaultTransportManagerComponents {
18
19
  logger: ComponentLogger
19
20
  }
20
21
 
22
+ interface IPStats {
23
+ success: number
24
+ attempts: number
25
+ }
26
+
27
+ interface ListenStats {
28
+ errors: Map<string, Error>
29
+ ipv4: IPStats
30
+ ipv6: IPStats
31
+ }
32
+
21
33
  export class DefaultTransportManager implements TransportManager, Startable {
22
34
  private readonly log: Logger
23
35
  private readonly components: DefaultTransportManagerComponents
@@ -192,11 +204,28 @@ export class DefaultTransportManager implements TransportManager, Startable {
192
204
  return
193
205
  }
194
206
 
195
- const couldNotListen = []
207
+ // track IPv4/IPv6 results - if we succeed on IPv4 but all IPv6 attempts
208
+ // fail then we are probably on a network without IPv6 support
209
+ const listenStats: ListenStats = {
210
+ errors: new Map(),
211
+ ipv4: {
212
+ success: 0,
213
+ attempts: 0
214
+ },
215
+ ipv6: {
216
+ success: 0,
217
+ attempts: 0
218
+ }
219
+ }
220
+
221
+ addrs.forEach(ma => {
222
+ listenStats.errors.set(ma.toString(), new UnsupportedListenAddressError())
223
+ })
224
+
225
+ const tasks: Array<Promise<void>> = []
196
226
 
197
227
  for (const [key, transport] of this.transports.entries()) {
198
228
  const supportedAddrs = transport.listenFilter(addrs)
199
- const tasks = []
200
229
 
201
230
  // For each supported multiaddr, create a listener
202
231
  for (const addr of supportedAddrs) {
@@ -231,36 +260,75 @@ export class DefaultTransportManager implements TransportManager, Startable {
231
260
  })
232
261
  })
233
262
 
263
+ // track IPv4/IPv6 support
264
+ if (IP4.matches(addr)) {
265
+ listenStats.ipv4.attempts++
266
+ } else if (IP6.matches(addr)) {
267
+ listenStats.ipv6.attempts++
268
+ }
269
+
234
270
  // We need to attempt to listen on everything
235
- tasks.push(listener.listen(addr))
271
+ tasks.push(
272
+ listener.listen(addr)
273
+ .then(() => {
274
+ listenStats.errors.delete(addr.toString())
275
+
276
+ if (IP4.matches(addr)) {
277
+ listenStats.ipv4.success++
278
+ }
279
+
280
+ if (IP6.matches(addr)) {
281
+ listenStats.ipv6.success++
282
+ }
283
+ }, (err) => {
284
+ this.log.error('transport %s could not listen on address %a - %e', key, addr, err)
285
+ listenStats.errors.set(addr.toString(), err)
286
+ throw err
287
+ })
288
+ )
236
289
  }
290
+ }
237
291
 
238
- // Keep track of transports we had no addresses for
239
- if (tasks.length === 0) {
240
- couldNotListen.push(key)
241
- continue
242
- }
292
+ const results = await Promise.allSettled(tasks)
243
293
 
244
- const results = await Promise.allSettled(tasks)
245
- // If we are listening on at least 1 address, succeed.
246
- // TODO: we should look at adding a retry (`p-retry`) here to better support
247
- // listening on remote addresses as they may be offline. We could then potentially
248
- // just wait for any (`p-any`) listener to succeed on each transport before returning
249
- const isListening = results.find(r => r.status === 'fulfilled')
250
- if ((isListening == null) && this.faultTolerance !== FaultTolerance.NO_FATAL) {
251
- throw new NoValidAddressesError(`Transport (${key}) could not listen on any available address`)
252
- }
294
+ // listening on all addresses, all good
295
+ if (results.length > 0 && results.every(res => res.status === 'fulfilled')) {
296
+ return
253
297
  }
254
298
 
255
- // If no transports were able to listen, throw an error. This likely
256
- // means we were given addresses we do not have transports for
257
- if (couldNotListen.length === this.transports.size) {
258
- const message = `no valid addresses were provided for transports [${couldNotListen.join(', ')}]`
259
- if (this.faultTolerance === FaultTolerance.FATAL_ALL) {
260
- throw new NoValidAddressesError(message)
261
- }
262
- this.log(`libp2p in dial mode only: ${message}`)
299
+ // detect lack of IPv6 support on the current network - if we tried to
300
+ // listen on IPv4 and IPv6 addresses, and all IPv4 addresses succeeded but
301
+ // all IPv6 addresses fail, then we can assume there's no IPv6 here
302
+ if (this.ipv6Unsupported(listenStats)) {
303
+ this.log('all IPv4 addresses succeed but all IPv6 failed')
304
+ return
305
+ }
306
+
307
+ if (this.faultTolerance === FaultTolerance.NO_FATAL) {
308
+ // ok to be dial-only
309
+ this.log('failed to listen on any address but fault tolerance allows this')
310
+ return
263
311
  }
312
+
313
+ // if a configured address was not able to be listened on, throw an error
314
+ throw new UnsupportedListenAddressesError(`Some configured addresses failed to be listened on, you may need to remove one or more listen addresses from your configuration or set \`transportManager.faultTolerance\` to NO_FATAL:\n${
315
+ [...listenStats.errors.entries()].map(([addr, err]) => {
316
+ return `
317
+ ${addr}: ${`${err.stack ?? err}`.split('\n').join('\n ')}
318
+ `
319
+ }).join('')
320
+ }`)
321
+ }
322
+
323
+ private ipv6Unsupported (listenStats: ListenStats): boolean {
324
+ if (listenStats.ipv4.attempts === 0 || listenStats.ipv6.attempts === 0) {
325
+ return false
326
+ }
327
+
328
+ const allIpv4Succeeded = listenStats.ipv4.attempts === listenStats.ipv4.success
329
+ const allIpv6Failed = listenStats.ipv6.success === 0
330
+
331
+ return allIpv4Succeeded && allIpv6Failed
264
332
  }
265
333
 
266
334
  /**
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
- export const version = '2.7.3'
1
+ export const version = '2.7.4-0555339ba'
2
2
  export const name = 'js-libp2p'
@@ -1,21 +0,0 @@
1
- {
2
- "AddressFilter": "https://libp2p.github.io/js-libp2p/interfaces/libp2p.index.AddressFilter.html",
3
- "AddressManagerInit": "https://libp2p.github.io/js-libp2p/interfaces/libp2p.index.AddressManagerInit.html",
4
- "ConnectionManagerInit": "https://libp2p.github.io/js-libp2p/interfaces/libp2p.index.ConnectionManagerInit.html",
5
- "ConnectionMonitorInit": "https://libp2p.github.io/js-libp2p/interfaces/libp2p.index.ConnectionMonitorInit.html",
6
- "Libp2pInit": "https://libp2p.github.io/js-libp2p/interfaces/libp2p.index.Libp2pInit.html",
7
- ".:Libp2pInit": "https://libp2p.github.io/js-libp2p/interfaces/libp2p.index.Libp2pInit.html",
8
- "TransportManagerInit": "https://libp2p.github.io/js-libp2p/interfaces/libp2p.index.TransportManagerInit.html",
9
- "Libp2pOptions": "https://libp2p.github.io/js-libp2p/types/libp2p.index.Libp2pOptions.html",
10
- ".:Libp2pOptions": "https://libp2p.github.io/js-libp2p/types/libp2p.index.Libp2pOptions.html",
11
- "ServiceFactoryMap": "https://libp2p.github.io/js-libp2p/types/libp2p.index.ServiceFactoryMap.html",
12
- ".:ServiceFactoryMap": "https://libp2p.github.io/js-libp2p/types/libp2p.index.ServiceFactoryMap.html",
13
- "createLibp2p": "https://libp2p.github.io/js-libp2p/functions/libp2p.index.createLibp2p.html",
14
- ".:createLibp2p": "https://libp2p.github.io/js-libp2p/functions/libp2p.index.createLibp2p.html",
15
- "userAgent": "https://libp2p.github.io/js-libp2p/functions/libp2p.user_agent.userAgent.html",
16
- "./user-agent:userAgent": "https://libp2p.github.io/js-libp2p/functions/libp2p.user_agent.userAgent.html",
17
- "name": "https://libp2p.github.io/js-libp2p/variables/libp2p.version.name.html",
18
- "./version:name": "https://libp2p.github.io/js-libp2p/variables/libp2p.version.name.html",
19
- "version": "https://libp2p.github.io/js-libp2p/variables/libp2p.version.version.html",
20
- "./version:version": "https://libp2p.github.io/js-libp2p/variables/libp2p.version.version.html"
21
- }