mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-06-14 09:41:22 +00:00
feat: auto dial discovered peers (#349)
This commit is contained in:
@ -79,7 +79,7 @@ const after = (done) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
bundlesize: { maxSize: '217kB' },
|
bundlesize: { maxSize: '218kB' },
|
||||||
hooks: {
|
hooks: {
|
||||||
pre: before,
|
pre: before,
|
||||||
post: after
|
post: after
|
||||||
|
60
PEER_DISCOVERY.md
Normal file
60
PEER_DISCOVERY.md
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# Peer Discovery and Auto Dial
|
||||||
|
|
||||||
|
**Synopsis**:
|
||||||
|
* All peers discovered are emitted via `peer:discovery` so applications can take any desired action.
|
||||||
|
* Libp2p defaults to automatically connecting to new peers, when under the [ConnectionManager](https://github.com/libp2p/js-libp2p-connection-manager) low watermark (minimum peers).
|
||||||
|
* Applications can disable this via the `peerDiscovery.autoDial` config property, and handle connections themselves.
|
||||||
|
* Applications who have not disabled this should **never** connect on peer discovery. Applications should use the `peer:connect` event if they wish to take a specific action on new peers.
|
||||||
|
|
||||||
|
## Scenarios
|
||||||
|
In any scenario, if a peer is discovered it should be added to the PeerBook. This ensures that even if we don't dial to a node when we discover it, we know about it in the event that it becomes known as a provider for something we need. The scenarios listed below detail what actions the auto dialer will take when peers are discovered.
|
||||||
|
|
||||||
|
### 1. Joining the network
|
||||||
|
The node is new and needs to join the network. It currently has 0 peers.
|
||||||
|
**Discovery Mechanisms**: [Ambient Discovery](#ambient-discovery)
|
||||||
|
|
||||||
|
### Action to take
|
||||||
|
Connect to discovered peers. This should have some degree of concurrency limiting. While the case should be low, if we immediately discover more peers than our high watermark we should avoid dialing them all.
|
||||||
|
|
||||||
|
### 2. Connected to some
|
||||||
|
The node is connected to other nodes. The current number of connections is less than the desired low watermark.
|
||||||
|
**Discovery Mechanisms**: [Ambient Discovery](#ambient-discovery) and [Active Discovery](#active-discovery)
|
||||||
|
|
||||||
|
### Action to take
|
||||||
|
Connect to discovered peers. This should have some degree of concurrency limiting. The concurrency may need to be modified to reflect the current number of peers connected. The more peers we have, the lower the concurrency may need to be.
|
||||||
|
|
||||||
|
### 3. Connected to enough
|
||||||
|
**Discovery Mechanisms**: [Ambient Discovery](#ambient-discovery) and [Active Discovery](#active-discovery)
|
||||||
|
|
||||||
|
### Action to take
|
||||||
|
None. If we are connected to enough peers, the low watermark, we should not connect to discovered peers. As other peers discover us, they may connect to us based on their current scenario.
|
||||||
|
|
||||||
|
For example, a long running node with adequate peers is on an MDNS network. A new peer joins the network and both become aware of each other. The new peer should be the peer that dials, as it has too few peers. The existing node has no reason to dial the new peer, but should keep a record of it in case it later becomes an important node due to its contents/capabilities.
|
||||||
|
|
||||||
|
Avoiding dials above the low watermark also allows for a pool of connections to be reserved for application specific actions, such as connecting to a specific content provider via a DHT query to find that content (ipfs-bitswap).
|
||||||
|
|
||||||
|
### 4. Connected to too many
|
||||||
|
The node has more connections than it wants. The current number of connections is greater than the high watermark.
|
||||||
|
|
||||||
|
[WIP Connection Manager v2 spec](https://github.com/libp2p/specs/pull/161)
|
||||||
|
**Discovery Mechanisms**: [Ambient Discovery](#ambient-discovery) and [Active Discovery](#active-discovery)
|
||||||
|
|
||||||
|
### Action to take
|
||||||
|
None, the `ConnectionManager` will automatically prune connections.
|
||||||
|
|
||||||
|
## Discovery Mechanisms
|
||||||
|
Means of which a libp2p node discovers other peers.
|
||||||
|
|
||||||
|
### Active Discovery
|
||||||
|
Through active use of the libp2p network, a node may discovery peers.
|
||||||
|
|
||||||
|
* Content/Peer routing (DHT, delegated, etc) provider and peer queries
|
||||||
|
* DHT random walk
|
||||||
|
* Rendezvous servers
|
||||||
|
|
||||||
|
### Ambient Discovery
|
||||||
|
Leveraging known addresses, or network discovery mechanisms, a node may discover peers outside of the bounds of the libp2p network.
|
||||||
|
|
||||||
|
* Bootstrap
|
||||||
|
* MDNS
|
||||||
|
* proximity based (bluetooth, sound, etc)
|
16
README.md
16
README.md
@ -160,6 +160,7 @@ class Node extends libp2p {
|
|||||||
// libp2p config options (typically found on a config.json)
|
// libp2p config options (typically found on a config.json)
|
||||||
config: { // The config object is the part of the config that can go into a file, config.json.
|
config: { // The config object is the part of the config that can go into a file, config.json.
|
||||||
peerDiscovery: {
|
peerDiscovery: {
|
||||||
|
autoDial: true, // Auto connect to discovered peers (limited by ConnectionManager minPeers)
|
||||||
mdns: { // mdns options
|
mdns: { // mdns options
|
||||||
interval: 1000, // ms
|
interval: 1000, // ms
|
||||||
enabled: true
|
enabled: true
|
||||||
@ -305,6 +306,9 @@ Required keys in the `options` object:
|
|||||||
|
|
||||||
> Peer has been discovered.
|
> Peer has been discovered.
|
||||||
|
|
||||||
|
If `autoDial` is `true`, applications should **not** attempt to connect to the peer
|
||||||
|
unless they are performing a specific action. See [peer discovery and auto dial](./PEER_DISCOVERY.md) for more information.
|
||||||
|
|
||||||
- `peer`: instance of [PeerInfo][]
|
- `peer`: instance of [PeerInfo][]
|
||||||
|
|
||||||
##### `libp2p.on('peer:connect', (peer) => {})`
|
##### `libp2p.on('peer:connect', (peer) => {})`
|
||||||
@ -509,18 +513,6 @@ Some available network protectors:
|
|||||||
> npm run test:browser
|
> npm run test:browser
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Run interop tests
|
|
||||||
|
|
||||||
```sh
|
|
||||||
N/A
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Run benchmark tests
|
|
||||||
|
|
||||||
```sh
|
|
||||||
N/A
|
|
||||||
```
|
|
||||||
|
|
||||||
### Packages
|
### Packages
|
||||||
|
|
||||||
List of packages currently in existence for libp2p
|
List of packages currently in existence for libp2p
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
"libp2p-connection-manager": "~0.0.2",
|
"libp2p-connection-manager": "~0.0.2",
|
||||||
"libp2p-floodsub": "~0.15.8",
|
"libp2p-floodsub": "~0.15.8",
|
||||||
"libp2p-ping": "~0.8.5",
|
"libp2p-ping": "~0.8.5",
|
||||||
"libp2p-switch": "~0.42.7",
|
"libp2p-switch": "~0.42.8",
|
||||||
"libp2p-websockets": "~0.12.2",
|
"libp2p-websockets": "~0.12.2",
|
||||||
"mafmt": "^6.0.7",
|
"mafmt": "^6.0.7",
|
||||||
"multiaddr": "^6.0.6",
|
"multiaddr": "^6.0.6",
|
||||||
|
136
src/config.js
136
src/config.js
@ -12,58 +12,88 @@ const transport = s.union([
|
|||||||
}),
|
}),
|
||||||
'function'
|
'function'
|
||||||
])
|
])
|
||||||
|
const modulesSchema = s({
|
||||||
|
connEncryption: optional(list([s('object|function')])),
|
||||||
|
// this is hacky to simulate optional because interface doesnt work correctly with it
|
||||||
|
// change to optional when fixed upstream
|
||||||
|
connProtector: s.union(['undefined', s.interface({ protect: 'function' })]),
|
||||||
|
contentRouting: optional(list(['object'])),
|
||||||
|
dht: optional(s('null|function|object')),
|
||||||
|
peerDiscovery: optional(list([s('object|function')])),
|
||||||
|
peerRouting: optional(list(['object'])),
|
||||||
|
streamMuxer: optional(list([s('object|function')])),
|
||||||
|
transport: s.intersection([[transport], s.interface({
|
||||||
|
length (v) {
|
||||||
|
return v > 0 ? true : 'ERROR_EMPTY'
|
||||||
|
}
|
||||||
|
})])
|
||||||
|
})
|
||||||
|
|
||||||
const optionsSchema = s(
|
const configSchema = s({
|
||||||
{
|
peerDiscovery: s('object', {
|
||||||
connectionManager: 'object?',
|
autoDial: true
|
||||||
datastore: 'object?',
|
}),
|
||||||
peerInfo: 'object',
|
relay: s({
|
||||||
peerBook: 'object?',
|
enabled: 'boolean',
|
||||||
modules: s({
|
hop: optional(s({
|
||||||
connEncryption: optional(list([s('object|function')])),
|
enabled: 'boolean',
|
||||||
// this is hacky to simulate optional because interface doesnt work correctly with it
|
active: 'boolean'
|
||||||
// change to optional when fixed upstream
|
}, {
|
||||||
connProtector: s.union(['undefined', s.interface({ protect: 'function' })]),
|
// HOP defaults
|
||||||
contentRouting: optional(list(['object'])),
|
enabled: false,
|
||||||
dht: optional(s('null|function|object')),
|
active: false
|
||||||
peerDiscovery: optional(list([s('object|function')])),
|
}))
|
||||||
peerRouting: optional(list(['object'])),
|
}, {
|
||||||
streamMuxer: optional(list([s('object|function')])),
|
// Relay defaults
|
||||||
transport: s.intersection([[transport], s.interface({
|
enabled: true
|
||||||
length (v) {
|
}),
|
||||||
return v > 0 ? true : 'ERROR_EMPTY'
|
// DHT config
|
||||||
}
|
dht: s({
|
||||||
})])
|
kBucketSize: 'number',
|
||||||
}),
|
enabled: 'boolean?',
|
||||||
config: s({
|
validators: 'object?',
|
||||||
peerDiscovery: 'object?',
|
selectors: 'object?',
|
||||||
relay: s({
|
randomWalk: optional(s({
|
||||||
enabled: 'boolean',
|
enabled: 'boolean?',
|
||||||
hop: optional(s({
|
queriesPerPeriod: 'number?',
|
||||||
enabled: 'boolean',
|
interval: 'number?',
|
||||||
active: 'boolean'
|
timeout: 'number?'
|
||||||
},
|
}, {
|
||||||
{ enabled: false, active: false }))
|
// random walk defaults
|
||||||
}, { enabled: true, hop: {} }),
|
enabled: false, // disabled waiting for https://github.com/libp2p/js-libp2p-kad-dht/issues/86
|
||||||
dht: s({
|
queriesPerPeriod: 1,
|
||||||
kBucketSize: 'number',
|
interval: 30000,
|
||||||
enabled: 'boolean?',
|
timeout: 10000
|
||||||
randomWalk: optional(s({
|
}))
|
||||||
enabled: 'boolean?', // disabled waiting for https://github.com/libp2p/js-libp2p-kad-dht/issues/86
|
}, {
|
||||||
queriesPerPeriod: 'number?',
|
// DHT defaults
|
||||||
interval: 'number?',
|
enabled: false,
|
||||||
timeout: 'number?'
|
kBucketSize: 20,
|
||||||
}, { enabled: false, queriesPerPeriod: 1, interval: 30000, timeout: 10000 })),
|
enabledDiscovery: false
|
||||||
validators: 'object?',
|
}),
|
||||||
selectors: 'object?'
|
// Experimental config
|
||||||
}, { enabled: false, kBucketSize: 20, enabledDiscovery: false }),
|
EXPERIMENTAL: s({
|
||||||
EXPERIMENTAL: s({
|
pubsub: 'boolean'
|
||||||
pubsub: 'boolean'
|
}, {
|
||||||
}, { pubsub: false })
|
// Experimental defaults
|
||||||
}, { relay: {}, dht: {}, EXPERIMENTAL: {} })
|
pubsub: false
|
||||||
},
|
})
|
||||||
{ config: {}, modules: {} }
|
}, {
|
||||||
)
|
relay: {},
|
||||||
|
dht: {},
|
||||||
|
EXPERIMENTAL: {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const optionsSchema = s({
|
||||||
|
connectionManager: s('object', {
|
||||||
|
minPeers: 25
|
||||||
|
}),
|
||||||
|
datastore: 'object?',
|
||||||
|
peerInfo: 'object',
|
||||||
|
peerBook: 'object?',
|
||||||
|
modules: modulesSchema,
|
||||||
|
config: configSchema
|
||||||
|
})
|
||||||
|
|
||||||
module.exports.validate = (opts) => {
|
module.exports.validate = (opts) => {
|
||||||
const [error, options] = optionsSchema.validate(opts)
|
const [error, options] = optionsSchema.validate(opts)
|
||||||
@ -78,5 +108,9 @@ module.exports.validate = (opts) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.config.peerDiscovery.autoDial === undefined) {
|
||||||
|
options.config.peerDiscovery.autoDial = true
|
||||||
|
}
|
||||||
|
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
188
src/index.js
188
src/index.js
@ -45,23 +45,23 @@ class Node extends EventEmitter {
|
|||||||
super()
|
super()
|
||||||
// validateConfig will ensure the config is correct,
|
// validateConfig will ensure the config is correct,
|
||||||
// and add default values where appropriate
|
// and add default values where appropriate
|
||||||
_options = validateConfig(_options)
|
this._options = validateConfig(_options)
|
||||||
|
|
||||||
this.datastore = _options.datastore
|
this.datastore = this._options.datastore
|
||||||
this.peerInfo = _options.peerInfo
|
this.peerInfo = this._options.peerInfo
|
||||||
this.peerBook = _options.peerBook || new PeerBook()
|
this.peerBook = this._options.peerBook || new PeerBook()
|
||||||
|
|
||||||
this._modules = _options.modules
|
this._modules = this._options.modules
|
||||||
this._config = _options.config
|
this._config = this._options.config
|
||||||
this._transport = [] // Transport instances/references
|
this._transport = [] // Transport instances/references
|
||||||
this._discovery = [] // Discovery service instances/references
|
this._discovery = [] // Discovery service instances/references
|
||||||
|
|
||||||
// create the switch, and listen for errors
|
// 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._switch.on('error', (...args) => this.emit('error', ...args))
|
||||||
|
|
||||||
this.stats = this._switch.stats
|
this.stats = this._switch.stats
|
||||||
this.connectionManager = new ConnectionManager(this, _options.connectionManager)
|
this.connectionManager = new ConnectionManager(this, this._options.connectionManager)
|
||||||
|
|
||||||
// Attach stream multiplexers
|
// Attach stream multiplexers
|
||||||
if (this._modules.streamMuxer) {
|
if (this._modules.streamMuxer) {
|
||||||
@ -165,6 +165,16 @@ class Node extends EventEmitter {
|
|||||||
log.error(err)
|
log.error(err)
|
||||||
this.emit('error', err)
|
this.emit('error', err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -352,45 +362,21 @@ class Node extends EventEmitter {
|
|||||||
this._switch.transport.add(ws.tag || ws.constructor.name, ws)
|
this._switch.transport.add(ws.tag || ws.constructor.name, ws)
|
||||||
}
|
}
|
||||||
|
|
||||||
// all transports need to be setup before discover starts
|
// detect which multiaddrs we don't have a transport for and remove them
|
||||||
if (this._modules.peerDiscovery) {
|
const multiaddrs = this.peerInfo.multiaddrs.toArray()
|
||||||
each(this._modules.peerDiscovery, (D, _cb) => {
|
|
||||||
let config = {}
|
|
||||||
|
|
||||||
if (D.tag &&
|
multiaddrs.forEach((multiaddr) => {
|
||||||
this._config.peerDiscovery &&
|
if (!multiaddr.toString().match(/\/p2p-circuit($|\/)/) &&
|
||||||
this._config.peerDiscovery[D.tag]) {
|
!this._transport.find((transport) => transport.filter(multiaddr).length > 0)) {
|
||||||
config = this._config.peerDiscovery[D.tag]
|
this.peerInfo.multiaddrs.delete(multiaddr)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
// If not configured to be enabled/disabled then enable by default
|
cb()
|
||||||
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)
|
|
||||||
} else {
|
|
||||||
cb()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
(cb) => {
|
(cb) => {
|
||||||
if (this._dht) {
|
if (this._dht) {
|
||||||
this._dht.start(() => {
|
this._dht.start(() => {
|
||||||
this._dht.on('peer', (peerInfo) => this.emit('peer:discovery', peerInfo))
|
this._dht.on('peer', this._peerDiscovered)
|
||||||
cb()
|
cb()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -403,17 +389,13 @@ class Node extends EventEmitter {
|
|||||||
}
|
}
|
||||||
cb()
|
cb()
|
||||||
},
|
},
|
||||||
|
// Peer Discovery
|
||||||
(cb) => {
|
(cb) => {
|
||||||
// detect which multiaddrs we don't have a transport for and remove them
|
if (this._modules.peerDiscovery) {
|
||||||
const multiaddrs = this.peerInfo.multiaddrs.toArray()
|
this._setupPeerDiscovery(cb)
|
||||||
|
} else {
|
||||||
multiaddrs.forEach((multiaddr) => {
|
cb()
|
||||||
if (!multiaddr.toString().match(/\/p2p-circuit($|\/)/) &&
|
}
|
||||||
!this._transport.find((transport) => transport.filter(multiaddr).length > 0)) {
|
|
||||||
this.peerInfo.multiaddrs.delete(multiaddr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
cb()
|
|
||||||
}
|
}
|
||||||
], (err) => {
|
], (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -428,16 +410,17 @@ class Node extends EventEmitter {
|
|||||||
_onStopping () {
|
_onStopping () {
|
||||||
series([
|
series([
|
||||||
(cb) => {
|
(cb) => {
|
||||||
if (this._modules.peerDiscovery) {
|
// stop all discoveries before continuing with shutdown
|
||||||
// stop all discoveries before continuing with shutdown
|
parallel(
|
||||||
return parallel(
|
this._discovery.map((d) => {
|
||||||
this._discovery.map((d) => {
|
d.removeListener('peer', this._peerDiscovered)
|
||||||
return (_cb) => d.stop(() => { _cb() })
|
return (_cb) => d.stop((err) => {
|
||||||
}),
|
log.error('an error occurred stopping the discovery service', err)
|
||||||
cb
|
_cb()
|
||||||
)
|
})
|
||||||
}
|
}),
|
||||||
cb()
|
cb
|
||||||
|
)
|
||||||
},
|
},
|
||||||
(cb) => {
|
(cb) => {
|
||||||
if (this._floodSub) {
|
if (this._floodSub) {
|
||||||
@ -447,6 +430,7 @@ class Node extends EventEmitter {
|
|||||||
},
|
},
|
||||||
(cb) => {
|
(cb) => {
|
||||||
if (this._dht) {
|
if (this._dht) {
|
||||||
|
this._dht.removeListener('peer', this._peerDiscovered)
|
||||||
return this._dht.stop(cb)
|
return this._dht.stop(cb)
|
||||||
}
|
}
|
||||||
cb()
|
cb()
|
||||||
@ -468,6 +452,86 @@ class Node extends EventEmitter {
|
|||||||
this.state('done')
|
this.state('done')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Node
|
module.exports = Node
|
||||||
|
@ -58,6 +58,56 @@ describe('configuration', () => {
|
|||||||
}).to.throw('ERROR_EMPTY')
|
}).to.throw('ERROR_EMPTY')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should add defaults to config', () => {
|
||||||
|
const options = {
|
||||||
|
peerInfo,
|
||||||
|
modules: {
|
||||||
|
transport: [ WS ],
|
||||||
|
peerDiscovery: [ Bootstrap ],
|
||||||
|
dht: DHT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const expected = {
|
||||||
|
peerInfo,
|
||||||
|
connectionManager: {
|
||||||
|
minPeers: 25
|
||||||
|
},
|
||||||
|
modules: {
|
||||||
|
transport: [ WS ],
|
||||||
|
peerDiscovery: [ Bootstrap ],
|
||||||
|
dht: DHT
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
peerDiscovery: {
|
||||||
|
autoDial: true
|
||||||
|
},
|
||||||
|
EXPERIMENTAL: {
|
||||||
|
pubsub: false
|
||||||
|
},
|
||||||
|
dht: {
|
||||||
|
kBucketSize: 20,
|
||||||
|
enabled: false,
|
||||||
|
randomWalk: {
|
||||||
|
enabled: false,
|
||||||
|
queriesPerPeriod: 1,
|
||||||
|
interval: 30000,
|
||||||
|
timeout: 10000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
relay: {
|
||||||
|
enabled: true,
|
||||||
|
hop: {
|
||||||
|
active: false,
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(validateConfig(options)).to.deep.equal(expected)
|
||||||
|
})
|
||||||
|
|
||||||
it('should add defaults to missing items', () => {
|
it('should add defaults to missing items', () => {
|
||||||
const options = {
|
const options = {
|
||||||
peerInfo,
|
peerInfo,
|
||||||
@ -78,6 +128,9 @@ describe('configuration', () => {
|
|||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
peerInfo,
|
peerInfo,
|
||||||
|
connectionManager: {
|
||||||
|
minPeers: 25
|
||||||
|
},
|
||||||
modules: {
|
modules: {
|
||||||
transport: [ WS ],
|
transport: [ WS ],
|
||||||
peerDiscovery: [ Bootstrap ],
|
peerDiscovery: [ Bootstrap ],
|
||||||
@ -85,6 +138,7 @@ describe('configuration', () => {
|
|||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
peerDiscovery: {
|
peerDiscovery: {
|
||||||
|
autoDial: true,
|
||||||
bootstrap: {
|
bootstrap: {
|
||||||
interval: 1000,
|
interval: 1000,
|
||||||
enabled: true
|
enabled: true
|
||||||
@ -180,6 +234,9 @@ describe('configuration', () => {
|
|||||||
}
|
}
|
||||||
const expected = {
|
const expected = {
|
||||||
peerInfo,
|
peerInfo,
|
||||||
|
connectionManager: {
|
||||||
|
minPeers: 25
|
||||||
|
},
|
||||||
modules: {
|
modules: {
|
||||||
transport: [WS],
|
transport: [WS],
|
||||||
dht: DHT
|
dht: DHT
|
||||||
@ -188,6 +245,9 @@ describe('configuration', () => {
|
|||||||
EXPERIMENTAL: {
|
EXPERIMENTAL: {
|
||||||
pubsub: false
|
pubsub: false
|
||||||
},
|
},
|
||||||
|
peerDiscovery: {
|
||||||
|
autoDial: true
|
||||||
|
},
|
||||||
relay: {
|
relay: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
hop: {
|
hop: {
|
||||||
|
@ -68,12 +68,17 @@ describe('peer discovery', () => {
|
|||||||
(cb) => ss.stop(cb)
|
(cb) => ss.stop(cb)
|
||||||
], done)
|
], done)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sinon.restore()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('module registration', () => {
|
describe('module registration', () => {
|
||||||
it('should enable by default a module passed as an object', (done) => {
|
it('should enable by default a module passed as an object', (done) => {
|
||||||
const mockDiscovery = {
|
const mockDiscovery = {
|
||||||
on: sinon.stub(),
|
on: sinon.stub(),
|
||||||
|
removeListener: sinon.stub(),
|
||||||
start: sinon.stub().callsArg(0),
|
start: sinon.stub().callsArg(0),
|
||||||
stop: sinon.stub().callsArg(0)
|
stop: sinon.stub().callsArg(0)
|
||||||
}
|
}
|
||||||
@ -94,6 +99,7 @@ describe('peer discovery', () => {
|
|||||||
it('should enable by default a module passed as a function', (done) => {
|
it('should enable by default a module passed as a function', (done) => {
|
||||||
const mockDiscovery = {
|
const mockDiscovery = {
|
||||||
on: sinon.stub(),
|
on: sinon.stub(),
|
||||||
|
removeListener: sinon.stub(),
|
||||||
start: sinon.stub().callsArg(0),
|
start: sinon.stub().callsArg(0),
|
||||||
stop: sinon.stub().callsArg(0)
|
stop: sinon.stub().callsArg(0)
|
||||||
}
|
}
|
||||||
@ -116,6 +122,7 @@ describe('peer discovery', () => {
|
|||||||
it('should enable module by configutation', (done) => {
|
it('should enable module by configutation', (done) => {
|
||||||
const mockDiscovery = {
|
const mockDiscovery = {
|
||||||
on: sinon.stub(),
|
on: sinon.stub(),
|
||||||
|
removeListener: sinon.stub(),
|
||||||
start: sinon.stub().callsArg(0),
|
start: sinon.stub().callsArg(0),
|
||||||
stop: sinon.stub().callsArg(0),
|
stop: sinon.stub().callsArg(0),
|
||||||
tag: 'mockDiscovery'
|
tag: 'mockDiscovery'
|
||||||
@ -151,6 +158,7 @@ describe('peer discovery', () => {
|
|||||||
it('should disable module by configutation', (done) => {
|
it('should disable module by configutation', (done) => {
|
||||||
const mockDiscovery = {
|
const mockDiscovery = {
|
||||||
on: sinon.stub(),
|
on: sinon.stub(),
|
||||||
|
removeListener: sinon.stub(),
|
||||||
start: sinon.stub().callsArg(0),
|
start: sinon.stub().callsArg(0),
|
||||||
stop: sinon.stub().callsArg(0),
|
stop: sinon.stub().callsArg(0),
|
||||||
tag: 'mockDiscovery'
|
tag: 'mockDiscovery'
|
||||||
@ -186,6 +194,7 @@ describe('peer discovery', () => {
|
|||||||
it('should register module passed as function', (done) => {
|
it('should register module passed as function', (done) => {
|
||||||
const mockDiscovery = {
|
const mockDiscovery = {
|
||||||
on: sinon.stub(),
|
on: sinon.stub(),
|
||||||
|
removeListener: sinon.stub(),
|
||||||
start: sinon.stub().callsArg(0),
|
start: sinon.stub().callsArg(0),
|
||||||
stop: sinon.stub().callsArg(0)
|
stop: sinon.stub().callsArg(0)
|
||||||
}
|
}
|
||||||
@ -223,6 +232,7 @@ describe('peer discovery', () => {
|
|||||||
it('should register module passed as object', (done) => {
|
it('should register module passed as object', (done) => {
|
||||||
const mockDiscovery = {
|
const mockDiscovery = {
|
||||||
on: sinon.stub(),
|
on: sinon.stub(),
|
||||||
|
removeListener: sinon.stub(),
|
||||||
start: sinon.stub().callsArg(0),
|
start: sinon.stub().callsArg(0),
|
||||||
stop: sinon.stub().callsArg(0),
|
stop: sinon.stub().callsArg(0),
|
||||||
tag: 'mockDiscovery'
|
tag: 'mockDiscovery'
|
||||||
@ -256,9 +266,10 @@ describe('peer discovery', () => {
|
|||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
peerDiscovery: {
|
peerDiscovery: {
|
||||||
|
autoDial: true,
|
||||||
mdns: {
|
mdns: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
interval: 1e3, // discover quickly
|
interval: 200, // discover quickly
|
||||||
// use a random tag to prevent CI collision
|
// use a random tag to prevent CI collision
|
||||||
serviceTag: crypto.randomBytes(10).toString('hex')
|
serviceTag: crypto.randomBytes(10).toString('hex')
|
||||||
}
|
}
|
||||||
@ -295,6 +306,7 @@ describe('peer discovery', () => {
|
|||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
peerDiscovery: {
|
peerDiscovery: {
|
||||||
|
autoDial: true,
|
||||||
webRTCStar: {
|
webRTCStar: {
|
||||||
enabled: true
|
enabled: true
|
||||||
}
|
}
|
||||||
@ -331,9 +343,10 @@ describe('peer discovery', () => {
|
|||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
peerDiscovery: {
|
peerDiscovery: {
|
||||||
|
autoDial: true,
|
||||||
mdns: {
|
mdns: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
interval: 1e3, // discovery quickly
|
interval: 200, // discovery quickly
|
||||||
// use a random tag to prevent CI collision
|
// use a random tag to prevent CI collision
|
||||||
serviceTag: crypto.randomBytes(10).toString('hex')
|
serviceTag: crypto.randomBytes(10).toString('hex')
|
||||||
},
|
},
|
||||||
@ -369,6 +382,7 @@ describe('peer discovery', () => {
|
|||||||
setup({
|
setup({
|
||||||
config: {
|
config: {
|
||||||
peerDiscovery: {
|
peerDiscovery: {
|
||||||
|
autoDial: true,
|
||||||
mdns: {
|
mdns: {
|
||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
@ -382,7 +396,7 @@ describe('peer discovery', () => {
|
|||||||
randomWalk: {
|
randomWalk: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
queriesPerPeriod: 1,
|
queriesPerPeriod: 1,
|
||||||
interval: 1000, // start the query sooner
|
interval: 200, // start the query sooner
|
||||||
timeout: 3000
|
timeout: 3000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -419,4 +433,45 @@ describe('peer discovery', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('auto dial', () => {
|
||||||
|
setup({
|
||||||
|
connectionManager: {
|
||||||
|
minPeers: 1
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
peerDiscovery: {
|
||||||
|
autoDial: true,
|
||||||
|
mdns: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
webRTCStar: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
bootstrap: {
|
||||||
|
enabled: true,
|
||||||
|
list: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dht: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should only dial when the peer count is below the low watermark', (done) => {
|
||||||
|
const bootstrap = nodeA._discovery[0]
|
||||||
|
sinon.stub(nodeA._switch.dialer, 'connect').callsFake((peerInfo) => {
|
||||||
|
nodeA._switch.connection.connections[peerInfo.id.toB58String()] = []
|
||||||
|
})
|
||||||
|
|
||||||
|
bootstrap.emit('peer', nodeB.peerInfo)
|
||||||
|
bootstrap.emit('peer', nodeC.peerInfo)
|
||||||
|
|
||||||
|
// Only nodeB should get dialed
|
||||||
|
expect(nodeA._switch.dialer.connect.callCount).to.eql(1)
|
||||||
|
expect(nodeA._switch.dialer.connect.getCall(0).args[0]).to.eql(nodeB.peerInfo)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
const chai = require('chai')
|
const chai = require('chai')
|
||||||
chai.use(require('dirty-chai'))
|
chai.use(require('dirty-chai'))
|
||||||
|
chai.use(require('chai-checkmark'))
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
const PeerInfo = require('peer-info')
|
const PeerInfo = require('peer-info')
|
||||||
const PeerId = require('peer-id')
|
const PeerId = require('peer-id')
|
||||||
@ -413,29 +414,19 @@ describe('transports', () => {
|
|||||||
it('node1 hangUp node2', (done) => {
|
it('node1 hangUp node2', (done) => {
|
||||||
node1.hangUp(peer2, (err) => {
|
node1.hangUp(peer2, (err) => {
|
||||||
expect(err).to.not.exist()
|
expect(err).to.not.exist()
|
||||||
setTimeout(check, 500)
|
const peers = node1.peerBook.getAll()
|
||||||
|
expect(Object.keys(peers)).to.have.length(1)
|
||||||
function check () {
|
expect(node1._switch.connection.getAll()).to.have.length(0)
|
||||||
const peers = node1.peerBook.getAll()
|
done()
|
||||||
expect(Object.keys(peers)).to.have.length(1)
|
|
||||||
expect(node1._switch.connection.getAll()).to.have.length(0)
|
|
||||||
done()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('create a third node and check that discovery works', function (done) {
|
it('create a third node and check that discovery works', function (done) {
|
||||||
this.timeout(10 * 1000)
|
this.timeout(10 * 1000)
|
||||||
|
const expectedPeers = [
|
||||||
let counter = 0
|
node1.peerInfo.id.toB58String(),
|
||||||
|
node2.peerInfo.id.toB58String()
|
||||||
function check () {
|
]
|
||||||
if (++counter === 3) {
|
|
||||||
expect(node1._switch.connection.getAll()).to.have.length(1)
|
|
||||||
expect(node2._switch.connection.getAll()).to.have.length(1)
|
|
||||||
done()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PeerId.create((err, id3) => {
|
PeerId.create((err, id3) => {
|
||||||
expect(err).to.not.exist()
|
expect(err).to.not.exist()
|
||||||
@ -444,13 +435,18 @@ describe('transports', () => {
|
|||||||
const ma3 = '/ip4/127.0.0.1/tcp/14444/ws/p2p-websocket-star/p2p/' + id3.toB58String()
|
const ma3 = '/ip4/127.0.0.1/tcp/14444/ws/p2p-websocket-star/p2p/' + id3.toB58String()
|
||||||
peer3.multiaddrs.add(ma3)
|
peer3.multiaddrs.add(ma3)
|
||||||
|
|
||||||
node1.on('peer:discovery', (peerInfo) => node1.dial(peerInfo, check))
|
// 2 connects and 1 start
|
||||||
node2.on('peer:discovery', (peerInfo) => node2.dial(peerInfo, check))
|
expect(3).checks(done)
|
||||||
|
|
||||||
const node3 = new Node({
|
const node3 = new Node({
|
||||||
peerInfo: peer3
|
peerInfo: peer3
|
||||||
})
|
})
|
||||||
node3.start(check)
|
node3.on('peer:connect', (peerInfo) => {
|
||||||
|
expect(expectedPeers).to.include(peerInfo.id.toB58String()).mark()
|
||||||
|
})
|
||||||
|
node3.start((err) => {
|
||||||
|
expect(err).to.not.exist().mark()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -438,6 +438,7 @@ describe('transports', () => {
|
|||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
peerDiscovery: {
|
peerDiscovery: {
|
||||||
|
autoDial: false,
|
||||||
[wstar.discovery.tag]: {
|
[wstar.discovery.tag]: {
|
||||||
enabled: true
|
enabled: true
|
||||||
}
|
}
|
||||||
@ -452,7 +453,13 @@ describe('transports', () => {
|
|||||||
},
|
},
|
||||||
(cb) => createNode([
|
(cb) => createNode([
|
||||||
'/ip4/0.0.0.0/tcp/0'
|
'/ip4/0.0.0.0/tcp/0'
|
||||||
], (err, node) => {
|
], {
|
||||||
|
config: {
|
||||||
|
peerDiscovery: {
|
||||||
|
autoDial: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, (err, node) => {
|
||||||
expect(err).to.not.exist()
|
expect(err).to.not.exist()
|
||||||
nodeTCP = node
|
nodeTCP = node
|
||||||
node.handle('/echo/1.0.0', echo)
|
node.handle('/echo/1.0.0', echo)
|
||||||
@ -460,7 +467,13 @@ describe('transports', () => {
|
|||||||
}),
|
}),
|
||||||
(cb) => createNode([
|
(cb) => createNode([
|
||||||
'/ip4/127.0.0.1/tcp/25022/ws'
|
'/ip4/127.0.0.1/tcp/25022/ws'
|
||||||
], (err, node) => {
|
], {
|
||||||
|
config: {
|
||||||
|
peerDiscovery: {
|
||||||
|
autoDial: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, (err, node) => {
|
||||||
expect(err).to.not.exist()
|
expect(err).to.not.exist()
|
||||||
nodeWS = node
|
nodeWS = node
|
||||||
node.handle('/echo/1.0.0', echo)
|
node.handle('/echo/1.0.0', echo)
|
||||||
@ -479,6 +492,7 @@ describe('transports', () => {
|
|||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
peerDiscovery: {
|
peerDiscovery: {
|
||||||
|
autoDial: false,
|
||||||
[wstar.discovery.tag]: {
|
[wstar.discovery.tag]: {
|
||||||
enabled: true
|
enabled: true
|
||||||
}
|
}
|
||||||
|
@ -61,6 +61,7 @@ class Node extends libp2p {
|
|||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
peerDiscovery: {
|
peerDiscovery: {
|
||||||
|
autoDial: true,
|
||||||
webRTCStar: {
|
webRTCStar: {
|
||||||
enabled: true
|
enabled: true
|
||||||
},
|
},
|
||||||
|
@ -56,6 +56,7 @@ class Node extends libp2p {
|
|||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
peerDiscovery: {
|
peerDiscovery: {
|
||||||
|
autoDial: true,
|
||||||
mdns: {
|
mdns: {
|
||||||
interval: 10000,
|
interval: 10000,
|
||||||
enabled: false
|
enabled: false
|
||||||
|
Reference in New Issue
Block a user