├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── package.json ├── src └── index.js └── test ├── default.spec.js ├── max-data.spec.js ├── max-event-loop-delay.js ├── max-peer-per-protocol.spec.js ├── max-peers.spec.js ├── max-received-data.spec.js ├── max-sent-data.spec.js ├── set-peer-value.spec.js └── utils ├── connect-all.js ├── create-libp2p-node.js ├── prepare.js └── try-connect-all.js /.gitignore: -------------------------------------------------------------------------------- 1 | # While testing new npm 2 | package-lock.json 3 | yarn.lock 4 | 5 | # Logs 6 | logs 7 | *.log 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directory 30 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 31 | node_modules 32 | 33 | dist 34 | 35 | test/test-repo-for* 36 | docs 37 | 38 | test/test-repo/datastore 39 | 40 | .DS_Store -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: npm 3 | stages: 4 | - check 5 | - test 6 | - cov 7 | 8 | node_js: 9 | - '10' 10 | 11 | os: 12 | - linux 13 | - osx 14 | - windows 15 | 16 | script: npx nyc -s npm run test:node -- --bail 17 | after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov 18 | 19 | jobs: 20 | include: 21 | - stage: check 22 | script: 23 | - npx aegir build --bundlesize 24 | - npx aegir commitlint --travis 25 | - npm run lint 26 | 27 | notifications: 28 | email: false 29 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # [0.1.0](https://github.com/libp2p/js-libp2p-connection-manager/compare/v0.0.2...v0.1.0) (2019-04-11) 3 | 4 | 5 | ### Features 6 | 7 | * use new connection emitters ([#19](https://github.com/libp2p/js-libp2p-connection-manager/issues/19)) ([8f6a40a](https://github.com/libp2p/js-libp2p-connection-manager/commit/8f6a40a)) 8 | 9 | 10 | ### BREAKING CHANGES 11 | 12 | * This migrates from using muxed events from libp2p 13 | to the new connection events introduced in https://github.com/libp2p/js-libp2p-switch/pull/328. 14 | This enables the connection manager to have better information about 15 | the number of connections 16 | 17 | 18 | 19 | 20 | ## 0.0.2 (2018-04-23) 21 | 22 | 23 | ### Bug Fixes 24 | 25 | * npm scripts ([d8091cf](https://github.com/libp2p/js-libp2p-connection-manager/commit/d8091cf)) 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ⛔️ DEPRECATED: libp2p-connection-manager is now included in [js-libp2p](https://github.com/libp2p/js-libp2p) 2 | ===== 3 | 4 | # libp2p-connection-manager 5 | 6 | [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://protocol.ai) 7 | [![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) 8 | [![](https://img.shields.io/badge/freenode-%23libp2p-yellow.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23libp2p) 9 | [![Discourse posts](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg)](https://discuss.libp2p.io) 10 | [![](https://img.shields.io/codecov/c/github/libp2p/js-libp2p-connection-manager.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-connection-manager) 11 | [![](https://img.shields.io/travis/libp2p/js-libp2p-connection-manager.svg?style=flat-square)](https://travis-ci.com/libp2p/js-libp2p-connection-manager) 12 | [![Dependency Status](https://david-dm.org/libp2p/js-libp2p-connection-manager.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-connection-manager) 13 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard) 14 | ![](https://img.shields.io/badge/npm-%3E%3D6.0.0-orange.svg?style=flat-square) 15 | ![](https://img.shields.io/badge/Node.js-%3E%3D10.0.0-orange.svg?style=flat-square) 16 | 17 | > JavaScript connection manager for libp2p 18 | 19 | ## Lead Maintainer 20 | 21 | [Vasco Santos](https://github.com/vasco-santos). 22 | 23 | ## Table of Contents 24 | 25 | - [Install](#install) 26 | - [npm](#npm) 27 | - [Use in Node.js, a browser with browserify, webpack or any other bundler](##use-in-nodejs-or-in-the-browser-with-browserify-webpack-or-any-other-bundler) 28 | - [Usage](#usage) 29 | - [API](#api) 30 | - [Contribute](#contribute) 31 | - [License](#license) 32 | 33 | ## Install 34 | 35 | ### npm 36 | 37 | ```bash 38 | > npm install libp2p-connection-manager 39 | ``` 40 | 41 | ### Use in Node.js or in the browser with browserify, webpack or any other bundler 42 | 43 | ```js 44 | const ConnManager = require('libp2p-connection-manager') 45 | ``` 46 | 47 | 48 | ## API 49 | 50 | A connection manager manages the peers you're connected to. The application provides one or more limits that will trigger the disconnection of peers. These limits can be any of the following: 51 | 52 | * number of connected peers 53 | * maximum bandwidth (sent / received or both) 54 | * maximum event loop delay 55 | 56 | The connection manager will disconnect peers (starting from the less important peers) until all the measures are withing the stated limits. 57 | 58 | A connection manager first disconnects the peers with the least value. By default all peers have the same importance (1), but the application can define otherwise. Once a peer disconnects the connection manager discards the peer importance. (If necessary, the application should redefine the peer state if the peer is again connected). 59 | 60 | 61 | ### Create a ConnectionManager 62 | 63 | ```js 64 | const libp2p = // … 65 | const options = {…} 66 | const connManager = new ConnManager(libp2p, options) 67 | ``` 68 | 69 | Options is an optional object with the following key-value pairs: 70 | 71 | * **`maxPeers`**: number identifying the maximum number of peers the current peer is willing to be connected to before is starts disconnecting. Defaults to `Infinity` 72 | * **`maxPeersPerProtocol`**: Object with key-value pairs, where a key is the protocol tag (case-insensitive) and the value is a number, representing the maximum number of peers allowing to connect for each protocol. Defaults to `{}`. 73 | * **`minPeers`**: number identifying the number of peers below which this node will not activate preemptive disconnections. Defaults to `0`. 74 | * **`maxData`**: sets the maximum data — in bytes per second - (sent and received) this node is willing to endure before it starts disconnecting peers. Defaults to `Infinity`. 75 | * **`maxSentData`**: sets the maximum sent data — in bytes per second - this node is willing to endure before it starts disconnecting peers. Defaults to `Infinity`. 76 | * **`maxReceivedData`**: sets the maximum received data — in bytes per second - this node is willing to endure before it starts disconnecting peers. Defaults to `Infinity`. 77 | * **`maxEventLoopDelay`**: sets the maximum event loop delay (measured in miliseconds) this node is willing to endure before it starts disconnecting peers. Defaults to `Infinity`. 78 | * **`pollInterval`**: sets the poll interval (in miliseconds) for assessing the current state and determining if this peer needs to force a disconnect. Defaults to `2000` (2 seconds). 79 | * **`movingAverageInterval`**: the interval used to calculate moving averages (in miliseconds). Defaults to `60000` (1 minute). 80 | * **`defaultPeerValue`**: number between 0 and 1. Defaults to 1. 81 | 82 | 83 | ### `connManager.start()` 84 | 85 | Starts the connection manager. 86 | 87 | ### `connManager.stop()` 88 | 89 | Stops the connection manager. 90 | 91 | 92 | ### `connManager.setPeerValue(peerId, value)` 93 | 94 | Sets the peer value for a given peer id. This is used to sort peers (in reverse order of value) to determine which to disconnect from first. 95 | 96 | Arguments: 97 | 98 | * peerId: B58-encoded string or [`peer-id`](https://github.com/libp2p/js-peer-id) instance. 99 | * value: a number between 0 and 1, which represents a scale of how valuable this given peer id is to the application. 100 | 101 | ### `connManager.peers()` 102 | 103 | Returns the peers this connection manager is connected to. 104 | 105 | Returns an array of [PeerInfo](https://github.com/libp2p/js-peer-info). 106 | 107 | ### `connManager.emit('limit:exceeded', limitName, measured)` 108 | 109 | Emitted when a limit is exceeded. Limit names can be: 110 | 111 | * `maxPeers` 112 | * `minPeers` 113 | * `maxData` 114 | * `maxSentData` 115 | * `maxReceivedData` 116 | * `maxEventLoopDelay` 117 | * a protocol tag string (lower-cased) 118 | 119 | 120 | ### `connManager.emit('disconnect:preemptive', peerId)` 121 | 122 | Emitted when a peer is about to be preemptively disconnected. 123 | 124 | ### `connManager.emit('disconnected', peerId)` 125 | 126 | Emitted when a peer is disconnected (preemptively or note). If this peer reconnects, you will need to reset it's value, since the connection manager does not remember it. 127 | 128 | ### `connManager.emit('connected', peerId: String)` 129 | 130 | Emitted when a peer connects. This is a good event to set the peer value, so you can get some control over who gets banned once a maximum number of peers is reached. 131 | 132 | 133 | ## Contribute 134 | 135 | Feel free to join in. All welcome. Open an [issue](https://github.com/libp2p/js-libp2p-connection-manager/issues)! 136 | 137 | This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). 138 | 139 | [![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/contributing.md) 140 | 141 | ## License 142 | 143 | [MIT](LICENSE) 144 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "libp2p-connection-manager", 3 | "version": "0.1.0", 4 | "description": "JS Libp2p Connection Manager", 5 | "leadMaintainer": "Vasco Santos ", 6 | "main": "src/index.js", 7 | "files": [ 8 | "dist", 9 | "src" 10 | ], 11 | "scripts": { 12 | "lint": "aegir lint", 13 | "test": "aegir test -t node", 14 | "test:node": "aegir test -t node", 15 | "test:browser": "aegir test -t browser", 16 | "release": "aegir release -t node", 17 | "release-minor": "aegir release --type minor -t node", 18 | "release-major": "aegir release --type major -t node" 19 | }, 20 | "keywords": [ 21 | "ipfs", 22 | "connection", 23 | "resource" 24 | ], 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/libp2p/js-libp2p-connection-manager.git" 28 | }, 29 | "license": "MIT", 30 | "pre-push": [ 31 | "lint" 32 | ], 33 | "devDependencies": { 34 | "aegir": "^18.2.1", 35 | "async": "^2.6.0", 36 | "chai": "^4.2.0", 37 | "dirty-chai": "^2.0.1", 38 | "libp2p": "~0.25.0", 39 | "libp2p-mplex": "~0.8.5", 40 | "libp2p-secio": "~0.11.1", 41 | "libp2p-tcp": "~0.13.0", 42 | "lodash": "^4.17.10", 43 | "peer-id": "~0.12.2", 44 | "peer-info": "~0.15.1" 45 | }, 46 | "dependencies": { 47 | "debug": "^4.1.1", 48 | "latency-monitor": "~0.2.1" 49 | }, 50 | "contributors": [ 51 | "David Dias ", 52 | "Jacob Heun ", 53 | "Pedro Teixeira ", 54 | "Vasco Santos " 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const EventEmitter = require('events') 4 | const LatencyMonitor = require('latency-monitor').default 5 | const debug = require('debug')('libp2p:connection-manager') 6 | 7 | const defaultOptions = { 8 | maxPeers: Infinity, 9 | minPeers: 0, 10 | maxData: Infinity, 11 | maxSentData: Infinity, 12 | maxReceivedData: Infinity, 13 | maxEventLoopDelay: Infinity, 14 | pollInterval: 2000, 15 | movingAverageInterval: 60000, 16 | defaultPeerValue: 1 17 | } 18 | 19 | class ConnectionManager extends EventEmitter { 20 | constructor (libp2p, options) { 21 | super() 22 | this._libp2p = libp2p 23 | this._options = Object.assign({}, defaultOptions, options) 24 | this._options.maxPeersPerProtocol = fixMaxPeersPerProtocol(this._options.maxPeersPerProtocol) 25 | 26 | debug('options: %j', this._options) 27 | 28 | this._stats = libp2p.stats 29 | if (options && !this._stats) { 30 | throw new Error('No libp2p.stats') 31 | } 32 | 33 | this._peerValues = new Map() 34 | this._peers = new Map() 35 | this._peerProtocols = new Map() 36 | this._peerCountPerProtocol = new Map() 37 | this._onStatsUpdate = this._onStatsUpdate.bind(this) 38 | this._onPeerConnect = this._onPeerConnect.bind(this) 39 | this._onPeerDisconnect = this._onPeerDisconnect.bind(this) 40 | 41 | if (this._libp2p.isStarted()) { 42 | this._onceStarted() 43 | } else { 44 | this._libp2p.once('start', this._onceStarted.bind(this)) 45 | } 46 | } 47 | 48 | start () { 49 | this._stats.on('update', this._onStatsUpdate) 50 | this._libp2p.on('connection:start', this._onPeerConnect) 51 | this._libp2p.on('connection:end', this._onPeerDisconnect) 52 | // latency monitor 53 | this._latencyMonitor = new LatencyMonitor({ 54 | dataEmitIntervalMs: this._options.pollInterval 55 | }) 56 | this._onLatencyMeasure = this._onLatencyMeasure.bind(this) 57 | this._latencyMonitor.on('data', this._onLatencyMeasure) 58 | } 59 | 60 | stop () { 61 | this._stats.removeListener('update', this._onStatsUpdate) 62 | this._libp2p.removeListener('connection:start', this._onPeerConnect) 63 | this._libp2p.removeListener('connection:end', this._onPeerDisconnect) 64 | this._latencyMonitor.removeListener('data', this._onLatencyMeasure) 65 | } 66 | 67 | setPeerValue (peerId, value) { 68 | if (value < 0 || value > 1) { 69 | throw new Error('value should be a number between 0 and 1') 70 | } 71 | if (peerId.toB58String) { 72 | peerId = peerId.toB58String() 73 | } 74 | this._peerValues.set(peerId, value) 75 | } 76 | 77 | _onceStarted () { 78 | this._peerId = this._libp2p.peerInfo.id.toB58String() 79 | } 80 | 81 | _onStatsUpdate () { 82 | const movingAvgs = this._stats.global.movingAverages 83 | const received = movingAvgs.dataReceived[this._options.movingAverageInterval].movingAverage() 84 | this._checkLimit('maxReceivedData', received) 85 | const sent = movingAvgs.dataSent[this._options.movingAverageInterval].movingAverage() 86 | this._checkLimit('maxSentData', sent) 87 | const total = received + sent 88 | this._checkLimit('maxData', total) 89 | debug('stats update', total) 90 | } 91 | 92 | _onPeerConnect (peerInfo) { 93 | const peerId = peerInfo.id.toB58String() 94 | debug('%s: connected to %s', this._peerId, peerId) 95 | this._peerValues.set(peerId, this._options.defaultPeerValue) 96 | this._peers.set(peerId, peerInfo) 97 | this.emit('connected', peerId) 98 | this._checkLimit('maxPeers', this._peers.size) 99 | 100 | protocolsFromPeerInfo(peerInfo).forEach((protocolTag) => { 101 | const protocol = this._peerCountPerProtocol[protocolTag] 102 | if (!protocol) { 103 | this._peerCountPerProtocol[protocolTag] = 0 104 | } 105 | this._peerCountPerProtocol[protocolTag]++ 106 | 107 | let peerProtocols = this._peerProtocols[peerId] 108 | if (!peerProtocols) { 109 | peerProtocols = this._peerProtocols[peerId] = new Set() 110 | } 111 | peerProtocols.add(protocolTag) 112 | this._checkProtocolMaxPeersLimit(protocolTag, this._peerCountPerProtocol[protocolTag]) 113 | }) 114 | } 115 | 116 | _onPeerDisconnect (peerInfo) { 117 | const peerId = peerInfo.id.toB58String() 118 | debug('%s: disconnected from %s', this._peerId, peerId) 119 | this._peerValues.delete(peerId) 120 | this._peers.delete(peerId) 121 | 122 | const peerProtocols = this._peerProtocols[peerId] 123 | if (peerProtocols) { 124 | Array.from(peerProtocols).forEach((protocolTag) => { 125 | const peerCountForProtocol = this._peerCountPerProtocol[protocolTag] 126 | if (peerCountForProtocol) { 127 | this._peerCountPerProtocol[protocolTag]-- 128 | } 129 | }) 130 | } 131 | 132 | this.emit('disconnected', peerId) 133 | } 134 | 135 | _onLatencyMeasure (summary) { 136 | this._checkLimit('maxEventLoopDelay', summary.avgMs) 137 | } 138 | 139 | _checkLimit (name, value) { 140 | const limit = this._options[name] 141 | debug('checking limit of %s. current value: %d of %d', name, value, limit) 142 | if (value > limit) { 143 | debug('%s: limit exceeded: %s, %d', this._peerId, name, value) 144 | this.emit('limit:exceeded', name, value) 145 | this._maybeDisconnectOne() 146 | } 147 | } 148 | 149 | _checkProtocolMaxPeersLimit (protocolTag, value) { 150 | debug('checking protocol limit. current value of %s is %d', protocolTag, value) 151 | const limit = this._options.maxPeersPerProtocol[protocolTag] 152 | if (value > limit) { 153 | debug('%s: protocol max peers limit exceeded: %s, %d', this._peerId, protocolTag, value) 154 | this.emit('limit:exceeded', protocolTag, value) 155 | this._maybeDisconnectOne() 156 | } 157 | } 158 | 159 | _maybeDisconnectOne () { 160 | if (this._options.minPeers < this._peerValues.size) { 161 | const peerValues = Array.from(this._peerValues).sort(byPeerValue) 162 | debug('%s: sorted peer values: %j', this._peerId, peerValues) 163 | const disconnectPeer = peerValues[0] 164 | if (disconnectPeer) { 165 | const peerId = disconnectPeer[0] 166 | debug('%s: lowest value peer is %s', this._peerId, peerId) 167 | debug('%s: forcing disconnection from %j', this._peerId, peerId) 168 | this._disconnectPeer(peerId) 169 | } 170 | } 171 | } 172 | 173 | _disconnectPeer (peerId) { 174 | debug('preemptively disconnecting peer', peerId) 175 | this.emit('%s: disconnect:preemptive', this._peerId, peerId) 176 | const peer = this._peers.get(peerId) 177 | this._libp2p.hangUp(peer, (err) => { 178 | if (err) { 179 | this.emit('error', err) 180 | } 181 | }) 182 | } 183 | } 184 | 185 | module.exports = ConnectionManager 186 | 187 | function byPeerValue (peerValueEntryA, peerValueEntryB) { 188 | return peerValueEntryA[1] - peerValueEntryB[1] 189 | } 190 | 191 | function fixMaxPeersPerProtocol (maxPeersPerProtocol) { 192 | if (!maxPeersPerProtocol) { 193 | maxPeersPerProtocol = {} 194 | } 195 | 196 | Object.keys(maxPeersPerProtocol).forEach((transportTag) => { 197 | const max = maxPeersPerProtocol[transportTag] 198 | delete maxPeersPerProtocol[transportTag] 199 | maxPeersPerProtocol[transportTag.toLowerCase()] = max 200 | }) 201 | 202 | return maxPeersPerProtocol 203 | } 204 | 205 | function protocolsFromPeerInfo (peerInfo) { 206 | const protocolTags = new Set() 207 | peerInfo.multiaddrs.forEach((multiaddr) => { 208 | multiaddr.protos().map(protocolToProtocolTag).forEach((protocolTag) => { 209 | protocolTags.add(protocolTag) 210 | }) 211 | }) 212 | 213 | return Array.from(protocolTags) 214 | } 215 | 216 | function protocolToProtocolTag (protocol) { 217 | return protocol.name.toLowerCase() 218 | } 219 | -------------------------------------------------------------------------------- /test/default.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 'use strict' 3 | 4 | const Prepare = require('./utils/prepare') 5 | 6 | describe('default', function () { 7 | const prepare = Prepare(3, { pollInterval: 1000 }) 8 | before(prepare.before) 9 | after(prepare.after) 10 | 11 | it('does not kick out any peer', (done) => { 12 | prepare.connManagers().forEach((connManager) => { 13 | connManager.on('disconnected', () => { 14 | throw new Error('should not have disconnected') 15 | }) 16 | }) 17 | setTimeout(done, 1900) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /test/max-data.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 'use strict' 3 | 4 | const chai = require('chai') 5 | chai.use(require('dirty-chai')) 6 | const expect = chai.expect 7 | 8 | const Prepare = require('./utils/prepare') 9 | 10 | const PEER_COUNT = 3 11 | 12 | describe('maxData', function () { 13 | const prepare = Prepare(PEER_COUNT, { 14 | maxData: 100, 15 | minPeers: 1 16 | }) 17 | before(prepare.create) 18 | after(prepare.after) 19 | 20 | it('kicks out peer after maxData reached', function (done) { 21 | this.timeout(10000) 22 | 23 | let disconnects = 0 24 | const manager = prepare.connManagers()[0] 25 | manager.on('disconnected', () => { 26 | disconnects++ 27 | expect(disconnects).to.be.most(PEER_COUNT - 2) 28 | manager.removeAllListeners('disconnected') 29 | done() 30 | }) 31 | 32 | prepare.tryConnectAll((err) => { 33 | expect(err).to.not.exist() 34 | }) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /test/max-event-loop-delay.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 'use strict' 3 | 4 | const chai = require('chai') 5 | chai.use(require('dirty-chai')) 6 | const expect = chai.expect 7 | 8 | const Prepare = require('./utils/prepare') 9 | 10 | const PEER_COUNT = 3 11 | 12 | describe('maxEventLoopDelay', function () { 13 | const prepare = Prepare(PEER_COUNT, [{ 14 | pollInterval: 1000, 15 | maxEventLoopDelay: 5, 16 | minPeers: 1 17 | }]) 18 | before(prepare.create) 19 | after(prepare.after) 20 | 21 | it('kicks out peer after maxEventLoopDelay reached', function (done) { 22 | this.timeout(10000) 23 | let stopped = false 24 | 25 | let disconnects = 0 26 | const manager = prepare.connManagers()[0] 27 | manager.on('disconnected', () => { 28 | disconnects++ 29 | expect(disconnects).to.be.most(PEER_COUNT - 2) 30 | manager.removeAllListeners('disconnected') 31 | stopped = true 32 | done() 33 | }) 34 | 35 | prepare.tryConnectAll((err) => { 36 | expect(err).to.not.exist() 37 | makeDelay() 38 | }) 39 | 40 | function makeDelay () { 41 | let sum = 0 42 | for (let i = 0; i < 1000000; i++) { 43 | sum += Math.random() 44 | } 45 | debug(sum) 46 | 47 | if (!stopped) { 48 | setTimeout(makeDelay, 0) 49 | } 50 | } 51 | }) 52 | }) 53 | 54 | function debug (what) { 55 | if (what === 0) { 56 | // never true but the compiler doesn't know that 57 | console.log('WHAT') 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/max-peer-per-protocol.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 'use strict' 3 | 4 | const chai = require('chai') 5 | chai.use(require('dirty-chai')) 6 | const expect = chai.expect 7 | 8 | const Prepare = require('./utils/prepare') 9 | 10 | const PEER_COUNT = 3 11 | 12 | describe('maxPeers', function () { 13 | const prepare = Prepare(PEER_COUNT, [{ 14 | maxPeersPerProtocol: { 15 | tcp: 1 16 | } 17 | }]) 18 | before(prepare.create) 19 | after(prepare.after) 20 | 21 | it('kicks out peers in excess', function (done) { 22 | this.timeout(10000) 23 | 24 | let disconnects = 0 25 | const manager = prepare.connManagers()[0] 26 | manager.on('disconnected', () => { 27 | disconnects++ 28 | expect(disconnects).to.be.most(PEER_COUNT - 2) 29 | manager.removeAllListeners('disconnected') 30 | done() 31 | }) 32 | 33 | prepare.tryConnectAll((err) => { 34 | expect(err).to.not.exist() 35 | }) 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /test/max-peers.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 'use strict' 3 | 4 | const chai = require('chai') 5 | chai.use(require('dirty-chai')) 6 | const expect = chai.expect 7 | 8 | const Prepare = require('./utils/prepare') 9 | 10 | const PEER_COUNT = 3 11 | 12 | describe('maxPeers', function () { 13 | const prepare = Prepare(PEER_COUNT, [{ 14 | maxPeers: 1 15 | }]) 16 | before(prepare.create) 17 | after(prepare.after) 18 | 19 | it('kicks out peers in excess', function (done) { 20 | this.timeout(10000) 21 | 22 | let disconnects = 0 23 | const manager = prepare.connManagers()[0] 24 | manager.on('disconnected', () => { 25 | disconnects++ 26 | expect(disconnects).to.be.most(PEER_COUNT - 2) 27 | manager.removeAllListeners('disconnected') 28 | done() 29 | }) 30 | 31 | prepare.tryConnectAll((err, eachNodeConnections) => { 32 | expect(err).to.not.exist() 33 | }) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /test/max-received-data.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 'use strict' 3 | 4 | const chai = require('chai') 5 | chai.use(require('dirty-chai')) 6 | const expect = chai.expect 7 | 8 | const Prepare = require('./utils/prepare') 9 | 10 | const PEER_COUNT = 3 11 | 12 | describe('maxReceivedData', function () { 13 | const prepare = Prepare(PEER_COUNT, { 14 | maxReceivedData: 50, 15 | minPeers: 1 16 | }) 17 | before(prepare.create) 18 | after(prepare.after) 19 | 20 | it('kicks out peer after maxReceivedData reached', function (done) { 21 | this.timeout(10000) 22 | 23 | let disconnects = 0 24 | const manager = prepare.connManagers()[0] 25 | manager.on('disconnected', () => { 26 | disconnects++ 27 | expect(disconnects).to.be.most(PEER_COUNT - 2) 28 | manager.removeAllListeners('disconnected') 29 | done() 30 | }) 31 | 32 | prepare.tryConnectAll((err, eachNodeConnections) => { 33 | expect(err).to.not.exist() 34 | }) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /test/max-sent-data.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 'use strict' 3 | 4 | const chai = require('chai') 5 | chai.use(require('dirty-chai')) 6 | const expect = chai.expect 7 | 8 | const Prepare = require('./utils/prepare') 9 | 10 | const PEER_COUNT = 3 11 | 12 | describe('maxSentData', function () { 13 | const prepare = Prepare(PEER_COUNT, [{ 14 | maxSentData: 50, 15 | minPeers: 1 16 | }]) 17 | before(prepare.create) 18 | after(prepare.after) 19 | 20 | it('kicks out peer after maxSentData reached', function (done) { 21 | this.timeout(10000) 22 | 23 | let disconnects = 0 24 | const manager = prepare.connManagers()[0] 25 | manager.on('disconnected', () => { 26 | disconnects++ 27 | expect(disconnects).to.be.most(PEER_COUNT - 2) 28 | manager.removeAllListeners('disconnected') 29 | done() 30 | }) 31 | 32 | prepare.tryConnectAll((err, eachNodeConnections) => { 33 | expect(err).to.not.exist() 34 | }) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /test/set-peer-value.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 'use strict' 3 | 4 | const chai = require('chai') 5 | chai.use(require('dirty-chai')) 6 | const expect = chai.expect 7 | 8 | const Prepare = require('./utils/prepare') 9 | 10 | const PEER_COUNT = 3 11 | 12 | describe('setPeerValue', function () { 13 | const prepare = Prepare(PEER_COUNT, [{ 14 | maxPeers: 1, 15 | defaultPeerValue: 0 16 | }]) 17 | before(prepare.create) 18 | after(prepare.after) 19 | 20 | it('kicks out lower valued peer first', function (done) { 21 | let disconnects = 0 22 | let firstConnectedPeer 23 | const manager = prepare.connManagers()[0] 24 | 25 | manager.once('connected', (peerId) => { 26 | if (!firstConnectedPeer) { 27 | firstConnectedPeer = peerId 28 | manager.setPeerValue(peerId, 1) 29 | } 30 | }) 31 | 32 | manager.on('disconnected', (peerId) => { 33 | disconnects++ 34 | expect(disconnects).to.be.most(PEER_COUNT - 2) 35 | expect(peerId).to.not.be.equal(firstConnectedPeer) 36 | manager.removeAllListeners('disconnected') 37 | done() 38 | }) 39 | 40 | prepare.tryConnectAll((err) => { 41 | expect(err).to.not.exist() 42 | }) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /test/utils/connect-all.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const eachSeries = require('async/eachSeries') 4 | const without = require('lodash/without') 5 | 6 | module.exports = (nodes, callback) => { 7 | eachSeries( 8 | nodes, 9 | (node, cb) => { 10 | eachSeries( 11 | without(nodes, node), 12 | (otherNode, cb) => node.dial(otherNode.peerInfo, cb), 13 | cb 14 | ) 15 | }, 16 | callback 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /test/utils/create-libp2p-node.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const TCP = require('libp2p-tcp') 4 | const Multiplex = require('libp2p-mplex') 5 | const SECIO = require('libp2p-secio') 6 | const libp2p = require('libp2p') 7 | const waterfall = require('async/waterfall') 8 | const PeerInfo = require('peer-info') 9 | const PeerId = require('peer-id') 10 | 11 | const ConnManager = require('../../') 12 | 13 | class Node extends libp2p { 14 | constructor (peerInfo) { 15 | const modules = { 16 | transport: [TCP], 17 | streamMuxer: [Multiplex], 18 | connEncryption: [SECIO] 19 | } 20 | 21 | super({ 22 | peerInfo, 23 | modules, 24 | config: { 25 | peerDiscovery: { 26 | autoDial: false 27 | } 28 | } 29 | }) 30 | } 31 | } 32 | 33 | function createLibp2pNode (options, callback) { 34 | let node 35 | 36 | waterfall([ 37 | (cb) => PeerId.create({ bits: 1024 }, cb), 38 | (id, cb) => PeerInfo.create(id, cb), 39 | (peerInfo, cb) => { 40 | peerInfo.multiaddrs.add('/ip4/127.0.0.1/tcp/0') 41 | node = new Node(peerInfo) 42 | // Replace the connection manager so we use source code instead of dep code 43 | node.connectionManager = new ConnManager(node, options) 44 | node.start(cb) 45 | } 46 | ], (err) => callback(err, node)) 47 | } 48 | 49 | exports = module.exports = createLibp2pNode 50 | exports.bundle = Node 51 | -------------------------------------------------------------------------------- /test/utils/prepare.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const chai = require('chai') 4 | chai.use(require('dirty-chai')) 5 | const expect = chai.expect 6 | 7 | const series = require('async/series') 8 | const each = require('async/each') 9 | 10 | const createLibp2pNode = require('./create-libp2p-node') 11 | const connectAll = require('./connect-all') 12 | const tryConnectAll = require('./try-connect-all') 13 | 14 | module.exports = (count, options) => { 15 | let nodes 16 | 17 | if (!Array.isArray(options)) { 18 | const opts = options 19 | options = [] 20 | for (let n = 0; n < count; n++) { 21 | options[n] = opts 22 | } 23 | } 24 | 25 | const create = (done) => { 26 | const tasks = [] 27 | for (let i = 0; i < count; i++) { 28 | tasks.push((cb) => createLibp2pNode(options.shift() || {}, cb)) 29 | } 30 | 31 | series(tasks, (err, things) => { 32 | if (!err) { 33 | nodes = things 34 | expect(things.length).to.equal(count) 35 | } 36 | done(err) 37 | }) 38 | } 39 | 40 | const connect = function (done) { 41 | if (this && this.timeout) { 42 | this.timeout(10000) 43 | } 44 | connectAll(nodes, done) 45 | } 46 | 47 | const tryConnectAllFn = function (done) { 48 | if (this && this.timeout) { 49 | this.timeout(10000) 50 | } 51 | tryConnectAll(nodes, done) 52 | } 53 | 54 | const before = (done) => { 55 | if (this && this.timeout) { 56 | this.timeout(10000) 57 | } 58 | series([ create, connect ], done) 59 | } 60 | 61 | const after = function (done) { 62 | if (this && this.timeout) { 63 | this.timeout(10000) 64 | } 65 | if (!nodes) { return done() } 66 | 67 | each(nodes, (node, cb) => { 68 | series([ 69 | (cb) => node.stop(cb) 70 | ], cb) 71 | }, done) 72 | } 73 | 74 | return { 75 | create, 76 | connect, 77 | tryConnectAll: tryConnectAllFn, 78 | before, 79 | after, 80 | things: () => nodes, 81 | connManagers: () => nodes.map((node) => node.connectionManager) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /test/utils/try-connect-all.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const mapSeries = require('async/mapSeries') 4 | const eachSeries = require('async/eachSeries') 5 | const without = require('lodash/without') 6 | 7 | module.exports = (nodes, callback) => { 8 | mapSeries( 9 | nodes, 10 | (node, cb) => { 11 | const connectedTo = [] 12 | eachSeries( 13 | without(nodes, node), 14 | (otherNode, cb) => { 15 | const otherNodePeerInfo = otherNode.peerInfo 16 | node.dial(otherNodePeerInfo, (err) => { 17 | if (!err) { 18 | connectedTo.push(otherNodePeerInfo.id.toB58String()) 19 | } 20 | cb() 21 | }) 22 | }, 23 | (err) => cb(err, connectedTo) 24 | ) 25 | }, 26 | callback 27 | ) 28 | } 29 | --------------------------------------------------------------------------------