diff --git a/src/config.js b/src/config.js index 58348279e1..2efa0f3d39 100644 --- a/src/config.js +++ b/src/config.js @@ -15,7 +15,9 @@ const transport = s.union([ const optionsSchema = s( { - connectionManager: 'object?', + connectionManager: s('object', { + minPeers: 25 + }), datastore: 'object?', peerInfo: 'object', peerBook: 'object?', diff --git a/src/index.js b/src/index.js index 315fec1120..bb79aa589a 100644 --- a/src/index.js +++ b/src/index.js @@ -25,6 +25,11 @@ const pubsub = require('./pubsub') const getPeerInfo = require('./get-peer-info') const validateConfig = require('./config').validate +const DISCOVERY_STRATEGIES = { + ALL: 0, // All peers + LOW: 1 // When below the ConnectionManager watermark +} + const notStarted = (action, state) => { return errCode( new Error(`libp2p cannot ${action} when not started; state is ${state}`), @@ -45,7 +50,7 @@ class Node extends EventEmitter { super() // validateConfig will ensure the config is correct, // and add default values where appropriate - _options = validateConfig(_options) + this._options = validateConfig(_options) this.datastore = _options.datastore this.peerInfo = _options.peerInfo @@ -57,11 +62,11 @@ class Node extends EventEmitter { this._discovery = [] // Discovery service instances/references // create the switch, and listen for errors - this._switch = new Switch(this.peerInfo, this.peerBook, _options.switch) + this._switch = new Switch(this.peerInfo, this.peerBook, this._options.switch) this._switch.on('error', (...args) => this.emit('error', ...args)) this.stats = this._switch.stats - this.connectionManager = new ConnectionManager(this, _options.connectionManager) + this.connectionManager = new ConnectionManager(this, this._options.connectionManager) // Attach stream multiplexers if (this._modules.streamMuxer) { @@ -359,35 +364,7 @@ class Node extends EventEmitter { // all transports need to be setup before discover starts if (this._modules.peerDiscovery) { - each(this._modules.peerDiscovery, (D, _cb) => { - 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 - - // If enabled then start it - if (enabled) { - let d - - if (typeof D === 'function') { - d = new D(Object.assign({}, config, { peerInfo: this.peerInfo })) - } else { - d = D - } - - d.on('peer', (peerInfo) => this.emit('peer:discovery', peerInfo)) - this._discovery.push(d) - d.start(_cb) - } else { - _cb() - } - }, cb) + this._setupPeerDiscovery(cb) } else { cb() } @@ -473,6 +450,57 @@ class Node extends EventEmitter { this.state('done') }) } + + /** + * Initializes and starts peer discovery services + * + * @private + * @param {function(Error)} callback + */ + _setupPeerDiscovery (callback) { + const minPeers = this._options.connectionManager.minPeers || 0 + 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 = 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', (peerInfo) => { + switch (config.strategy) { + case DISCOVERY_STRATEGIES.LOW: + const peerConns = Object.keys(this._switch.connection.connections).length + if (peerConns < minPeers || peerConns === 0) { + this.emit('peer:discovery', peerInfo) + } + break + default: + this.emit('peer:discovery', peerInfo) + } + }) + + this._discovery.push(discoveryService) + } + } + + each(this._discovery, (d, cb) => { + d.start(cb) + }, callback) + } } module.exports = Node +module.exports.DISCOVERY_STRATEGIES = DISCOVERY_STRATEGIES diff --git a/test/config.spec.js b/test/config.spec.js index 252d09ad13..fcf8343120 100644 --- a/test/config.spec.js +++ b/test/config.spec.js @@ -78,6 +78,9 @@ describe('configuration', () => { const expected = { peerInfo, + connectionManager: { + minPeers: 25 + }, modules: { transport: [ WS ], peerDiscovery: [ Bootstrap ], @@ -180,6 +183,9 @@ describe('configuration', () => { } const expected = { peerInfo, + connectionManager: { + minPeers: 25 + }, modules: { transport: [WS], dht: DHT diff --git a/test/peer-discovery.node.js b/test/peer-discovery.node.js index 881532049b..bba1a267c8 100644 --- a/test/peer-discovery.node.js +++ b/test/peer-discovery.node.js @@ -8,6 +8,7 @@ const sinon = require('sinon') const signalling = require('libp2p-webrtc-star/src/sig-server') const parallel = require('async/parallel') const crypto = require('crypto') +const { DISCOVERY_STRATEGIES } = require('../src') const createNode = require('./utils/create-node') const echo = require('./utils/echo') @@ -419,4 +420,56 @@ describe('peer discovery', () => { }) }) }) + + describe('discovery strategies', () => { + describe('low', () => { + setup({ + connectionManager: { + minPeers: 1 + }, + config: { + peerDiscovery: { + mdns: { + enabled: false + }, + webRTCStar: { + enabled: false + }, + bootstrap: { + enabled: true, + strategy: DISCOVERY_STRATEGIES.LOW, + list: [] + } + }, + dht: { + enabled: false + } + } + }) + + it('should only emit when the peer count is below the low watermark', (done) => { + let expectedPeers = [ + nodeB.peerInfo + ] + let actualPeers = [] + const bootstrap = nodeA._discovery[0] + + nodeA.on('peer:discovery', (peerInfo) => { + actualPeers.push(peerInfo) + + // Connect, which brings us to our low watermark + nodeA.dial(peerInfo, () => { + // This second emit should not trigger discovery + bootstrap.emit('peer', nodeC.peerInfo) + + expect(expectedPeers).to.eql(actualPeers) + nodeA.removeAllListeners('peer:discovery') + done() + }) + }) + + bootstrap.emit('peer', nodeB.peerInfo) + }) + }) + }) }) diff --git a/test/utils/bundle-browser.js b/test/utils/bundle-browser.js index e066031404..71535c1bf9 100644 --- a/test/utils/bundle-browser.js +++ b/test/utils/bundle-browser.js @@ -69,6 +69,7 @@ class Node extends libp2p { }, bootstrap: { interval: 10000, + strategy: libp2p.DISCOVERY_STRATEGIES.LOW, enabled: false, list: _options.boostrapList } diff --git a/test/utils/bundle-nodejs.js b/test/utils/bundle-nodejs.js index cbebd2d58a..d9b149452e 100644 --- a/test/utils/bundle-nodejs.js +++ b/test/utils/bundle-nodejs.js @@ -62,6 +62,7 @@ class Node extends libp2p { }, bootstrap: { interval: 10000, + strategy: libp2p.DISCOVERY_STRATEGIES.LOW, enabled: false, list: _options.bootstrapList }