2019-10-21 16:53:58 +02:00
|
|
|
'use strict'
|
|
|
|
|
|
|
|
const multiaddr = require('multiaddr')
|
|
|
|
const errCode = require('err-code')
|
|
|
|
const AbortController = require('abort-controller')
|
2019-12-03 10:28:52 +01:00
|
|
|
const delay = require('delay')
|
2019-10-21 16:53:58 +02:00
|
|
|
const debug = require('debug')
|
|
|
|
const log = debug('libp2p:dialer')
|
|
|
|
log.error = debug('libp2p:dialer:error')
|
2019-12-03 10:28:52 +01:00
|
|
|
const { DialRequest } = require('./dialer/dial-request')
|
|
|
|
const { anySignal } = require('./util')
|
2019-10-21 16:53:58 +02:00
|
|
|
|
|
|
|
const { codes } = require('./errors')
|
|
|
|
const {
|
2019-12-03 10:28:52 +01:00
|
|
|
DIAL_TIMEOUT,
|
2019-10-21 16:53:58 +02:00
|
|
|
MAX_PARALLEL_DIALS,
|
2019-12-03 10:28:52 +01:00
|
|
|
PER_PEER_LIMIT
|
2019-10-21 16:53:58 +02:00
|
|
|
} = require('./constants')
|
|
|
|
|
|
|
|
class Dialer {
|
|
|
|
/**
|
|
|
|
* @constructor
|
|
|
|
* @param {object} options
|
|
|
|
* @param {TransportManager} options.transportManager
|
2019-11-26 16:40:04 +01:00
|
|
|
* @param {Peerstore} peerStore
|
2019-10-21 16:53:58 +02:00
|
|
|
* @param {number} options.concurrency Number of max concurrent dials. Defaults to `MAX_PARALLEL_DIALS`
|
|
|
|
* @param {number} options.timeout How long a dial attempt is allowed to take. Defaults to `DIAL_TIMEOUT`
|
|
|
|
*/
|
|
|
|
constructor ({
|
|
|
|
transportManager,
|
2019-11-26 16:40:04 +01:00
|
|
|
peerStore,
|
2019-10-21 16:53:58 +02:00
|
|
|
concurrency = MAX_PARALLEL_DIALS,
|
2019-12-03 10:28:52 +01:00
|
|
|
timeout = DIAL_TIMEOUT,
|
|
|
|
perPeerLimit = PER_PEER_LIMIT
|
2019-10-21 16:53:58 +02:00
|
|
|
}) {
|
|
|
|
this.transportManager = transportManager
|
2019-11-26 16:40:04 +01:00
|
|
|
this.peerStore = peerStore
|
2019-10-21 16:53:58 +02:00
|
|
|
this.concurrency = concurrency
|
|
|
|
this.timeout = timeout
|
2019-12-03 10:28:52 +01:00
|
|
|
this.perPeerLimit = perPeerLimit
|
|
|
|
this.tokens = [...new Array(concurrency)].map((_, index) => index)
|
2019-11-07 12:11:50 +01:00
|
|
|
|
2019-12-03 10:28:52 +01:00
|
|
|
this.releaseToken = this.releaseToken.bind(this)
|
2019-10-21 16:53:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Connects to a given `Multiaddr`. `addr` should include the id of the peer being
|
|
|
|
* dialed, it will be used for encryption verification.
|
|
|
|
*
|
|
|
|
* @param {Multiaddr} addr The address to dial
|
|
|
|
* @param {object} [options]
|
|
|
|
* @param {AbortSignal} [options.signal] An AbortController signal
|
|
|
|
* @returns {Promise<Connection>}
|
|
|
|
*/
|
2019-12-03 10:28:52 +01:00
|
|
|
connectToMultiaddr (addr, options = {}) {
|
2019-10-21 16:53:58 +02:00
|
|
|
addr = multiaddr(addr)
|
|
|
|
|
2019-12-03 10:28:52 +01:00
|
|
|
return this.connectToMultiaddrs([addr], options)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Connects to the first success of a given list of `Multiaddr`. `addrs` should
|
|
|
|
* include the id of the peer being dialed, it will be used for encryption verification.
|
|
|
|
*
|
|
|
|
* @param {Array<Multiaddr>} addrs
|
|
|
|
* @param {object} [options]
|
|
|
|
* @param {AbortSignal} [options.signal] An AbortController signal
|
|
|
|
* @returns {Promise<Connection>}
|
|
|
|
*/
|
|
|
|
async connectToMultiaddrs (addrs, options = {}) {
|
|
|
|
const dialAction = (addr, options) => this.transportManager.dial(addr, options)
|
|
|
|
const dialRequest = new DialRequest({
|
|
|
|
addrs,
|
|
|
|
dialAction,
|
|
|
|
dialer: this
|
|
|
|
})
|
|
|
|
|
|
|
|
// Combine the timeout signal and options.signal, if provided
|
|
|
|
const timeoutController = new AbortController()
|
|
|
|
const signals = [timeoutController.signal]
|
|
|
|
options.signal && signals.push(options.signal)
|
|
|
|
const signal = anySignal(signals)
|
|
|
|
const timeoutPromise = delay.reject(this.timeout, {
|
|
|
|
value: errCode(new Error('Dial timed out'), codes.ERR_TIMEOUT)
|
|
|
|
})
|
2019-10-21 16:53:58 +02:00
|
|
|
|
|
|
|
try {
|
2019-12-03 10:28:52 +01:00
|
|
|
// Race the dial request and the timeout
|
|
|
|
const dialResult = await Promise.race([
|
|
|
|
dialRequest.run({
|
|
|
|
...options,
|
|
|
|
signal
|
|
|
|
}),
|
|
|
|
timeoutPromise
|
|
|
|
])
|
|
|
|
timeoutPromise.clear()
|
|
|
|
return dialResult
|
2019-10-21 16:53:58 +02:00
|
|
|
} catch (err) {
|
2019-12-03 10:28:52 +01:00
|
|
|
log.error(err)
|
|
|
|
timeoutController.abort()
|
2019-10-21 16:53:58 +02:00
|
|
|
throw err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-11-26 16:40:04 +01:00
|
|
|
* Connects to a given `PeerInfo` or `PeerId` by dialing all of its known addresses.
|
2019-10-21 16:53:58 +02: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
|
|
|
* @param {PeerId} peerId The remote peer id to dial
|
2019-10-21 16:53:58 +02:00
|
|
|
* @param {object} [options]
|
|
|
|
* @param {AbortSignal} [options.signal] An AbortController signal
|
|
|
|
* @returns {Promise<Connection>}
|
|
|
|
*/
|
2019-12-03 10:28:52 +01:00
|
|
|
connectToPeer (peerId, options = {}) {
|
|
|
|
const addrs = this.peerStore.multiaddrsForPeer(peerId)
|
2019-11-26 16:40:04 +01:00
|
|
|
|
2019-12-03 10:28:52 +01:00
|
|
|
// TODO: ensure the peer id is on the multiaddr
|
|
|
|
|
|
|
|
return this.connectToMultiaddrs(addrs, options)
|
|
|
|
}
|
2019-10-21 16:53:58 +02:00
|
|
|
|
2019-12-03 10:28:52 +01:00
|
|
|
getTokens (num) {
|
|
|
|
const total = Math.min(num, this.perPeerLimit, this.tokens.length)
|
|
|
|
const tokens = this.tokens.splice(0, total)
|
|
|
|
log('%d tokens request, returning %d, %d remaining', num, total, this.tokens.length)
|
|
|
|
return tokens
|
|
|
|
}
|
|
|
|
|
|
|
|
releaseToken (token) {
|
|
|
|
log('token %d released', token)
|
|
|
|
this.tokens.push(token)
|
2019-10-21 16:53:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = Dialer
|