├── .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 | [](http://protocol.ai)
7 | [](http://libp2p.io/)
8 | [](http://webchat.freenode.net/?channels=%23libp2p)
9 | [](https://discuss.libp2p.io)
10 | [](https://codecov.io/gh/libp2p/js-libp2p-connection-manager)
11 | [](https://travis-ci.com/libp2p/js-libp2p-connection-manager)
12 | [](https://david-dm.org/libp2p/js-libp2p-connection-manager)
13 | [](https://github.com/feross/standard)
14 | 
15 | 
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://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 |
--------------------------------------------------------------------------------