2016-03-23 15:30:14 +01:00
|
|
|
'use strict'
|
|
|
|
|
2018-10-19 17:37:34 +02:00
|
|
|
const FSM = require('fsm-event')
|
2017-04-06 15:45:23 -04:00
|
|
|
const EventEmitter = require('events').EventEmitter
|
2018-10-19 17:37:34 +02:00
|
|
|
const debug = require('debug')
|
|
|
|
const log = debug('libp2p')
|
|
|
|
log.error = debug('libp2p:error')
|
2018-12-14 17:54:32 +01:00
|
|
|
const errCode = require('err-code')
|
2019-07-29 14:40:40 +01:00
|
|
|
const promisify = require('promisify-es6')
|
2017-04-06 15:45:23 -04:00
|
|
|
|
|
|
|
const each = require('async/each')
|
|
|
|
const series = require('async/series')
|
2018-06-28 10:06:25 +02:00
|
|
|
const parallel = require('async/parallel')
|
2019-06-06 12:21:31 +02:00
|
|
|
const nextTick = require('async/nextTick')
|
2017-04-06 15:45:23 -04:00
|
|
|
|
2016-11-26 03:07:52 +01:00
|
|
|
const PeerBook = require('peer-book')
|
2019-06-06 12:21:31 +02:00
|
|
|
const PeerInfo = require('peer-info')
|
2018-02-07 07:48:37 +00:00
|
|
|
const Switch = require('libp2p-switch')
|
|
|
|
const Ping = require('libp2p-ping')
|
2018-04-03 15:51:05 +01:00
|
|
|
const WebSockets = require('libp2p-websockets')
|
2018-06-20 11:19:37 +01:00
|
|
|
const ConnectionManager = require('libp2p-connection-manager')
|
2018-02-07 07:48:37 +00:00
|
|
|
|
2019-02-01 16:32:34 +01:00
|
|
|
const { emitFirst } = require('./util')
|
2018-02-07 07:48:37 +00:00
|
|
|
const peerRouting = require('./peer-routing')
|
|
|
|
const contentRouting = require('./content-routing')
|
|
|
|
const dht = require('./dht')
|
2018-02-14 11:30:36 +01:00
|
|
|
const pubsub = require('./pubsub')
|
2019-08-16 17:30:03 +02:00
|
|
|
const { getPeerInfoRemote } = require('./get-peer-info')
|
2018-06-28 10:06:25 +02:00
|
|
|
const validateConfig = require('./config').validate
|
2019-04-16 12:05:22 +02:00
|
|
|
const { codes } = require('./errors')
|
2015-09-27 00:14:40 +01:00
|
|
|
|
2018-12-14 17:54:32 +01:00
|
|
|
const notStarted = (action, state) => {
|
|
|
|
return errCode(
|
|
|
|
new Error(`libp2p cannot ${action} when not started; state is ${state}`),
|
2019-04-16 12:05:22 +02:00
|
|
|
codes.ERR_NODE_NOT_STARTED
|
2018-12-14 17:54:32 +01:00
|
|
|
)
|
|
|
|
}
|
2016-11-26 03:07:52 +01:00
|
|
|
|
2018-10-19 17:37:34 +02:00
|
|
|
/**
|
2019-06-06 12:21:31 +02:00
|
|
|
* @fires Libp2p#error Emitted when an error occurs
|
|
|
|
* @fires Libp2p#peer:connect Emitted when a peer is connected to this node
|
|
|
|
* @fires Libp2p#peer:disconnect Emitted when a peer disconnects from this node
|
|
|
|
* @fires Libp2p#peer:discovery Emitted when a peer is discovered
|
|
|
|
* @fires Libp2p#start Emitted when the node and its services has started
|
|
|
|
* @fires Libp2p#stop Emitted when the node and its services has stopped
|
2018-10-19 17:37:34 +02:00
|
|
|
*/
|
2019-06-06 12:21:31 +02:00
|
|
|
class Libp2p extends EventEmitter {
|
2018-06-28 10:06:25 +02:00
|
|
|
constructor (_options) {
|
2017-03-27 12:26:34 +01:00
|
|
|
super()
|
2018-06-28 10:06:25 +02:00
|
|
|
// validateConfig will ensure the config is correct,
|
|
|
|
// and add default values where appropriate
|
2019-04-11 12:44:58 +02:00
|
|
|
this._options = validateConfig(_options)
|
2016-11-26 03:07:52 +01:00
|
|
|
|
2019-04-11 12:44:58 +02:00
|
|
|
this.datastore = this._options.datastore
|
|
|
|
this.peerInfo = this._options.peerInfo
|
|
|
|
this.peerBook = this._options.peerBook || new PeerBook()
|
2017-07-20 14:19:36 -07:00
|
|
|
|
2019-04-11 12:44:58 +02:00
|
|
|
this._modules = this._options.modules
|
|
|
|
this._config = this._options.config
|
2018-06-28 10:06:25 +02:00
|
|
|
this._transport = [] // Transport instances/references
|
|
|
|
this._discovery = [] // Discovery service instances/references
|
2016-11-26 03:07:52 +01:00
|
|
|
|
2018-11-12 15:43:32 +01:00
|
|
|
// create the switch, and listen for errors
|
2019-04-11 12:44:58 +02:00
|
|
|
this._switch = new Switch(this.peerInfo, this.peerBook, this._options.switch)
|
2018-11-12 15:43:32 +01:00
|
|
|
this._switch.on('error', (...args) => this.emit('error', ...args))
|
|
|
|
|
2018-06-28 10:06:25 +02:00
|
|
|
this.stats = this._switch.stats
|
2019-04-11 12:44:58 +02:00
|
|
|
this.connectionManager = new ConnectionManager(this, this._options.connectionManager)
|
2016-11-26 03:07:52 +01:00
|
|
|
|
|
|
|
// Attach stream multiplexers
|
2018-06-28 10:06:25 +02:00
|
|
|
if (this._modules.streamMuxer) {
|
2019-07-30 15:11:24 +02:00
|
|
|
const muxers = this._modules.streamMuxer
|
2018-06-28 10:06:25 +02:00
|
|
|
muxers.forEach((muxer) => this._switch.connection.addStreamMuxer(muxer))
|
2016-11-26 03:07:52 +01:00
|
|
|
|
2018-06-28 10:06:25 +02:00
|
|
|
// If muxer exists
|
|
|
|
// we can use Identify
|
|
|
|
this._switch.connection.reuse()
|
|
|
|
// we can use Relay for listening/dialing
|
|
|
|
this._switch.connection.enableCircuitRelay(this._config.relay)
|
2016-11-26 03:07:52 +01:00
|
|
|
|
2018-06-28 10:06:25 +02:00
|
|
|
// Received incomming dial and muxer upgrade happened,
|
2017-03-29 18:03:00 +01:00
|
|
|
// reuse this muxed connection
|
2018-06-28 10:06:25 +02:00
|
|
|
this._switch.on('peer-mux-established', (peerInfo) => {
|
2017-03-29 07:32:46 +01:00
|
|
|
this.emit('peer:connect', peerInfo)
|
2016-11-26 03:07:52 +01:00
|
|
|
})
|
|
|
|
|
2018-06-28 10:06:25 +02:00
|
|
|
this._switch.on('peer-mux-closed', (peerInfo) => {
|
2017-03-29 07:32:46 +01:00
|
|
|
this.emit('peer:disconnect', peerInfo)
|
2016-11-26 03:07:52 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-04-11 13:49:31 +02:00
|
|
|
// Events for anytime connections are created/removed
|
|
|
|
this._switch.on('connection:start', (peerInfo) => {
|
|
|
|
this.emit('connection:start', peerInfo)
|
|
|
|
})
|
|
|
|
this._switch.on('connection:end', (peerInfo) => {
|
|
|
|
this.emit('connection:end', peerInfo)
|
|
|
|
})
|
|
|
|
|
2016-11-26 03:07:52 +01:00
|
|
|
// Attach crypto channels
|
2018-06-28 10:06:25 +02:00
|
|
|
if (this._modules.connEncryption) {
|
2019-07-30 15:11:24 +02:00
|
|
|
const cryptos = this._modules.connEncryption
|
2016-11-26 03:07:52 +01:00
|
|
|
cryptos.forEach((crypto) => {
|
2018-06-28 10:06:25 +02:00
|
|
|
this._switch.connection.crypto(crypto.tag, crypto.encrypt)
|
2016-11-26 03:07:52 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-06-08 00:30:21 +02:00
|
|
|
// Attach private network protector
|
|
|
|
if (this._modules.connProtector) {
|
|
|
|
this._switch.protector = this._modules.connProtector
|
|
|
|
} else if (process.env.LIBP2P_FORCE_PNET) {
|
|
|
|
throw new Error('Private network is enforced, but no protector was provided')
|
|
|
|
}
|
|
|
|
|
2018-06-28 10:06:25 +02:00
|
|
|
// dht provided components (peerRouting, contentRouting, dht)
|
2019-01-29 17:57:09 +00:00
|
|
|
if (this._config.dht.enabled) {
|
2018-06-28 10:06:25 +02:00
|
|
|
const DHT = this._modules.dht
|
2018-10-04 13:40:32 +01:00
|
|
|
|
2018-06-28 10:06:25 +02:00
|
|
|
this._dht = new DHT(this._switch, {
|
2018-11-29 14:10:23 +00:00
|
|
|
datastore: this.datastore,
|
2019-02-04 12:20:24 +11:00
|
|
|
...this._config.dht
|
2016-11-26 03:07:52 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-07-31 09:38:14 +02:00
|
|
|
// start pubsub
|
|
|
|
if (this._modules.pubsub && this._config.pubsub.enabled !== false) {
|
|
|
|
this.pubsub = pubsub(this, this._modules.pubsub)
|
2017-04-06 15:45:23 -04:00
|
|
|
}
|
|
|
|
|
2018-06-28 10:06:25 +02:00
|
|
|
// Attach remaining APIs
|
2018-10-19 16:28:28 +02:00
|
|
|
// peer and content routing will automatically get modules from _modules and _dht
|
2018-02-07 07:48:37 +00:00
|
|
|
this.peerRouting = peerRouting(this)
|
|
|
|
this.contentRouting = contentRouting(this)
|
|
|
|
this.dht = dht(this)
|
2017-04-06 15:45:23 -04:00
|
|
|
|
2018-02-07 07:48:37 +00:00
|
|
|
// Mount default protocols
|
2018-06-28 10:06:25 +02:00
|
|
|
Ping.mount(this._switch)
|
2018-10-19 17:37:34 +02:00
|
|
|
|
|
|
|
this.state = new FSM('STOPPED', {
|
|
|
|
STOPPED: {
|
|
|
|
start: 'STARTING',
|
|
|
|
stop: 'STOPPED'
|
|
|
|
},
|
|
|
|
STARTING: {
|
|
|
|
done: 'STARTED',
|
|
|
|
abort: 'STOPPED',
|
|
|
|
stop: 'STOPPING'
|
|
|
|
},
|
|
|
|
STARTED: {
|
|
|
|
stop: 'STOPPING',
|
|
|
|
start: 'STARTED'
|
|
|
|
},
|
|
|
|
STOPPING: {
|
|
|
|
stop: 'STOPPING',
|
|
|
|
done: 'STOPPED'
|
|
|
|
}
|
|
|
|
})
|
|
|
|
this.state.on('STARTING', () => {
|
|
|
|
log('libp2p is starting')
|
|
|
|
this._onStarting()
|
|
|
|
})
|
|
|
|
this.state.on('STOPPING', () => {
|
|
|
|
log('libp2p is stopping')
|
|
|
|
this._onStopping()
|
|
|
|
})
|
|
|
|
this.state.on('STARTED', () => {
|
|
|
|
log('libp2p has started')
|
|
|
|
this.emit('start')
|
|
|
|
})
|
|
|
|
this.state.on('STOPPED', () => {
|
|
|
|
log('libp2p has stopped')
|
|
|
|
this.emit('stop')
|
|
|
|
})
|
|
|
|
this.state.on('error', (err) => {
|
|
|
|
log.error(err)
|
|
|
|
this.emit('error', err)
|
|
|
|
})
|
2019-04-11 12:44:58 +02:00
|
|
|
|
|
|
|
// Once we start, emit and dial any peers we may have already discovered
|
|
|
|
this.state.on('STARTED', () => {
|
|
|
|
this.peerBook.getAllArray().forEach((peerInfo) => {
|
|
|
|
this.emit('peer:discovery', peerInfo)
|
|
|
|
this._maybeConnect(peerInfo)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
this._peerDiscovered = this._peerDiscovered.bind(this)
|
2019-08-06 10:53:23 +02:00
|
|
|
|
|
|
|
// promisify all instance methods
|
|
|
|
;['start', 'stop', 'dial', 'dialProtocol', 'dialFSM', 'hangUp', 'ping'].forEach(method => {
|
|
|
|
this[method] = promisify(this[method], { context: this })
|
|
|
|
})
|
2018-10-19 17:37:34 +02:00
|
|
|
}
|
|
|
|
|
2018-11-12 15:43:32 +01:00
|
|
|
/**
|
|
|
|
* Overrides EventEmitter.emit to conditionally emit errors
|
|
|
|
* if there is a handler. If not, errors will be logged.
|
|
|
|
* @param {string} eventName
|
|
|
|
* @param {...any} args
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
emit (eventName, ...args) {
|
|
|
|
if (eventName === 'error' && !this._events.error) {
|
|
|
|
log.error(...args)
|
|
|
|
} else {
|
|
|
|
super.emit(eventName, ...args)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-19 17:37:34 +02:00
|
|
|
/**
|
|
|
|
* Starts the libp2p node and all sub services
|
|
|
|
*
|
|
|
|
* @param {function(Error)} callback
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
start (callback = () => {}) {
|
2019-02-01 16:32:34 +01:00
|
|
|
emitFirst(this, ['error', 'start'], callback)
|
2018-10-19 17:37:34 +02:00
|
|
|
this.state('start')
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stop the libp2p node by closing its listeners and open connections
|
|
|
|
*
|
|
|
|
* @param {function(Error)} callback
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
stop (callback = () => {}) {
|
2019-02-01 16:32:34 +01:00
|
|
|
emitFirst(this, ['error', 'stop'], callback)
|
2018-10-19 17:37:34 +02:00
|
|
|
this.state('stop')
|
|
|
|
}
|
|
|
|
|
|
|
|
isStarted () {
|
|
|
|
return this.state ? this.state._state === 'STARTED' : false
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Dials to the provided peer. If successful, the `PeerInfo` of the
|
|
|
|
* peer will be added to the nodes `PeerBook`
|
|
|
|
*
|
|
|
|
* @param {PeerInfo|PeerId|Multiaddr|string} peer The peer to dial
|
|
|
|
* @param {function(Error)} callback
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
dial (peer, callback) {
|
|
|
|
this.dialProtocol(peer, null, callback)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Dials to the provided peer and handshakes with the given protocol.
|
|
|
|
* If successful, the `PeerInfo` of the peer will be added to the nodes `PeerBook`,
|
|
|
|
* and the `Connection` will be sent in the callback
|
|
|
|
*
|
|
|
|
* @param {PeerInfo|PeerId|Multiaddr|string} peer The peer to dial
|
|
|
|
* @param {string} protocol
|
|
|
|
* @param {function(Error, Connection)} callback
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
dialProtocol (peer, protocol, callback) {
|
2018-12-14 17:54:32 +01:00
|
|
|
if (!this.isStarted()) {
|
|
|
|
return callback(notStarted('dial', this.state._state))
|
|
|
|
}
|
2018-10-19 17:37:34 +02:00
|
|
|
|
|
|
|
if (typeof protocol === 'function') {
|
|
|
|
callback = protocol
|
|
|
|
protocol = undefined
|
|
|
|
}
|
|
|
|
|
2019-08-16 17:30:03 +02:00
|
|
|
getPeerInfoRemote(peer, this)
|
|
|
|
.then(peerInfo => {
|
|
|
|
this._switch.dial(peerInfo, protocol, callback)
|
|
|
|
}, callback)
|
2016-11-26 03:07:52 +01:00
|
|
|
}
|
|
|
|
|
2018-10-19 17:37:34 +02:00
|
|
|
/**
|
|
|
|
* Similar to `dial` and `dialProtocol`, but the callback will contain a
|
|
|
|
* Connection State Machine.
|
|
|
|
*
|
|
|
|
* @param {PeerInfo|PeerId|Multiaddr|string} peer The peer to dial
|
|
|
|
* @param {string} protocol
|
|
|
|
* @param {function(Error, ConnectionFSM)} callback
|
|
|
|
* @returns {void}
|
2016-11-26 03:07:52 +01:00
|
|
|
*/
|
2018-10-19 17:37:34 +02:00
|
|
|
dialFSM (peer, protocol, callback) {
|
2018-12-14 17:54:32 +01:00
|
|
|
if (!this.isStarted()) {
|
|
|
|
return callback(notStarted('dial', this.state._state))
|
|
|
|
}
|
2018-10-19 17:37:34 +02:00
|
|
|
|
|
|
|
if (typeof protocol === 'function') {
|
|
|
|
callback = protocol
|
|
|
|
protocol = undefined
|
|
|
|
}
|
|
|
|
|
2019-08-16 17:30:03 +02:00
|
|
|
getPeerInfoRemote(peer, this)
|
|
|
|
.then(peerInfo => {
|
|
|
|
this._switch.dialFSM(peerInfo, protocol, callback)
|
|
|
|
}, callback)
|
2018-10-19 17:37:34 +02:00
|
|
|
}
|
|
|
|
|
2019-08-06 10:53:23 +02:00
|
|
|
/**
|
|
|
|
* Disconnects from the given peer
|
|
|
|
*
|
|
|
|
* @param {PeerInfo|PeerId|Multiaddr|string} peer The peer to ping
|
|
|
|
* @param {function(Error)} callback
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
2018-10-19 17:37:34 +02:00
|
|
|
hangUp (peer, callback) {
|
2019-08-16 17:30:03 +02:00
|
|
|
getPeerInfoRemote(peer, this)
|
|
|
|
.then(peerInfo => {
|
|
|
|
this._switch.hangUp(peerInfo, callback)
|
|
|
|
}, callback)
|
2018-10-19 17:37:34 +02:00
|
|
|
}
|
|
|
|
|
2019-08-06 10:53:23 +02:00
|
|
|
/**
|
|
|
|
* Pings the provided peer
|
|
|
|
*
|
|
|
|
* @param {PeerInfo|PeerId|Multiaddr|string} peer The peer to ping
|
|
|
|
* @param {function(Error, Ping)} callback
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
2018-10-19 17:37:34 +02:00
|
|
|
ping (peer, callback) {
|
|
|
|
if (!this.isStarted()) {
|
2018-12-14 17:54:32 +01:00
|
|
|
return callback(notStarted('ping', this.state._state))
|
2018-10-19 17:37:34 +02:00
|
|
|
}
|
|
|
|
|
2019-08-16 17:30:03 +02:00
|
|
|
getPeerInfoRemote(peer, this)
|
|
|
|
.then(peerInfo => {
|
|
|
|
callback(null, new Ping(this._switch, peerInfo))
|
|
|
|
}, callback)
|
2018-10-19 17:37:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
handle (protocol, handlerFunc, matchFunc) {
|
|
|
|
this._switch.handle(protocol, handlerFunc, matchFunc)
|
|
|
|
}
|
|
|
|
|
|
|
|
unhandle (protocol) {
|
|
|
|
this._switch.unhandle(protocol)
|
|
|
|
}
|
|
|
|
|
|
|
|
_onStarting () {
|
2018-06-28 10:06:25 +02:00
|
|
|
if (!this._modules.transport) {
|
2018-10-19 17:37:34 +02:00
|
|
|
this.emit('error', new Error('no transports were present'))
|
|
|
|
return this.state('abort')
|
2016-11-26 03:07:52 +01:00
|
|
|
}
|
2016-11-27 18:20:25 +00:00
|
|
|
|
|
|
|
let ws
|
2017-03-29 21:02:37 +01:00
|
|
|
|
|
|
|
// so that we can have webrtc-star addrs without adding manually the id
|
2017-03-31 15:52:20 +01:00
|
|
|
const maOld = []
|
|
|
|
const maNew = []
|
2017-12-14 07:27:13 +00:00
|
|
|
this.peerInfo.multiaddrs.toArray().forEach((ma) => {
|
2017-10-26 04:51:36 -07:00
|
|
|
if (!ma.getPeerId()) {
|
2017-03-31 15:52:20 +01:00
|
|
|
maOld.push(ma)
|
2019-02-06 04:59:42 +10:00
|
|
|
maNew.push(ma.encapsulate('/p2p/' + this.peerInfo.id.toB58String()))
|
2017-03-29 21:02:37 +01:00
|
|
|
}
|
|
|
|
})
|
2017-03-31 15:52:20 +01:00
|
|
|
this.peerInfo.multiaddrs.replace(maOld, maNew)
|
2016-11-27 18:20:25 +00:00
|
|
|
|
2017-12-14 07:27:13 +00:00
|
|
|
const multiaddrs = this.peerInfo.multiaddrs.toArray()
|
2018-06-28 10:06:25 +02:00
|
|
|
|
|
|
|
this._modules.transport.forEach((Transport) => {
|
|
|
|
let t
|
|
|
|
|
|
|
|
if (typeof Transport === 'function') {
|
2019-05-17 13:11:22 +03:00
|
|
|
t = new Transport({ libp2p: this })
|
2018-06-28 10:06:25 +02:00
|
|
|
} else {
|
|
|
|
t = Transport
|
|
|
|
}
|
|
|
|
|
|
|
|
if (t.filter(multiaddrs).length > 0) {
|
2018-12-04 16:04:17 +01:00
|
|
|
this._switch.transport.add(t.tag || t[Symbol.toStringTag], t)
|
2018-06-28 10:06:25 +02:00
|
|
|
} else if (WebSockets.isWebSockets(t)) {
|
|
|
|
// TODO find a cleaner way to signal that a transport is always used
|
|
|
|
// for dialing, even if no listener
|
|
|
|
ws = t
|
2016-11-26 03:07:52 +01:00
|
|
|
}
|
2018-06-28 10:06:25 +02:00
|
|
|
this._transport.push(t)
|
2016-11-26 03:07:52 +01:00
|
|
|
})
|
|
|
|
|
2017-04-06 15:45:23 -04:00
|
|
|
series([
|
2018-07-19 15:53:18 +02:00
|
|
|
(cb) => {
|
|
|
|
this.connectionManager.start()
|
|
|
|
this._switch.start(cb)
|
|
|
|
},
|
2017-04-06 15:45:23 -04:00
|
|
|
(cb) => {
|
|
|
|
if (ws) {
|
|
|
|
// always add dialing on websockets
|
2018-06-28 10:06:25 +02:00
|
|
|
this._switch.transport.add(ws.tag || ws.constructor.name, ws)
|
2017-04-06 15:45:23 -04:00
|
|
|
}
|
|
|
|
|
2019-04-11 12:44:58 +02:00
|
|
|
// detect which multiaddrs we don't have a transport for and remove them
|
|
|
|
const multiaddrs = this.peerInfo.multiaddrs.toArray()
|
|
|
|
|
|
|
|
multiaddrs.forEach((multiaddr) => {
|
|
|
|
if (!multiaddr.toString().match(/\/p2p-circuit($|\/)/) &&
|
|
|
|
!this._transport.find((transport) => transport.filter(multiaddr).length > 0)) {
|
|
|
|
this.peerInfo.multiaddrs.delete(multiaddr)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
cb()
|
2017-04-06 15:45:23 -04:00
|
|
|
},
|
|
|
|
(cb) => {
|
|
|
|
if (this._dht) {
|
2018-12-28 10:54:56 +00:00
|
|
|
this._dht.start(() => {
|
2019-04-11 12:44:58 +02:00
|
|
|
this._dht.on('peer', this._peerDiscovered)
|
2018-12-28 10:54:56 +00:00
|
|
|
cb()
|
|
|
|
})
|
2018-02-14 11:30:36 +01:00
|
|
|
} else {
|
|
|
|
cb()
|
2017-04-06 15:45:23 -04:00
|
|
|
}
|
2017-07-07 12:56:46 +01:00
|
|
|
},
|
2018-02-14 11:30:36 +01:00
|
|
|
(cb) => {
|
2019-07-31 09:38:14 +02:00
|
|
|
if (this.pubsub) {
|
|
|
|
return this.pubsub.start(cb)
|
2018-02-14 11:30:36 +01:00
|
|
|
}
|
2018-10-19 17:37:34 +02:00
|
|
|
cb()
|
2018-02-14 11:30:36 +01:00
|
|
|
},
|
2019-04-11 12:44:58 +02:00
|
|
|
// Peer Discovery
|
2017-12-14 07:27:13 +00:00
|
|
|
(cb) => {
|
2019-04-11 12:44:58 +02:00
|
|
|
if (this._modules.peerDiscovery) {
|
|
|
|
this._setupPeerDiscovery(cb)
|
|
|
|
} else {
|
|
|
|
cb()
|
|
|
|
}
|
2016-11-27 18:20:25 +00:00
|
|
|
}
|
2018-10-19 17:37:34 +02:00
|
|
|
], (err) => {
|
|
|
|
if (err) {
|
|
|
|
log.error(err)
|
|
|
|
this.emit('error', err)
|
|
|
|
return this.state('stop')
|
|
|
|
}
|
|
|
|
this.state('done')
|
|
|
|
})
|
2015-09-27 00:14:40 +01:00
|
|
|
}
|
|
|
|
|
2018-10-19 17:37:34 +02:00
|
|
|
_onStopping () {
|
2017-04-06 15:45:23 -04:00
|
|
|
series([
|
2018-02-14 11:30:36 +01:00
|
|
|
(cb) => {
|
2019-04-11 12:44:58 +02:00
|
|
|
// stop all discoveries before continuing with shutdown
|
|
|
|
parallel(
|
|
|
|
this._discovery.map((d) => {
|
|
|
|
d.removeListener('peer', this._peerDiscovered)
|
|
|
|
return (_cb) => d.stop((err) => {
|
|
|
|
log.error('an error occurred stopping the discovery service', err)
|
|
|
|
_cb()
|
|
|
|
})
|
|
|
|
}),
|
|
|
|
cb
|
|
|
|
)
|
2018-06-28 10:06:25 +02:00
|
|
|
},
|
|
|
|
(cb) => {
|
2019-07-31 09:38:14 +02:00
|
|
|
if (this.pubsub) {
|
|
|
|
return this.pubsub.stop(cb)
|
2018-06-28 10:06:25 +02:00
|
|
|
}
|
|
|
|
cb()
|
2018-02-14 11:30:36 +01:00
|
|
|
},
|
2017-04-06 15:45:23 -04:00
|
|
|
(cb) => {
|
|
|
|
if (this._dht) {
|
2019-04-11 12:44:58 +02:00
|
|
|
this._dht.removeListener('peer', this._peerDiscovered)
|
2017-04-06 15:45:23 -04:00
|
|
|
return this._dht.stop(cb)
|
|
|
|
}
|
|
|
|
cb()
|
|
|
|
},
|
2017-07-07 12:56:46 +01:00
|
|
|
(cb) => {
|
2018-10-19 17:37:34 +02:00
|
|
|
this.connectionManager.stop()
|
|
|
|
this._switch.stop(cb)
|
2018-12-14 17:54:32 +01:00
|
|
|
},
|
|
|
|
(cb) => {
|
2019-02-01 16:32:34 +01:00
|
|
|
// Ensures idempotent restarts, ignore any errors
|
|
|
|
// from removeAll, they're not useful at this point
|
|
|
|
this._switch.transport.removeAll(() => cb())
|
2017-07-07 12:56:46 +01:00
|
|
|
}
|
2017-12-15 07:08:44 +00:00
|
|
|
], (err) => {
|
2018-10-19 17:37:34 +02:00
|
|
|
if (err) {
|
|
|
|
log.error(err)
|
|
|
|
this.emit('error', err)
|
|
|
|
}
|
|
|
|
this.state('done')
|
2018-02-07 07:48:37 +00:00
|
|
|
})
|
|
|
|
}
|
2019-04-11 12:44:58 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles discovered peers. Each discovered peer will be emitted via
|
|
|
|
* the `peer:discovery` event. If auto dial is enabled for libp2p
|
|
|
|
* and the current connection count is under the low watermark, the
|
|
|
|
* peer will be dialed.
|
|
|
|
*
|
|
|
|
* TODO: If `peerBook.put` becomes centralized, https://github.com/libp2p/js-libp2p/issues/345,
|
|
|
|
* it would be ideal if only new peers were emitted. Currently, with
|
|
|
|
* other modules adding peers to the `PeerBook` we have no way of knowing
|
|
|
|
* if a peer is new or not, so it has to be emitted.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {PeerInfo} peerInfo
|
|
|
|
*/
|
|
|
|
_peerDiscovered (peerInfo) {
|
2019-04-16 12:05:22 +02:00
|
|
|
if (peerInfo.id.toB58String() === this.peerInfo.id.toB58String()) {
|
|
|
|
log.error(new Error(codes.ERR_DISCOVERED_SELF))
|
|
|
|
return
|
|
|
|
}
|
2019-04-11 12:44:58 +02:00
|
|
|
peerInfo = this.peerBook.put(peerInfo)
|
|
|
|
|
|
|
|
if (!this.isStarted()) return
|
|
|
|
|
|
|
|
this.emit('peer:discovery', peerInfo)
|
|
|
|
this._maybeConnect(peerInfo)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Will dial to the given `peerInfo` if the current number of
|
|
|
|
* connected peers is less than the configured `ConnectionManager`
|
|
|
|
* minPeers.
|
|
|
|
* @private
|
|
|
|
* @param {PeerInfo} peerInfo
|
|
|
|
*/
|
|
|
|
_maybeConnect (peerInfo) {
|
|
|
|
// If auto dialing is on, check if we should dial
|
|
|
|
if (this._config.peerDiscovery.autoDial === true && !peerInfo.isConnected()) {
|
|
|
|
const minPeers = this._options.connectionManager.minPeers || 0
|
|
|
|
if (minPeers > Object.keys(this._switch.connection.connections).length) {
|
|
|
|
log('connecting to discovered peer')
|
|
|
|
this._switch.dialer.connect(peerInfo, (err) => {
|
|
|
|
err && log.error('could not connect to discovered peer', err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializes and starts peer discovery services
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {function(Error)} callback
|
|
|
|
*/
|
|
|
|
_setupPeerDiscovery (callback) {
|
|
|
|
for (const DiscoveryService of this._modules.peerDiscovery) {
|
|
|
|
let config = {
|
|
|
|
enabled: true // on by default
|
|
|
|
}
|
|
|
|
|
|
|
|
if (DiscoveryService.tag &&
|
|
|
|
this._config.peerDiscovery &&
|
|
|
|
this._config.peerDiscovery[DiscoveryService.tag]) {
|
|
|
|
config = { ...config, ...this._config.peerDiscovery[DiscoveryService.tag] }
|
|
|
|
}
|
|
|
|
|
|
|
|
if (config.enabled) {
|
|
|
|
let discoveryService
|
|
|
|
|
|
|
|
if (typeof DiscoveryService === 'function') {
|
|
|
|
discoveryService = new DiscoveryService(Object.assign({}, config, { peerInfo: this.peerInfo }))
|
|
|
|
} else {
|
|
|
|
discoveryService = DiscoveryService
|
|
|
|
}
|
|
|
|
|
|
|
|
discoveryService.on('peer', this._peerDiscovered)
|
|
|
|
this._discovery.push(discoveryService)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
each(this._discovery, (d, cb) => {
|
|
|
|
d.start(cb)
|
|
|
|
}, callback)
|
|
|
|
}
|
2016-11-26 03:07:52 +01:00
|
|
|
}
|
|
|
|
|
2019-06-06 12:21:31 +02:00
|
|
|
module.exports = Libp2p
|
|
|
|
/**
|
|
|
|
* Like `new Libp2p(options)` except it will create a `PeerInfo`
|
|
|
|
* instance if one is not provided in options.
|
|
|
|
* @param {object} options Libp2p configuration options
|
|
|
|
* @param {function(Error, Libp2p)} callback
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
2019-07-29 14:40:40 +01:00
|
|
|
module.exports.createLibp2p = promisify((options, callback) => {
|
2019-06-06 12:21:31 +02:00
|
|
|
if (options.peerInfo) {
|
|
|
|
return nextTick(callback, null, new Libp2p(options))
|
|
|
|
}
|
|
|
|
PeerInfo.create((err, peerInfo) => {
|
|
|
|
if (err) return callback(err)
|
|
|
|
options.peerInfo = peerInfo
|
|
|
|
callback(null, new Libp2p(options))
|
|
|
|
})
|
2019-07-29 14:40:40 +01:00
|
|
|
})
|