-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: initial implementation * chore: remove bundlesize for now * test: add query test and fix related bugs * fix: add discovery tag * chore: add logging * fix: add support for removeListener * test: improve coverage * chore: apply suggestions from code review Co-Authored-By: Vasco Santos <vasco.santos@ua.pt> * chore: add lead maintainer to package.json Co-authored-by: Vasco Santos <vasco.santos@ua.pt>
- Loading branch information
1 parent
d7111aa
commit d6f7a31
Showing
7 changed files
with
465 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
language: node_js | ||
cache: npm | ||
stages: | ||
- check | ||
- test | ||
- cov | ||
|
||
node_js: | ||
- '10' | ||
- '12' | ||
|
||
os: | ||
- linux | ||
- osx | ||
- windows | ||
|
||
script: npx nyc -s npm run test:node -- --bail | ||
after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov | ||
|
||
jobs: | ||
include: | ||
- stage: check | ||
script: | ||
- npx aegir dep-check | ||
- npm run lint | ||
|
||
- stage: test | ||
name: chrome | ||
addons: | ||
chrome: stable | ||
script: | ||
- npx aegir test -t browser -t webworker | ||
|
||
- stage: test | ||
name: firefox | ||
addons: | ||
firefox: latest | ||
script: | ||
- npx aegir test -t browser -t webworker -- --browsers FirefoxHeadless | ||
|
||
notifications: | ||
email: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# js-libp2p Pubsub Peer Discovery | ||
|
||
[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://protocol.ai) | ||
[![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) | ||
[![](https://img.shields.io/badge/freenode-%23libp2p-yellow.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23libp2p) | ||
[![](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg)](https://discuss.libp2p.io) | ||
|
||
> A js-libp2p module that uses pubsub for mdns like peer discovery | ||
## Lead Maintainer | ||
|
||
[Jacob Heun](https://github.com/jacobheun). | ||
|
||
## Design | ||
|
||
This module takes a similar approach to [MulticastDNS (MDNS)](https://github.com/libp2p/specs/blob/master/discovery/mdns.md) queries, except it leverages pubsub to "query" peers on the pubsub topic. A `Query` is performed by publishing a unique Query ID, along with your peers information (Peer ID, PublicKey, Multiaddrs). Each peer that receives the Query will submit a `QueryResponse`, consisting of their peer information and the Query ID being responded to. | ||
|
||
### Flow | ||
- When the discovery module is started by libp2p it subscribes to the discovery pubsub topic | ||
- Once subscribed, the peer will "query" the network by publishing a `Query` | ||
- The `Query` will also include your peers `QueryResponse`, so that other nodes can learn about you without needing to poll | ||
- Whenever another pubsub discovery peer joins the pubsub mesh, it will post its `Query` | ||
|
||
### Security Considerations | ||
It is worth noting that this module does not include any message signing for queries. The reason for this is that libp2p-pubsub supports message signing and enables it by default, which means the message you received has been verified to be from the originator, so we can trust that the peer information we have received is indeed from the peer who owns it. This doesn't mean the peer can't falsify its own records, but this module isn't currently concerned with that scenario. | ||
|
||
## Usage | ||
|
||
### Requirements | ||
|
||
This module *MUST* be used on a libp2p node that is running [Pubsub](https://github.com/libp2p/js-libp2p-pubsub). If Pubsub does not exist, or is not running, this module will not work. | ||
|
||
## Contribute | ||
|
||
Feel free to join in. All welcome. Open an [issue](https://github.com/libp2p/js-libp2p-pubsub-peer-discovery/issues)! | ||
|
||
This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). | ||
|
||
[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/contributing.md) | ||
|
||
## License | ||
|
||
MIT - Protocol Labs 2020 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
{ | ||
"name": "libp2p-pubsub-peer-discovery", | ||
"version": "0.0.0", | ||
"description": "A libp2p module that uses pubsub for mdns like peer discovery", | ||
"leadMaintainer": "Jacob Heun <jacobheun@gmail.com>", | ||
"main": "src/index.js", | ||
"files": [ | ||
"dist", | ||
"src" | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/libp2p/js-libp2p-pubsub-peer-discovery.git" | ||
}, | ||
"keywords": [ | ||
"libp2p", | ||
"peer", | ||
"discovery", | ||
"pubsub" | ||
], | ||
"bugs": { | ||
"url": "https://github.com/libp2p/js-libp2p-pubsub-peer-discovery/issues" | ||
}, | ||
"homepage": "https://libp2p.io", | ||
"license": "MIT", | ||
"engines": { | ||
"node": ">=10.0.0", | ||
"npm": ">=6.0.0" | ||
}, | ||
"scripts": { | ||
"lint": "aegir lint", | ||
"build": "aegir build", | ||
"test": "aegir test", | ||
"test:node": "aegir test -t node", | ||
"test:browser": "aegir test -t browser", | ||
"release": "aegir release", | ||
"release-minor": "aegir release --type minor", | ||
"release-major": "aegir release --type major", | ||
"coverage": "nyc --reporter=text --reporter=lcov npm test" | ||
}, | ||
"devDependencies": { | ||
"aegir": "^21.4.5", | ||
"chai": "^4.2.0", | ||
"dirty-chai": "^2.0.1", | ||
"libp2p-interfaces": "^0.2.7", | ||
"p-defer": "^3.0.0", | ||
"sinon": "^9.0.1" | ||
}, | ||
"dependencies": { | ||
"debug": "^4.1.1", | ||
"emittery": "^0.6.0", | ||
"libp2p-crypto": "^0.17.5", | ||
"multiaddr": "^7.4.3", | ||
"peer-id": "^0.13.11", | ||
"peer-info": "^0.17.5", | ||
"protons": "^1.0.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
'use strict' | ||
|
||
const Emittery = require('emittery') | ||
const debug = require('debug') | ||
|
||
const PeerId = require('peer-id') | ||
const PeerInfo = require('peer-info') | ||
const randomBytes = require('libp2p-crypto/src/random-bytes') | ||
const multiaddr = require('multiaddr') | ||
|
||
const PB = require('./query') | ||
|
||
const log = debug('libp2p:discovery:pubsub') | ||
log.error = debug('libp2p:discovery:pubsub:error') | ||
const TOPIC = '_peer-discovery._p2p._pubsub' | ||
|
||
/** | ||
* @typedef Message | ||
* @property {string} from | ||
* @property {Buffer} data | ||
* @property {Buffer} seqno | ||
* @property {Array<string>} topicIDs | ||
* @property {Buffer} signature | ||
* @property {key} Buffer | ||
*/ | ||
|
||
/** | ||
* A Peer Discovery Service that leverages libp2p Pubsub to find peers. | ||
*/ | ||
class PubsubPeerDiscovery extends Emittery { | ||
/** | ||
* | ||
* @param {Libp2p} param0.libp2p Our libp2p node | ||
* @param {Number} [param0.delay] How long to wait (ms) after startup before publishing our Query. Default: 1000ms | ||
*/ | ||
constructor ({ libp2p, delay = 1000 }) { | ||
super() | ||
this.libp2p = libp2p | ||
this.delay = delay | ||
this._timeout = null | ||
this.removeListener = this.off.bind(this) | ||
} | ||
|
||
/** | ||
* Subscribes to the discovery topic on `libp2p.pubsub` and peforms a query | ||
* after `this.delay` milliseconds | ||
*/ | ||
start () { | ||
if (this._timeout) return | ||
|
||
// Subscribe to pubsub | ||
this.libp2p.pubsub.subscribe(TOPIC, (msg) => this._onMessage(msg)) | ||
// Perform a delayed publish to give pubsub time to do its thing | ||
this._timeout = setTimeout(() => { | ||
this._query() | ||
}, this.delay) | ||
} | ||
|
||
/** | ||
* Unsubscribes from the discovery topic | ||
*/ | ||
stop () { | ||
clearTimeout(this._timeout) | ||
this._timeout = null | ||
this.libp2p.pubsub.unsubscribe(TOPIC) | ||
} | ||
|
||
/** | ||
* Performs a Query via Pubsub publish | ||
* @private | ||
*/ | ||
_query () { | ||
const id = randomBytes(32) | ||
const query = { | ||
id, | ||
queryResponse: { | ||
queryID: id, | ||
publicKey: this.libp2p.peerInfo.id.pubKey.bytes, | ||
addrs: this.libp2p.peerInfo.multiaddrs.toArray().map(ma => ma.buffer) | ||
} | ||
} | ||
const encodedQuery = PB.Query.encode(query) | ||
this.libp2p.pubsub.publish(TOPIC, encodedQuery) | ||
} | ||
|
||
/** | ||
* Handles incoming pubsub messages for our discovery topic | ||
* @private | ||
* @async | ||
* @param {Message} message | ||
*/ | ||
async _onMessage (message) { | ||
if (await this._handleQuery(message.data)) return | ||
|
||
this._handleQueryResponse(message.data) | ||
} | ||
|
||
/** | ||
* Attempts to decode a QueryResponse from the given data. Any errors will be logged and ignored. | ||
* @private | ||
* @async | ||
* @param {Buffer} data The Pubsub message data to decode | ||
*/ | ||
async _handleQuery (data) { | ||
try { | ||
const query = PB.Query.decode(data) | ||
const peerId = await PeerId.createFromPubKey(query.queryResponse.publicKey) | ||
// Ignore if we received our own response | ||
if (peerId.equals(this.libp2p.peerInfo.id)) return | ||
const peerInfo = new PeerInfo(peerId) | ||
query.queryResponse.addrs.forEach(buffer => peerInfo.multiaddrs.add(multiaddr(buffer))) | ||
this.emit('peer', peerInfo) | ||
log('discovered peer', peerInfo.id) | ||
return true | ||
} catch (err) { | ||
log.error(err) | ||
return false | ||
} | ||
} | ||
|
||
/** | ||
* Attempts to decode a QueryResponse from the given data. Any errors will be logged and ignored. | ||
* @private | ||
* @async | ||
* @param {Buffer} data The Pubsub message data to decode | ||
*/ | ||
async _handleQueryResponse (data) { | ||
try { | ||
const queryResponse = PB.QueryResponse.decode(data) | ||
const peerId = await PeerId.createFromPubKey(queryResponse.publicKey) | ||
const peerInfo = new PeerInfo(peerId) | ||
queryResponse.addrs.forEach(buffer => peerInfo.multiaddrs.add(multiaddr(buffer))) | ||
this.emit('peer', peerInfo) | ||
log('discovered peer', peerInfo.id) | ||
} catch (err) { | ||
log.error(err) | ||
} | ||
} | ||
} | ||
|
||
module.exports = PubsubPeerDiscovery | ||
module.exports.TOPIC = TOPIC | ||
module.exports.tag = 'PubsubPeerDiscovery' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
'use strict' | ||
|
||
const protons = require('protons') | ||
const schema = ` | ||
message Query { | ||
// id is 32 random bytes | ||
required bytes id = 0; | ||
required QueryResponse queryResponse = 1; | ||
} | ||
message QueryResponse { | ||
required bytes queryID = 0; | ||
required bytes publicKey = 1; | ||
repeated bytes addrs = 2; | ||
} | ||
` | ||
|
||
module.exports = protons(schema) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/* eslint-env mocha */ | ||
'use strict' | ||
|
||
const tests = require('libp2p-interfaces/src/peer-discovery/tests') | ||
const PubsubPeerDiscovery = require('../src') | ||
|
||
const PeerID = require('peer-id') | ||
const PeerInfo = require('peer-info') | ||
|
||
describe('compliance tests', () => { | ||
tests({ | ||
async setup () { | ||
const peerId = await PeerID.create({ bits: 512 }) | ||
const peerInfo = new PeerInfo(peerId) | ||
await new Promise(resolve => setTimeout(resolve, 10)) | ||
return new PubsubPeerDiscovery({ | ||
libp2p: { | ||
peerInfo, | ||
pubsub: { | ||
subscribe: () => {}, | ||
unsubscribe: () => {}, | ||
publish: () => {} | ||
} | ||
} | ||
}) | ||
}, | ||
async teardown () { | ||
await new Promise(resolve => setTimeout(resolve, 10)) | ||
} | ||
}) | ||
}) |
Oops, something went wrong.