2019-10-21 16:53:58 +02:00
|
|
|
'use strict'
|
|
|
|
|
2020-12-10 14:48:14 +01:00
|
|
|
const debug = require('debug')
|
|
|
|
const log = Object.assign(debug('libp2p:dialer'), {
|
|
|
|
error: debug('libp2p:dialer:err')
|
|
|
|
})
|
2019-10-21 16:53:58 +02:00
|
|
|
const errCode = require('err-code')
|
2021-04-15 09:40:02 +02:00
|
|
|
const { Multiaddr } = require('multiaddr')
|
|
|
|
// @ts-ignore timeout-abourt-controles does not export types
|
2019-12-10 13:48:34 +01:00
|
|
|
const TimeoutController = require('timeout-abort-controller')
|
2021-06-14 09:19:23 +02:00
|
|
|
const { AbortError } = require('abortable-iterator')
|
2021-01-27 09:45:31 +01:00
|
|
|
const { anySignal } = require('any-signal')
|
2020-04-14 14:05:30 +02:00
|
|
|
|
2020-12-10 14:48:14 +01:00
|
|
|
const DialRequest = require('./dial-request')
|
2020-11-20 15:16:40 +01:00
|
|
|
const { publicAddressesFirst } = require('libp2p-utils/src/address-sort')
|
2020-04-24 13:01:04 +02:00
|
|
|
const getPeer = require('../get-peer')
|
2019-10-21 16:53:58 +02:00
|
|
|
|
2019-12-04 23:04:43 +01:00
|
|
|
const { codes } = require('../errors')
|
2019-10-21 16:53:58 +02:00
|
|
|
const {
|
2019-12-03 10:28:52 +01:00
|
|
|
DIAL_TIMEOUT,
|
2019-10-21 16:53:58 +02:00
|
|
|
MAX_PARALLEL_DIALS,
|
2019-12-04 13:58:23 +01:00
|
|
|
MAX_PER_PEER_DIALS
|
2019-12-04 23:04:43 +01:00
|
|
|
} = require('../constants')
|
2019-10-21 16:53:58 +02:00
|
|
|
|
2020-12-10 14:48:14 +01:00
|
|
|
/**
|
|
|
|
* @typedef {import('libp2p-interfaces/src/connection').Connection} Connection
|
|
|
|
* @typedef {import('peer-id')} PeerId
|
|
|
|
* @typedef {import('../peer-store')} PeerStore
|
|
|
|
* @typedef {import('../peer-store/address-book').Address} Address
|
|
|
|
* @typedef {import('../transport-manager')} TransportManager
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @typedef {Object} DialerProperties
|
|
|
|
* @property {PeerStore} peerStore
|
|
|
|
* @property {TransportManager} transportManager
|
|
|
|
*
|
|
|
|
* @typedef {(addr:Multiaddr) => Promise<string[]>} Resolver
|
|
|
|
*
|
|
|
|
* @typedef {Object} DialerOptions
|
|
|
|
* @property {(addresses: Address[]) => Address[]} [options.addressSorter = publicAddressesFirst] - Sort the known addresses of a peer before trying to dial.
|
2021-04-15 09:40:02 +02:00
|
|
|
* @property {number} [maxParallelDials = MAX_PARALLEL_DIALS] - Number of max concurrent dials.
|
|
|
|
* @property {number} [maxDialsPerPeer = MAX_PER_PEER_DIALS] - Number of max concurrent dials per peer.
|
|
|
|
* @property {number} [dialTimeout = DIAL_TIMEOUT] - How long a dial attempt is allowed to take.
|
2020-12-10 14:48:14 +01:00
|
|
|
* @property {Record<string, Resolver>} [resolvers = {}] - multiaddr resolvers to use when dialing
|
|
|
|
*
|
|
|
|
* @typedef DialTarget
|
|
|
|
* @property {string} id
|
|
|
|
* @property {Multiaddr[]} addrs
|
|
|
|
*
|
|
|
|
* @typedef PendingDial
|
|
|
|
* @property {DialRequest} dialRequest
|
|
|
|
* @property {TimeoutController} controller
|
2021-04-15 09:40:02 +02:00
|
|
|
* @property {Promise<Connection>} promise
|
2020-12-10 14:48:14 +01:00
|
|
|
* @property {function():void} destroy
|
|
|
|
*/
|
|
|
|
|
2019-10-21 16:53:58 +02:00
|
|
|
class Dialer {
|
|
|
|
/**
|
2020-10-06 14:59:43 +02:00
|
|
|
* @class
|
2020-12-10 14:48:14 +01:00
|
|
|
* @param {DialerProperties & DialerOptions} options
|
2019-10-21 16:53:58 +02:00
|
|
|
*/
|
|
|
|
constructor ({
|
|
|
|
transportManager,
|
2019-11-26 16:40:04 +01:00
|
|
|
peerStore,
|
2020-11-20 15:16:40 +01:00
|
|
|
addressSorter = publicAddressesFirst,
|
2021-04-15 09:40:02 +02:00
|
|
|
maxParallelDials = MAX_PARALLEL_DIALS,
|
|
|
|
dialTimeout = DIAL_TIMEOUT,
|
|
|
|
maxDialsPerPeer = MAX_PER_PEER_DIALS,
|
2020-11-04 13:54:50 +01:00
|
|
|
resolvers = {}
|
2019-10-21 16:53:58 +02:00
|
|
|
}) {
|
|
|
|
this.transportManager = transportManager
|
2019-11-26 16:40:04 +01:00
|
|
|
this.peerStore = peerStore
|
2020-11-20 15:16:40 +01:00
|
|
|
this.addressSorter = addressSorter
|
2021-04-15 09:40:02 +02:00
|
|
|
this.maxParallelDials = maxParallelDials
|
|
|
|
this.timeout = dialTimeout
|
|
|
|
this.maxDialsPerPeer = maxDialsPerPeer
|
|
|
|
this.tokens = [...new Array(maxParallelDials)].map((_, index) => index)
|
2019-12-15 17:33:16 +01:00
|
|
|
this._pendingDials = new Map()
|
2021-06-14 09:19:23 +02:00
|
|
|
this._pendingDialTargets = new Map()
|
2020-11-04 13:54:50 +01:00
|
|
|
|
|
|
|
for (const [key, value] of Object.entries(resolvers)) {
|
2021-04-15 09:40:02 +02:00
|
|
|
Multiaddr.resolvers.set(key, value)
|
2020-11-04 13:54:50 +01:00
|
|
|
}
|
2019-12-10 13:48:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clears any pending dials
|
|
|
|
*/
|
|
|
|
destroy () {
|
|
|
|
for (const dial of this._pendingDials.values()) {
|
|
|
|
try {
|
|
|
|
dial.controller.abort()
|
|
|
|
} catch (err) {
|
|
|
|
log.error(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this._pendingDials.clear()
|
2021-06-14 09:19:23 +02:00
|
|
|
|
|
|
|
for (const pendingTarget of this._pendingDialTargets.values()) {
|
|
|
|
pendingTarget.reject(new AbortError('Dialer was destroyed'))
|
|
|
|
}
|
|
|
|
this._pendingDialTargets.clear()
|
2019-10-21 16:53:58 +02:00
|
|
|
}
|
|
|
|
|
2019-12-03 10:28:52 +01:00
|
|
|
/**
|
2020-04-14 14:05:30 +02:00
|
|
|
* Connects to a given `peer` by dialing all of its known addresses.
|
2019-12-15 17:33:16 +01:00
|
|
|
* The dial to the first address that is successfully able to upgrade a connection
|
|
|
|
* will be used.
|
2019-12-03 10:28:52 +01:00
|
|
|
*
|
2020-10-06 14:59:43 +02:00
|
|
|
* @param {PeerId|Multiaddr|string} peer - The peer to dial
|
2019-12-03 10:28:52 +01:00
|
|
|
* @param {object} [options]
|
2020-10-06 14:59:43 +02:00
|
|
|
* @param {AbortSignal} [options.signal] - An AbortController signal
|
2019-12-03 10:28:52 +01:00
|
|
|
* @returns {Promise<Connection>}
|
|
|
|
*/
|
2020-04-14 14:05:30 +02:00
|
|
|
async connectToPeer (peer, options = {}) {
|
2021-06-14 09:19:23 +02:00
|
|
|
const dialTarget = await this._createCancellableDialTarget(peer)
|
2020-04-14 14:05:30 +02:00
|
|
|
|
|
|
|
if (!dialTarget.addrs.length) {
|
2021-04-20 09:31:16 +02:00
|
|
|
throw errCode(new Error('The dial request has no valid addresses'), codes.ERR_NO_VALID_ADDRESSES)
|
2019-12-04 16:27:33 +01:00
|
|
|
}
|
2019-12-15 17:33:16 +01:00
|
|
|
const pendingDial = this._pendingDials.get(dialTarget.id) || this._createPendingDial(dialTarget, options)
|
2019-12-06 17:45:29 +01:00
|
|
|
|
2019-10-21 16:53:58 +02:00
|
|
|
try {
|
2019-12-15 17:33:16 +01:00
|
|
|
const connection = await pendingDial.promise
|
|
|
|
log('dial succeeded to %s', dialTarget.id)
|
|
|
|
return connection
|
2019-10-21 16:53:58 +02:00
|
|
|
} catch (err) {
|
2019-12-04 16:59:38 +01:00
|
|
|
// Error is a timeout
|
2019-12-15 17:33:16 +01:00
|
|
|
if (pendingDial.controller.signal.aborted) {
|
2019-12-04 17:32:11 +01:00
|
|
|
err.code = codes.ERR_TIMEOUT
|
2019-12-04 16:59:38 +01:00
|
|
|
}
|
2019-12-03 10:28:52 +01:00
|
|
|
log.error(err)
|
2019-10-21 16:53:58 +02:00
|
|
|
throw err
|
2019-12-06 17:45:29 +01:00
|
|
|
} finally {
|
2019-12-15 17:33:16 +01:00
|
|
|
pendingDial.destroy()
|
2019-10-21 16:53:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-14 09:19:23 +02:00
|
|
|
/**
|
|
|
|
* Connects to a given `peer` by dialing all of its known addresses.
|
|
|
|
* The dial to the first address that is successfully able to upgrade a connection
|
|
|
|
* will be used.
|
|
|
|
*
|
|
|
|
* @param {PeerId|Multiaddr|string} peer - The peer to dial
|
|
|
|
* @returns {Promise<DialTarget>}
|
|
|
|
*/
|
|
|
|
async _createCancellableDialTarget (peer) {
|
|
|
|
// Make dial target promise cancellable
|
|
|
|
const id = `${(parseInt(String(Math.random() * 1e9), 10)).toString() + Date.now()}`
|
|
|
|
const cancellablePromise = new Promise((resolve, reject) => {
|
|
|
|
this._pendingDialTargets.set(id, { resolve, reject })
|
|
|
|
})
|
|
|
|
|
|
|
|
const dialTarget = await Promise.race([
|
|
|
|
this._createDialTarget(peer),
|
|
|
|
cancellablePromise
|
|
|
|
])
|
|
|
|
|
|
|
|
this._pendingDialTargets.delete(id)
|
|
|
|
|
|
|
|
return dialTarget
|
|
|
|
}
|
|
|
|
|
2019-12-15 17:33:16 +01:00
|
|
|
/**
|
|
|
|
* Creates a DialTarget. The DialTarget is used to create and track
|
|
|
|
* the DialRequest to a given peer.
|
2020-04-14 14:05:30 +02:00
|
|
|
* If a multiaddr is received it should be the first address attempted.
|
2021-04-20 09:31:16 +02:00
|
|
|
* Multiaddrs not supported by the available transports will be filtered out.
|
2020-10-06 14:59:43 +02:00
|
|
|
*
|
2019-12-15 17:33:16 +01:00
|
|
|
* @private
|
2020-10-06 14:59:43 +02:00
|
|
|
* @param {PeerId|Multiaddr|string} peer - A PeerId or Multiaddr
|
2020-11-04 13:54:50 +01:00
|
|
|
* @returns {Promise<DialTarget>}
|
2019-12-15 17:33:16 +01:00
|
|
|
*/
|
2020-11-04 13:54:50 +01:00
|
|
|
async _createDialTarget (peer) {
|
2020-04-24 13:01:04 +02:00
|
|
|
const { id, multiaddrs } = getPeer(peer)
|
|
|
|
|
|
|
|
if (multiaddrs) {
|
|
|
|
this.peerStore.addressBook.add(id, multiaddrs)
|
|
|
|
}
|
|
|
|
|
2020-11-20 15:16:40 +01:00
|
|
|
let knownAddrs = this.peerStore.addressBook.getMultiaddrsForPeer(id, this.addressSorter) || []
|
2020-04-09 16:07:18 +02:00
|
|
|
|
2020-04-14 14:05:30 +02:00
|
|
|
// If received a multiaddr to dial, it should be the first to use
|
|
|
|
// But, if we know other multiaddrs for the peer, we should try them too.
|
2021-04-15 09:40:02 +02:00
|
|
|
if (Multiaddr.isMultiaddr(peer)) {
|
2020-11-04 13:54:50 +01:00
|
|
|
knownAddrs = knownAddrs.filter((addr) => !peer.equals(addr))
|
|
|
|
knownAddrs.unshift(peer)
|
|
|
|
}
|
|
|
|
|
2021-04-15 09:40:02 +02:00
|
|
|
/** @type {Multiaddr[]} */
|
2020-11-04 13:54:50 +01:00
|
|
|
const addrs = []
|
|
|
|
for (const a of knownAddrs) {
|
|
|
|
const resolvedAddrs = await this._resolve(a)
|
|
|
|
resolvedAddrs.forEach(ra => addrs.push(ra))
|
2020-04-14 14:05:30 +02:00
|
|
|
}
|
2020-04-09 16:07:18 +02:00
|
|
|
|
2021-04-20 09:31:16 +02:00
|
|
|
// Multiaddrs not supported by the available transports will be filtered out.
|
|
|
|
const supportedAddrs = addrs.filter(a => this.transportManager.transportForMultiaddr(a))
|
|
|
|
|
2019-12-15 17:33:16 +01:00
|
|
|
return {
|
2020-04-24 13:01:04 +02:00
|
|
|
id: id.toB58String(),
|
2021-04-20 09:31:16 +02:00
|
|
|
addrs: supportedAddrs
|
2019-12-15 17:33:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a PendingDial that wraps the underlying DialRequest
|
2020-10-06 14:59:43 +02:00
|
|
|
*
|
2019-12-15 17:33:16 +01:00
|
|
|
* @private
|
|
|
|
* @param {DialTarget} dialTarget
|
2019-10-21 16:53:58 +02:00
|
|
|
* @param {object} [options]
|
2020-10-06 14:59:43 +02:00
|
|
|
* @param {AbortSignal} [options.signal] - An AbortController signal
|
2019-12-15 17:33:16 +01:00
|
|
|
* @returns {PendingDial}
|
2019-10-21 16:53:58 +02:00
|
|
|
*/
|
2020-12-10 14:48:14 +01:00
|
|
|
_createPendingDial (dialTarget, options = {}) {
|
2021-04-15 09:40:02 +02:00
|
|
|
/**
|
|
|
|
* @param {Multiaddr} addr
|
|
|
|
* @param {{ signal: { aborted: any; }; }} options
|
|
|
|
*/
|
2019-12-15 17:33:16 +01:00
|
|
|
const dialAction = (addr, options) => {
|
2020-02-11 16:32:40 +01:00
|
|
|
if (options.signal.aborted) throw errCode(new Error('already aborted'), codes.ERR_ALREADY_ABORTED)
|
2019-12-15 17:33:16 +01:00
|
|
|
return this.transportManager.dial(addr, options)
|
|
|
|
}
|
2019-11-26 16:40:04 +01:00
|
|
|
|
2019-12-15 17:33:16 +01:00
|
|
|
const dialRequest = new DialRequest({
|
|
|
|
addrs: dialTarget.addrs,
|
|
|
|
dialAction,
|
|
|
|
dialer: this
|
|
|
|
})
|
|
|
|
|
|
|
|
// Combine the timeout signal and options.signal, if provided
|
|
|
|
const timeoutController = new TimeoutController(this.timeout)
|
|
|
|
const signals = [timeoutController.signal]
|
|
|
|
options.signal && signals.push(options.signal)
|
|
|
|
const signal = anySignal(signals)
|
2019-12-03 10:28:52 +01:00
|
|
|
|
2019-12-15 17:33:16 +01:00
|
|
|
const pendingDial = {
|
|
|
|
dialRequest,
|
|
|
|
controller: timeoutController,
|
|
|
|
promise: dialRequest.run({ ...options, signal }),
|
|
|
|
destroy: () => {
|
|
|
|
timeoutController.clear()
|
|
|
|
this._pendingDials.delete(dialTarget.id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this._pendingDials.set(dialTarget.id, pendingDial)
|
|
|
|
return pendingDial
|
2019-12-03 10:28:52 +01:00
|
|
|
}
|
2019-10-21 16:53:58 +02:00
|
|
|
|
2021-04-15 09:40:02 +02:00
|
|
|
/**
|
|
|
|
* @param {number} num
|
|
|
|
*/
|
2019-12-03 10:28:52 +01:00
|
|
|
getTokens (num) {
|
2021-04-15 09:40:02 +02:00
|
|
|
const total = Math.min(num, this.maxDialsPerPeer, this.tokens.length)
|
2019-12-03 10:28:52 +01:00
|
|
|
const tokens = this.tokens.splice(0, total)
|
|
|
|
log('%d tokens request, returning %d, %d remaining', num, total, this.tokens.length)
|
|
|
|
return tokens
|
|
|
|
}
|
|
|
|
|
2021-04-15 09:40:02 +02:00
|
|
|
/**
|
|
|
|
* @param {number} token
|
|
|
|
*/
|
2019-12-03 10:28:52 +01:00
|
|
|
releaseToken (token) {
|
2019-12-04 22:58:52 +01:00
|
|
|
// Guard against duplicate releases
|
|
|
|
if (this.tokens.indexOf(token) > -1) return
|
2019-12-03 10:28:52 +01:00
|
|
|
log('token %d released', token)
|
|
|
|
this.tokens.push(token)
|
2019-10-21 16:53:58 +02:00
|
|
|
}
|
2020-11-04 13:54:50 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Resolve multiaddr recursively.
|
|
|
|
*
|
|
|
|
* @param {Multiaddr} ma
|
2020-12-10 14:48:14 +01:00
|
|
|
* @returns {Promise<Multiaddr[]>}
|
2020-11-04 13:54:50 +01:00
|
|
|
*/
|
|
|
|
async _resolve (ma) {
|
|
|
|
// TODO: recursive logic should live in multiaddr once dns4/dns6 support is in place
|
|
|
|
// Now only supporting resolve for dnsaddr
|
|
|
|
const resolvableProto = ma.protoNames().includes('dnsaddr')
|
|
|
|
|
|
|
|
// Multiaddr is not resolvable? End recursion!
|
|
|
|
if (!resolvableProto) {
|
|
|
|
return [ma]
|
|
|
|
}
|
|
|
|
|
|
|
|
const resolvedMultiaddrs = await this._resolveRecord(ma)
|
|
|
|
const recursiveMultiaddrs = await Promise.all(resolvedMultiaddrs.map((nm) => {
|
|
|
|
return this._resolve(nm)
|
|
|
|
}))
|
|
|
|
|
2020-12-10 14:48:14 +01:00
|
|
|
const addrs = recursiveMultiaddrs.flat()
|
|
|
|
return addrs.reduce((array, newM) => {
|
2020-11-04 13:54:50 +01:00
|
|
|
if (!array.find(m => m.equals(newM))) {
|
|
|
|
array.push(newM)
|
|
|
|
}
|
|
|
|
return array
|
2020-12-10 14:48:14 +01:00
|
|
|
}, /** @type {Multiaddr[]} */([]))
|
2020-11-04 13:54:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resolve a given multiaddr. If this fails, an empty array will be returned
|
|
|
|
*
|
|
|
|
* @param {Multiaddr} ma
|
2020-12-10 14:48:14 +01:00
|
|
|
* @returns {Promise<Multiaddr[]>}
|
2020-11-04 13:54:50 +01:00
|
|
|
*/
|
|
|
|
async _resolveRecord (ma) {
|
|
|
|
try {
|
2021-04-15 09:40:02 +02:00
|
|
|
ma = new Multiaddr(ma.toString()) // Use current multiaddr module
|
2020-11-04 13:54:50 +01:00
|
|
|
const multiaddrs = await ma.resolve()
|
|
|
|
return multiaddrs
|
|
|
|
} catch (_) {
|
|
|
|
log.error(`multiaddr ${ma} could not be resolved`)
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
}
|
2019-10-21 16:53:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = Dialer
|