Compare commits

..

6 Commits

Author SHA1 Message Date
432da545b4 chore: release version v0.1.4 2019-11-14 11:54:38 +01:00
b00bcd453d chore: update contributors 2019-11-14 11:54:37 +01:00
8bee747c7c feat: add topology interfaces (#7)
* feat: topology

* feat: multicodec-topology

* chore: address review

Co-Authored-By: Jacob Heun <jacobheun@gmail.com>

* chore: remove error from disconnect

* docs: topology

* chore: apply suggestions from code review

Co-Authored-By: Jacob Heun <jacobheun@gmail.com>
2019-11-14 11:52:06 +01:00
b960f29757 chore: release version v0.1.3 2019-10-30 17:10:37 +01:00
5206862504 chore: update contributors 2019-10-30 17:10:37 +01:00
749a8d035d fix: localAddr should be optional (#6)
The local address of a connection will not always be known, such as a browser client, so it should not be required.
2019-10-30 16:56:28 +01:00
13 changed files with 516 additions and 4 deletions

View File

@ -1,3 +1,23 @@
<a name="0.1.4"></a>
## [0.1.4](https://github.com/libp2p/js-interfaces/compare/v0.1.3...v0.1.4) (2019-11-14)
### Features
* add topology interfaces ([#7](https://github.com/libp2p/js-interfaces/issues/7)) ([8bee747](https://github.com/libp2p/js-interfaces/commit/8bee747))
<a name="0.1.3"></a>
## [0.1.3](https://github.com/libp2p/js-interfaces/compare/v0.1.2...v0.1.3) (2019-10-30)
### Bug Fixes
* localAddr should be optional ([#6](https://github.com/libp2p/js-interfaces/issues/6)) ([749a8d0](https://github.com/libp2p/js-interfaces/commit/749a8d0))
<a name="0.1.2"></a> <a name="0.1.2"></a>
## [0.1.2](https://github.com/libp2p/js-interfaces/compare/v0.1.1...v0.1.2) (2019-10-29) ## [0.1.2](https://github.com/libp2p/js-interfaces/compare/v0.1.1...v0.1.2) (2019-10-29)

View File

@ -19,6 +19,7 @@
- [Peer Discovery](./src/peer-discovery) - [Peer Discovery](./src/peer-discovery)
- [Peer Routing](./src/peer-routing) - [Peer Routing](./src/peer-routing)
- [Stream Muxer](./src/stream-muxer) - [Stream Muxer](./src/stream-muxer)
- [Topology](./src/topology)
- [Transport](./src/transport) - [Transport](./src/transport)
### Origin Repositories ### Origin Repositories

View File

@ -1,6 +1,6 @@
{ {
"name": "libp2p-interfaces", "name": "libp2p-interfaces",
"version": "0.1.2", "version": "0.1.4",
"description": "Interfaces for JS Libp2p", "description": "Interfaces for JS Libp2p",
"main": "src/index.js", "main": "src/index.js",
"files": [ "files": [

View File

@ -109,7 +109,7 @@ const conn = new Connection({
Creates a new Connection instance. Creates a new Connection instance.
`localAddr` is the [multiaddr](https://github.com/multiformats/multiaddr) address used by the local peer to reach the remote. `localAddr` is the optional [multiaddr](https://github.com/multiformats/multiaddr) address used by the local peer to reach the remote.
`remoteAddr` is the [multiaddr](https://github.com/multiformats/multiaddr) address used to communicate with the remote peer. `remoteAddr` is the [multiaddr](https://github.com/multiformats/multiaddr) address used to communicate with the remote peer.
`localPeer` is the [PeerId](https://github.com/libp2p/js-peer-id) of the local peer. `localPeer` is the [PeerId](https://github.com/libp2p/js-peer-id) of the local peer.
`remotePeer` is the [PeerId](https://github.com/libp2p/js-peer-id) of the remote peer. `remotePeer` is the [PeerId](https://github.com/libp2p/js-peer-id) of the remote peer.

View File

@ -16,7 +16,7 @@ class Connection {
/** /**
* Creates an instance of Connection. * Creates an instance of Connection.
* @param {object} properties properties of the connection. * @param {object} properties properties of the connection.
* @param {multiaddr} properties.localAddr local multiaddr of the connection. * @param {multiaddr} [properties.localAddr] local multiaddr of the connection if known.
* @param {multiaddr} properties.remoteAddr remote multiaddr of the connection. * @param {multiaddr} properties.remoteAddr remote multiaddr of the connection.
* @param {PeerId} properties.localPeer local peer-id. * @param {PeerId} properties.localPeer local peer-id.
* @param {PeerId} properties.remotePeer remote peer-id. * @param {PeerId} properties.remotePeer remote peer-id.
@ -32,7 +32,7 @@ class Connection {
* @param {string} [properties.stat.encryption] connection encryption method identifier. * @param {string} [properties.stat.encryption] connection encryption method identifier.
*/ */
constructor ({ localAddr, remoteAddr, localPeer, remotePeer, newStream, close, getStreams, stat }) { constructor ({ localAddr, remoteAddr, localPeer, remotePeer, newStream, close, getStreams, stat }) {
assert(multiaddr.isMultiaddr(localAddr), 'localAddr must be an instance of multiaddr') localAddr && assert(multiaddr.isMultiaddr(localAddr), 'localAddr must be an instance of multiaddr')
assert(multiaddr.isMultiaddr(remoteAddr), 'remoteAddr must be an instance of multiaddr') assert(multiaddr.isMultiaddr(remoteAddr), 'remoteAddr must be an instance of multiaddr')
assert(PeerId.isPeerId(localPeer), 'localPeer must be an instance of peer-id') assert(PeerId.isPeerId(localPeer), 'localPeer must be an instance of peer-id')
assert(PeerId.isPeerId(remotePeer), 'remotePeer must be an instance of peer-id') assert(PeerId.isPeerId(remotePeer), 'remotePeer must be an instance of peer-id')

141
src/topology/README.md Normal file
View File

@ -0,0 +1,141 @@
interface-topology
========================
> Implementation of the topology interface used by the `js-libp2p` registrar.
Topologies can be used in conjunction with `js-libp2p` to help shape its network and the overlays of its subsystems, such as pubsub and the DHT.
## Table of Contents
- [Implementations](#implementations)
- [Install](#install)
- [Modules using the interface](#modulesUsingTheInterface)
- [Usage](#usage)
- [Api](#api)
## Implementations
### Topology
A libp2p topology with a group of common peers.
### Multicodec Topology
A libp2p topology with a group of peers that support the same protocol.
## Install
```sh
$ npm install libp2p-interfaces
```
## Modules using the interface
TBA
## Usage
### Topology
```js
const Topology = require('libp2p-interfaces/src/topology')
const toplogy = new Topology({
min: 0,
max: 50
})
```
### Multicodec Topology
```js
const MulticodecTopology = require('libp2p-interfaces/src/topology/multicodec-topology')
const toplogy = new MulticodecTopology({
min: 0,
max: 50,
multicodecs: ['/echo/1.0.0'],
handlers: {
onConnect: (peerInfo, conn) => {},
onDisconnect: (peerInfo) => {}
}
})
```
## API
The `MulticodecTopology` extends the `Topology`, which makes the `Topology` API a subset of the `MulticodecTopology` API.
### Topology
- `Topology`
- `peers<Map<string, PeerInfo>>`: A Map of peers belonging to the topology.
- `disconnect<function(PeerInfo)>`: Called when a peer has been disconnected
#### Constructor
```js
const toplogy = new Topology({
min: 0,
max: 50,
handlers: {
onConnect: (peerInfo, conn) => {},
onDisconnect: (peerInfo) => {}
}
})
```
**Parameters**
- `properties` is an `Object` containing the properties of the topology.
- `min` is a `number` with the minimum needed connections (default: 0)
- `max` is a `number` with the maximum needed connections (default: Infinity)
- `handlers` is an optional `Object` containing the handler called when a peer is connected or disconnected.
- `onConnect` is a `function` called everytime a peer is connected in the topology context.
- `onDisconnect` is a `function` called everytime a peer is disconnected in the topology context.
#### Set a peer
- `topology.peers.set(id, peerInfo)`
Add a peer to the topology.
**Parameters**
- `id` is the `string` that identifies the peer to add.
- `peerInfo` is the [PeerInfo][peer-info] of the peer to add.
#### Notify about a peer disconnected event
- `topology.disconnect(peerInfo)`
**Parameters**
- `peerInfo` is the [PeerInfo][peer-info] of the peer disconnected.
### Multicodec Topology
- `MulticodecTopology`
- `registrar<Registrar>`: The `Registrar` of the topology. This is set by the `Registrar` during registration.
- `peers<Map<string, PeerInfo>>`: The Map of peers that belong to the topology
- `disconnect<function(PeerInfo)>`: Disconnects a peer from the topology.
#### Constructor
```js
const toplogy = new MulticodecTopology({
min: 0,
max: 50,
multicodecs: ['/echo/1.0.0'],
handlers: {
onConnect: (peerInfo, conn) => {},
onDisconnect: (peerInfo) => {}
}
})
```
**Parameters**
- `properties` is an `Object` containing the properties of the topology.
- `min` is a `number` with the minimum needed connections (default: 0)
- `max` is a `number` with the maximum needed connections (default: Infinity)
- `multicodecs` is a `Array<String>` with the multicodecs associated with the topology.
- `handlers` is an optional `Object` containing the handler called when a peer is connected or disconnected.
- `onConnect` is a `function` called everytime a peer is connected in the topology context.
- `onDisconnect` is a `function` called everytime a peer is disconnected in the topology context.

44
src/topology/index.js Normal file
View File

@ -0,0 +1,44 @@
'use strict'
const noop = () => {}
class Topology {
/**
* @param {Object} props
* @param {number} props.min minimum needed connections (default: 0)
* @param {number} props.max maximum needed connections (default: Infinity)
* @param {Object} [props.handlers]
* @param {function} [props.handlers.onConnect] protocol "onConnect" handler
* @param {function} [props.handlers.onDisconnect] protocol "onDisconnect" handler
* @constructor
*/
constructor ({
min = 0,
max = Infinity,
handlers = {}
}) {
this.min = min
this.max = max
// Handlers
this._onConnect = handlers.onConnect || noop
this._onDisconnect = handlers.onDisconnect || noop
this.peers = new Map()
}
set registrar (registrar) {
this._registrar = registrar
}
/**
* Notify about peer disconnected event.
* @param {PeerInfo} peerInfo
* @returns {void}
*/
disconnect (peerInfo) {
this._onDisconnect(peerInfo)
}
}
module.exports = Topology

View File

@ -0,0 +1,93 @@
'use strict'
const assert = require('assert')
const Topology = require('./index')
class MulticodecTopology extends Topology {
/**
* @param {Object} props
* @param {number} props.min minimum needed connections (default: 0)
* @param {number} props.max maximum needed connections (default: Infinity)
* @param {Array<string>} props.multicodecs protocol multicodecs
* @param {Object} props.handlers
* @param {function} props.handlers.onConnect protocol "onConnect" handler
* @param {function} props.handlers.onDisconnect protocol "onDisconnect" handler
* @constructor
*/
constructor ({
min,
max,
multicodecs,
handlers
}) {
super({ min, max, handlers })
assert(multicodecs, 'one or more multicodec should be provided')
assert(handlers, 'the handlers should be provided')
assert(handlers.onConnect && typeof handlers.onConnect === 'function',
'the \'onConnect\' handler must be provided')
assert(handlers.onDisconnect && typeof handlers.onDisconnect === 'function',
'the \'onDisconnect\' handler must be provided')
this.multicodecs = Array.isArray(multicodecs) ? multicodecs : [multicodecs]
this._registrar = undefined
this._onProtocolChange = this._onProtocolChange.bind(this)
}
set registrar (registrar) {
this._registrar = registrar
this._registrar.peerStore.on('change:protocols', this._onProtocolChange)
// Update topology peers
this._updatePeers(this._registrar.peerStore.peers.values())
}
/**
* Update topology.
* @param {Array<PeerInfo>} peerInfoIterable
* @returns {void}
*/
_updatePeers (peerInfoIterable) {
for (const peerInfo of peerInfoIterable) {
if (this.multicodecs.filter(multicodec => peerInfo.protocols.has(multicodec))) {
// Add the peer regardless of whether or not there is currently a connection
this.peers.set(peerInfo.id.toB58String(), peerInfo)
// If there is a connection, call _onConnect
const connection = this._registrar.getConnection(peerInfo)
connection && this._onConnect(peerInfo, connection)
} else {
// Remove any peers we might be tracking that are no longer of value to us
this.peers.delete(peerInfo.id.toB58String())
}
}
}
/**
* Check if a new peer support the multicodecs for this topology.
* @param {Object} props
* @param {PeerInfo} props.peerInfo
* @param {Array<string>} props.protocols
*/
_onProtocolChange ({ peerInfo, protocols }) {
const existingPeer = this.peers.get(peerInfo.id.toB58String())
const hasProtocol = protocols.filter(protocol => this.multicodecs.includes(protocol))
// Not supporting the protocol anymore?
if (existingPeer && hasProtocol.length === 0) {
this._onDisconnect({
peerInfo
})
}
// New to protocol support
for (const protocol of protocols) {
if (this.multicodecs.includes(protocol)) {
this._updatePeers([peerInfo])
return
}
}
}
}
module.exports = MulticodecTopology

View File

@ -0,0 +1,89 @@
/* eslint-env mocha */
'use strict'
const chai = require('chai')
const expect = chai.expect
chai.use(require('dirty-chai'))
const sinon = require('sinon')
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const peers = require('../../utils/peers')
module.exports = (test) => {
describe('multicodec topology', () => {
let topology, peer
beforeEach(async () => {
topology = await test.setup()
if (!topology) throw new Error('missing multicodec topology')
const id = await PeerId.createFromJSON(peers[0])
peer = await PeerInfo.create(id)
})
afterEach(async () => {
sinon.restore()
await test.teardown()
})
it('should have properties set', () => {
expect(topology.multicodecs).to.exist()
expect(topology._onConnect).to.exist()
expect(topology._onDisconnect).to.exist()
expect(topology.peers).to.exist()
expect(topology._registrar).to.exist()
})
it('should trigger "onDisconnect" on peer disconnected', () => {
sinon.spy(topology, '_onDisconnect')
topology.disconnect(peer)
expect(topology._onDisconnect.callCount).to.equal(1)
})
it('should update peers on protocol change', async () => {
sinon.spy(topology, '_updatePeers')
expect(topology.peers.size).to.eql(0)
const id2 = await PeerId.createFromJSON(peers[1])
const peer2 = await PeerInfo.create(id2)
const peerStore = topology._registrar.peerStore
peerStore.emit('change:protocols', {
peerInfo: peer2,
protocols: Array.from(topology.multicodecs)
})
expect(topology._updatePeers.callCount).to.equal(1)
expect(topology.peers.size).to.eql(1)
})
it('should disconnect if peer no longer supports a protocol', async () => {
sinon.spy(topology, '_onDisconnect')
expect(topology.peers.size).to.eql(0)
const id2 = await PeerId.createFromJSON(peers[1])
const peer2 = await PeerInfo.create(id2)
const peerStore = topology._registrar.peerStore
// Peer with the protocol
peerStore.emit('change:protocols', {
peerInfo: peer2,
protocols: Array.from(topology.multicodecs)
})
expect(topology.peers.size).to.eql(1)
// Peer does not support the protocol anymore
peerStore.emit('change:protocols', {
peerInfo: peer2,
protocols: []
})
expect(topology.peers.size).to.eql(1)
expect(topology._onDisconnect.callCount).to.equal(1)
})
})
}

View File

@ -0,0 +1,46 @@
/* eslint-env mocha */
'use strict'
const chai = require('chai')
const expect = chai.expect
chai.use(require('dirty-chai'))
const sinon = require('sinon')
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const peers = require('../../utils/peers')
module.exports = (test) => {
describe('topology', () => {
let topology, peer
beforeEach(async () => {
topology = await test.setup()
if (!topology) throw new Error('missing multicodec topology')
const id = await PeerId.createFromJSON(peers[0])
peer = await PeerInfo.create(id)
})
afterEach(async () => {
sinon.restore()
await test.teardown()
})
it('should have properties set', () => {
expect(topology.min).to.exist()
expect(topology.max).to.exist()
expect(topology._onConnect).to.exist()
expect(topology._onDisconnect).to.exist()
expect(topology.peers).to.exist()
})
it('should trigger "onDisconnect" on peer disconnected', () => {
sinon.spy(topology, '_onDisconnect')
topology.disconnect(peer)
expect(topology._onDisconnect.callCount).to.equal(1)
})
})
}

View File

@ -0,0 +1,12 @@
'use strict'
const { EventEmitter } = require('events')
class MockPeerStore extends EventEmitter {
constructor (peers) {
super()
this.peers = peers
}
}
module.exports = MockPeerStore

View File

@ -0,0 +1,40 @@
/* eslint-env mocha */
'use strict'
const tests = require('../../src/topology/tests/multicodec-topology')
const MulticodecTopology = require('../../src/topology/multicodec-topology')
const MockPeerStore = require('./mock-peer-store')
describe('multicodec topology compliance tests', () => {
tests({
setup (properties, registrar) {
const multicodecs = ['/echo/1.0.0']
const handlers = {
onConnect: () => { },
onDisconnect: () => { }
}
const topology = new MulticodecTopology({
multicodecs,
handlers,
...properties
})
if (!registrar) {
const peerStore = new MockPeerStore([])
registrar = {
peerStore,
getConnection: () => { }
}
}
topology.registrar = registrar
return topology
},
teardown () {
// cleanup resources created by setup()
}
})
})

View File

@ -0,0 +1,26 @@
/* eslint-env mocha */
'use strict'
const tests = require('../../src/topology/tests/topology')
const Topology = require('../../src/topology')
describe('topology compliance tests', () => {
tests({
setup (properties) {
const handlers = {
onConnect: () => { },
onDisconnect: () => { }
}
const topology = new Topology({
handlers,
...properties
})
return topology
},
teardown () {
// cleanup resources created by setup()
}
})
})