2016-03-23 15:30:14 +01:00
|
|
|
'use strict'
|
|
|
|
|
2017-04-06 15:45:23 -04:00
|
|
|
const EventEmitter = require('events').EventEmitter
|
|
|
|
const assert = require('assert')
|
|
|
|
|
|
|
|
const each = require('async/each')
|
|
|
|
const series = require('async/series')
|
2018-06-28 10:06:25 +02:00
|
|
|
const parallel = require('async/parallel')
|
2017-04-06 15:45:23 -04:00
|
|
|
|
2016-11-26 03:07:52 +01:00
|
|
|
const PeerBook = require('peer-book')
|
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
|
|
|
|
|
|
|
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')
|
2018-02-07 07:48:37 +00:00
|
|
|
const getPeerInfo = require('./get-peer-info')
|
2018-06-28 10:06:25 +02:00
|
|
|
const validateConfig = require('./config').validate
|
2015-09-27 00:14:40 +01:00
|
|
|
|
2016-11-26 03:07:52 +01:00
|
|
|
exports = module.exports
|
2015-09-27 00:14:40 +01:00
|
|
|
|
2017-07-07 12:56:46 +01:00
|
|
|
const NOT_STARTED_ERROR_MESSAGE = 'The libp2p node is not started yet'
|
2016-11-26 03:07:52 +01:00
|
|
|
|
2017-03-27 12:26:34 +01:00
|
|
|
class Node 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
|
|
|
|
_options = validateConfig(_options)
|
2016-11-26 03:07:52 +01:00
|
|
|
|
2018-06-28 10:06:25 +02:00
|
|
|
this.peerInfo = _options.peerInfo
|
|
|
|
this.peerBook = _options.peerBook || new PeerBook()
|
2017-07-20 14:19:36 -07:00
|
|
|
|
2018-06-28 16:06:42 +01:00
|
|
|
this._modules = _options.modules
|
|
|
|
this._config = _options.config
|
2017-07-07 12:56:46 +01:00
|
|
|
this._isStarted = false
|
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-06-28 10:06:25 +02:00
|
|
|
this._switch = new Switch(this.peerInfo, this.peerBook, _options.switch)
|
|
|
|
this.stats = this._switch.stats
|
2018-06-20 11:19:37 +01:00
|
|
|
this.connectionManager = new ConnectionManager(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) {
|
|
|
|
let muxers = this._modules.streamMuxer
|
|
|
|
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
|
|
|
this.peerBook.put(peerInfo)
|
|
|
|
})
|
|
|
|
|
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
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attach crypto channels
|
2018-06-28 10:06:25 +02:00
|
|
|
if (this._modules.connEncryption) {
|
|
|
|
let 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)
|
2018-06-28 16:06:42 +01:00
|
|
|
if (this._config.EXPERIMENTAL.dht) {
|
2018-06-28 10:06:25 +02:00
|
|
|
const DHT = this._modules.dht
|
2018-10-04 13:40:32 +01:00
|
|
|
const enabledDiscovery = this._config.dht.enabledDiscovery !== false
|
|
|
|
|
2018-06-28 10:06:25 +02:00
|
|
|
this._dht = new DHT(this._switch, {
|
|
|
|
kBucketSize: this._config.dht.kBucketSize || 20,
|
2018-10-04 13:40:32 +01:00
|
|
|
enabledDiscovery,
|
2018-06-28 10:06:25 +02:00
|
|
|
// TODO make datastore an option of libp2p itself so
|
|
|
|
// that other things can use it as well
|
|
|
|
datastore: dht.datastore
|
2016-11-26 03:07:52 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-06-28 10:06:25 +02:00
|
|
|
// enable/disable pubsub
|
2018-06-28 16:06:42 +01:00
|
|
|
if (this._config.EXPERIMENTAL.pubsub) {
|
2018-06-28 10:06:25 +02:00
|
|
|
this.pubsub = pubsub(this)
|
2017-04-06 15:45:23 -04:00
|
|
|
}
|
|
|
|
|
2018-06-28 10:06:25 +02:00
|
|
|
// Attach remaining APIs
|
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
|
|
|
this._getPeerInfo = getPeerInfo(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)
|
2016-11-26 03:07:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Start the libp2p node
|
|
|
|
* - create listeners on the multiaddrs the Peer wants to listen
|
|
|
|
*/
|
|
|
|
start (callback) {
|
2018-06-28 10:06:25 +02:00
|
|
|
if (!this._modules.transport) {
|
2016-11-26 03:07:52 +01:00
|
|
|
return callback(new Error('no transports were present'))
|
|
|
|
}
|
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)
|
|
|
|
maNew.push(ma.encapsulate('/ipfs/' + 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') {
|
|
|
|
t = new Transport()
|
|
|
|
} else {
|
|
|
|
t = Transport
|
|
|
|
}
|
|
|
|
|
|
|
|
if (t.filter(multiaddrs).length > 0) {
|
|
|
|
this._switch.transport.add(t.tag || t.constructor.name, t)
|
|
|
|
} 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
|
|
|
}
|
|
|
|
|
|
|
|
// all transports need to be setup before discover starts
|
2018-06-28 12:21:34 +01:00
|
|
|
if (this._modules.peerDiscovery) {
|
2018-06-28 10:06:25 +02:00
|
|
|
each(this._modules.peerDiscovery, (D, _cb) => {
|
2018-06-28 12:21:34 +01:00
|
|
|
let config = {}
|
|
|
|
|
|
|
|
if (D.tag &&
|
|
|
|
this._config.peerDiscovery &&
|
|
|
|
this._config.peerDiscovery[D.tag]) {
|
|
|
|
config = this._config.peerDiscovery[D.tag]
|
|
|
|
}
|
|
|
|
|
|
|
|
// If not configured to be enabled/disabled then enable by default
|
|
|
|
const enabled = config.enabled == null ? true : config.enabled
|
|
|
|
|
2018-06-28 10:06:25 +02:00
|
|
|
// If enabled then start it
|
2018-06-28 12:21:34 +01:00
|
|
|
if (enabled) {
|
2018-06-28 10:06:25 +02:00
|
|
|
let d
|
|
|
|
|
|
|
|
if (typeof D === 'function') {
|
2018-06-28 12:29:00 +01:00
|
|
|
d = new D(Object.assign({}, config, { peerInfo: this.peerInfo }))
|
2018-06-28 10:06:25 +02:00
|
|
|
} else {
|
|
|
|
d = D
|
|
|
|
}
|
|
|
|
|
|
|
|
d.on('peer', (peerInfo) => this.emit('peer:discovery', peerInfo))
|
|
|
|
this._discovery.push(d)
|
|
|
|
d.start(_cb)
|
|
|
|
} else {
|
|
|
|
_cb()
|
|
|
|
}
|
|
|
|
}, cb)
|
|
|
|
} else {
|
|
|
|
cb()
|
2017-04-06 15:45:23 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
(cb) => {
|
2018-02-14 11:30:36 +01:00
|
|
|
// TODO: chicken-and-egg problem #1:
|
2017-07-07 12:56:46 +01:00
|
|
|
// have to set started here because DHT requires libp2p is already started
|
|
|
|
this._isStarted = true
|
2017-04-06 15:45:23 -04:00
|
|
|
if (this._dht) {
|
2018-02-14 11:30:36 +01:00
|
|
|
this._dht.start(cb)
|
|
|
|
} 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) => {
|
|
|
|
// TODO: chicken-and-egg problem #2:
|
|
|
|
// have to set started here because FloodSub requires libp2p is already started
|
2018-06-28 10:06:25 +02:00
|
|
|
if (this._floodSub) {
|
2018-02-14 11:30:36 +01:00
|
|
|
this._floodSub.start(cb)
|
|
|
|
} else {
|
|
|
|
cb()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-12-14 07:27:13 +00:00
|
|
|
(cb) => {
|
|
|
|
// detect which multiaddrs we don't have a transport for and remove them
|
|
|
|
const multiaddrs = this.peerInfo.multiaddrs.toArray()
|
2018-02-14 11:30:36 +01:00
|
|
|
|
2018-06-28 10:06:25 +02:00
|
|
|
multiaddrs.forEach((multiaddr) => {
|
|
|
|
if (!multiaddr.toString().match(/\/p2p-circuit($|\/)/) &&
|
|
|
|
!this._transport.find((transport) => transport.filter(multiaddr).length > 0)) {
|
|
|
|
this.peerInfo.multiaddrs.delete(multiaddr)
|
|
|
|
}
|
2017-12-14 07:27:13 +00:00
|
|
|
})
|
|
|
|
cb()
|
|
|
|
},
|
2017-07-07 12:56:46 +01:00
|
|
|
(cb) => {
|
|
|
|
this.emit('start')
|
|
|
|
cb()
|
2016-11-27 18:20:25 +00:00
|
|
|
}
|
2017-04-06 15:45:23 -04:00
|
|
|
], callback)
|
2015-09-27 00:14:40 +01:00
|
|
|
}
|
|
|
|
|
2016-11-26 03:07:52 +01:00
|
|
|
/*
|
|
|
|
* Stop the libp2p node by closing its listeners and open connections
|
|
|
|
*/
|
|
|
|
stop (callback) {
|
2017-04-06 15:45:23 -04:00
|
|
|
series([
|
2018-02-14 11:30:36 +01:00
|
|
|
(cb) => {
|
2018-06-28 10:06:25 +02:00
|
|
|
if (this._modules.peerDiscovery) {
|
|
|
|
// stop all discoveries before continuing with shutdown
|
|
|
|
return parallel(
|
|
|
|
this._discovery.map((d) => {
|
|
|
|
return (_cb) => d.stop(() => { _cb() })
|
|
|
|
}),
|
|
|
|
cb
|
|
|
|
)
|
2018-02-14 11:30:36 +01:00
|
|
|
}
|
2018-06-28 10:06:25 +02:00
|
|
|
cb()
|
|
|
|
},
|
|
|
|
(cb) => {
|
|
|
|
if (this._floodSub) {
|
|
|
|
return this._floodSub.stop(cb)
|
|
|
|
}
|
|
|
|
cb()
|
2018-02-14 11:30:36 +01:00
|
|
|
},
|
2017-04-06 15:45:23 -04:00
|
|
|
(cb) => {
|
|
|
|
if (this._dht) {
|
|
|
|
return this._dht.stop(cb)
|
|
|
|
}
|
|
|
|
cb()
|
|
|
|
},
|
2018-07-19 15:53:18 +02:00
|
|
|
(cb) => {
|
|
|
|
this.connectionManager.stop()
|
|
|
|
this._switch.stop(cb)
|
|
|
|
},
|
2017-07-07 12:56:46 +01:00
|
|
|
(cb) => {
|
|
|
|
this.emit('stop')
|
|
|
|
cb()
|
|
|
|
}
|
2017-12-15 07:08:44 +00:00
|
|
|
], (err) => {
|
|
|
|
this._isStarted = false
|
|
|
|
callback(err)
|
|
|
|
})
|
2015-09-27 00:14:40 +01:00
|
|
|
}
|
|
|
|
|
2017-07-07 12:56:46 +01:00
|
|
|
isStarted () {
|
|
|
|
return this._isStarted
|
2016-12-01 11:53:27 +00:00
|
|
|
}
|
|
|
|
|
2018-02-07 07:48:37 +00:00
|
|
|
dial (peer, callback) {
|
2017-07-07 12:56:46 +01:00
|
|
|
assert(this.isStarted(), NOT_STARTED_ERROR_MESSAGE)
|
2018-02-07 07:48:37 +00:00
|
|
|
|
2017-04-06 15:45:23 -04:00
|
|
|
this._getPeerInfo(peer, (err, peerInfo) => {
|
2018-02-07 07:48:37 +00:00
|
|
|
if (err) { return callback(err) }
|
2017-04-06 15:45:23 -04:00
|
|
|
|
2018-06-28 10:06:25 +02:00
|
|
|
this._switch.dial(peerInfo, (err) => {
|
2018-02-07 07:48:37 +00:00
|
|
|
if (err) { return callback(err) }
|
|
|
|
|
|
|
|
this.peerBook.put(peerInfo)
|
2018-02-07 08:22:03 +00:00
|
|
|
callback()
|
2018-02-07 07:48:37 +00:00
|
|
|
})
|
2017-04-06 15:45:23 -04:00
|
|
|
})
|
2016-12-01 11:53:27 +00:00
|
|
|
}
|
|
|
|
|
2018-02-07 07:48:37 +00:00
|
|
|
dialProtocol (peer, protocol, callback) {
|
2017-07-07 12:56:46 +01:00
|
|
|
assert(this.isStarted(), NOT_STARTED_ERROR_MESSAGE)
|
2016-11-26 03:07:52 +01:00
|
|
|
|
|
|
|
if (typeof protocol === 'function') {
|
|
|
|
callback = protocol
|
|
|
|
protocol = undefined
|
|
|
|
}
|
|
|
|
|
2017-04-06 15:45:23 -04:00
|
|
|
this._getPeerInfo(peer, (err, peerInfo) => {
|
2018-02-07 07:48:37 +00:00
|
|
|
if (err) { return callback(err) }
|
2017-04-06 15:45:23 -04:00
|
|
|
|
2018-06-28 10:06:25 +02:00
|
|
|
this._switch.dial(peerInfo, protocol, (err, conn) => {
|
2018-02-07 07:48:37 +00:00
|
|
|
if (err) { return callback(err) }
|
2017-04-06 15:45:23 -04:00
|
|
|
this.peerBook.put(peerInfo)
|
|
|
|
callback(null, conn)
|
|
|
|
})
|
2016-11-26 03:07:52 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-03-27 12:26:34 +01:00
|
|
|
hangUp (peer, callback) {
|
2017-07-07 12:56:46 +01:00
|
|
|
assert(this.isStarted(), NOT_STARTED_ERROR_MESSAGE)
|
2016-11-26 03:07:52 +01:00
|
|
|
|
2017-04-06 15:45:23 -04:00
|
|
|
this._getPeerInfo(peer, (err, peerInfo) => {
|
2018-02-07 07:48:37 +00:00
|
|
|
if (err) { return callback(err) }
|
2017-04-06 15:45:23 -04:00
|
|
|
|
2018-06-28 10:06:25 +02:00
|
|
|
this._switch.hangUp(peerInfo, callback)
|
2017-04-06 15:45:23 -04:00
|
|
|
})
|
2016-11-26 03:07:52 +01:00
|
|
|
}
|
|
|
|
|
2018-02-07 07:48:37 +00:00
|
|
|
ping (peer, callback) {
|
2018-06-28 10:06:25 +02:00
|
|
|
if (!this.isStarted()) {
|
|
|
|
return callback(new Error(NOT_STARTED_ERROR_MESSAGE))
|
|
|
|
}
|
|
|
|
|
2018-02-07 07:48:37 +00:00
|
|
|
this._getPeerInfo(peer, (err, peerInfo) => {
|
|
|
|
if (err) { return callback(err) }
|
|
|
|
|
2018-06-28 10:06:25 +02:00
|
|
|
callback(null, new Ping(this._switch, peerInfo))
|
2018-02-07 07:48:37 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2016-11-26 03:07:52 +01:00
|
|
|
handle (protocol, handlerFunc, matchFunc) {
|
2018-06-28 10:06:25 +02:00
|
|
|
this._switch.handle(protocol, handlerFunc, matchFunc)
|
2016-11-26 03:07:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
unhandle (protocol) {
|
2018-06-28 10:06:25 +02:00
|
|
|
this._switch.unhandle(protocol)
|
2016-11-26 03:07:52 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-28 10:38:40 +00:00
|
|
|
module.exports = Node
|