From 699b4e519dd8a2eac7463255ddf0209f30f5c8b3 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 5 Oct 2020 15:54:26 +0200 Subject: [PATCH] feat: rendezvous integration --- README.md | 2 + doc/CONFIGURATION.md | 63 +++++++++++++++++ package-list.json | 3 + package.json | 1 + src/config.js | 5 ++ src/index.js | 13 ++++ test/discovery/rendezvous.spec.js | 111 ++++++++++++++++++++++++++++++ test/discovery/utils.js | 41 +++++++++++ test/relay/auto-relay.node.js | 17 ----- 9 files changed, 239 insertions(+), 17 deletions(-) create mode 100644 test/discovery/rendezvous.spec.js create mode 100644 test/discovery/utils.js diff --git a/README.md b/README.md index e6d8af7285..db0e803dd1 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,8 @@ List of packages currently in existence for libp2p | **peer routing** | | [`libp2p-delegated-peer-routing`](//github.com/libp2p/js-libp2p-delegated-peer-routing) | [![npm](https://img.shields.io/npm/v/libp2p-delegated-peer-routing.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-delegated-peer-routing/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-delegated-peer-routing.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-delegated-peer-routing) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-delegated-peer-routing/master)](https://travis-ci.com/libp2p/js-libp2p-delegated-peer-routing) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-delegated-peer-routing/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-delegated-peer-routing) | [Jacob Heun](mailto:jacobheun@gmail.com) | | [`libp2p-kad-dht`](//github.com/libp2p/js-libp2p-kad-dht) | [![npm](https://img.shields.io/npm/v/libp2p-kad-dht.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-kad-dht/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-kad-dht.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-kad-dht) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-kad-dht/master)](https://travis-ci.com/libp2p/js-libp2p-kad-dht) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-kad-dht/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-kad-dht) | [Vasco Santos](mailto:vasco.santos@moxy.studio) | +| **service discovery** | +| [`libp2p-rendezvous`](//github.com/libp2p/js-libp2p-rendezvous) | [![npm](https://img.shields.io/npm/v/libp2p-rendezvous.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-rendezvous/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-rendezvous.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-rendezvous) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-rendezvous/master)](https://travis-ci.com/libp2p/js-libp2p-rendezvous) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-rendezvous/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-rendezvous) | [Vasco Santos](mailto:santos.vasco10@gmail.com) | | **utilities** | | [`libp2p-crypto`](//github.com/libp2p/js-libp2p-crypto) | [![npm](https://img.shields.io/npm/v/libp2p-crypto.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-crypto/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-crypto.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-crypto) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-crypto/master)](https://travis-ci.com/libp2p/js-libp2p-crypto) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-crypto/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-crypto) | [Jacob Heun](mailto:jacobheun@gmail.com) | | [`libp2p-crypto-secp256k1`](//github.com/libp2p/js-libp2p-crypto-secp256k1) | [![npm](https://img.shields.io/npm/v/libp2p-crypto-secp256k1.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-crypto-secp256k1/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-crypto-secp256k1.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-crypto-secp256k1) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-crypto-secp256k1/master)](https://travis-ci.com/libp2p/js-libp2p-crypto-secp256k1) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-crypto-secp256k1/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-crypto-secp256k1) | [Friedel Ziegelmayer](mailto:dignifiedquire@gmail.com) | diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 15f34d4ff6..d82bf69a76 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -21,6 +21,7 @@ - [Setup with Content and Peer Routing](#setup-with-content-and-peer-routing) - [Setup with Relay](#setup-with-relay) - [Setup with Auto Relay](#setup-with-auto-relay) + - [Setup with Rendezvous](#setup-with-rendezvous) - [Setup with Keychain](#setup-with-keychain) - [Configuring Dialing](#configuring-dialing) - [Configuring Connection Manager](#configuring-connection-manager) @@ -457,6 +458,68 @@ const node = await Libp2p.create({ }) ``` +#### Setup with Rendezvous + +You will need to setup a rendezvous server, which will be used by rendezvous client nodes. + +A rendezvous server can be configured as follows: + +```js +const Libp2p = require('libp2p') +const TCP = require('libp2p-tcp') +const MPLEX = require('libp2p-mplex') +const { NOISE } = require('libp2p-noise') +const Rendezvous = require('libp2p-rendezvous') + +const node = await Libp2p.create({ + modules: { + transport: [TCP], + streamMuxer: [MPLEX], + connEncryption: [NOISE], + rendezvous: Rendezvous + }, + config: { + rendezvous: { // Rendezvous options (this config is part of libp2p core configurations) + server: { + enabled: true, // Allows you to be a rendezvous server for other peers + gcInterval: 3e5 // Interval for gc to check outdated rendezvous registrations + } + } + } +}) +``` + +A rendezvous client only needs the rendezvous module. However, it will need to discover and get connected with a rendezvous server. A good option is to leverage the bootstrap module for this. + +```js +const Libp2p = require('libp2p') +const TCP = require('libp2p-tcp') +const MPLEX = require('libp2p-mplex') +const { NOISE } = require('libp2p-noise') +const Rendezvous = require('libp2p-rendezvous') +const Bootstrap = require('libp2p-bootstrap') + +const node = await Libp2p.create({ + modules: { + transport: [TCP], + streamMuxer: [MPLEX], + connEncryption: [NOISE], + rendezvous: Rendezvous, + peerDiscovery: [Bootstrap] + }, + config: { + peerDiscovery: { + bootstrap: { + enabled: true, + list: [ + // Insert rendezvous servers multiaddrs + ] + } + } + } +}) +``` + #### Setup with Keychain Libp2p allows you to setup a secure keychain to manage your keys. The keychain configuration object should have the following properties: diff --git a/package-list.json b/package-list.json index 84c648a971..7612cc4b27 100644 --- a/package-list.json +++ b/package-list.json @@ -43,6 +43,9 @@ ["libp2p/js-libp2p-delegated-peer-routing", "libp2p-delegated-peer-routing"], ["libp2p/js-libp2p-kad-dht", "libp2p-kad-dht"], + "service discovery", + ["libp2p/js-libp2p-rendezvous", "libp2p-rendezvous"], + "utilities", ["libp2p/js-libp2p-crypto", "libp2p-crypto"], ["libp2p/js-libp2p-crypto-secp256k1", "libp2p-crypto-secp256k1"], diff --git a/package.json b/package.json index b1ae4c5ebe..92dd25c253 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "libp2p-mdns": "^0.15.0", "libp2p-mplex": "^0.10.1", "libp2p-noise": "^2.0.0", + "libp2p-rendezvous": "libp2p/js-libp2p-rendezvous#feat/rendezvous-protocol-full-implementation", "libp2p-secio": "^0.13.1", "libp2p-tcp": "^0.15.1", "libp2p-webrtc-star": "^0.20.0", diff --git a/src/config.js b/src/config.js index 2337e249ae..380218486e 100644 --- a/src/config.js +++ b/src/config.js @@ -71,6 +71,11 @@ const DefaultConfig = { maxListeners: 2 } }, + rendezvous: { + server: { + enabled: false + } + }, transport: {} } } diff --git a/src/index.js b/src/index.js index afc28fe79c..4390af6572 100644 --- a/src/index.js +++ b/src/index.js @@ -190,6 +190,15 @@ class Libp2p extends EventEmitter { this.pubsub = PubsubAdapter(Pubsub, this, this._config.pubsub) } + // Create rendezvous if provided + if (this._modules.rendezvous) { + const Rendezvous = this._modules.rendezvous + this.rendezvous = new Rendezvous({ + libp2p: this, + ...this._config.rendezvous + }) + } + // Attach remaining APIs // peer and content routing will automatically get modules from _modules and _dht this.peerRouting = peerRouting(this) @@ -269,6 +278,8 @@ class Libp2p extends EventEmitter { this.metrics && this.metrics.stop() ]) + this.rendezvous && this.rendezvous.stop() + await this.transportManager.close() ping.unmount(this) @@ -500,6 +511,8 @@ class Libp2p extends EventEmitter { this.connectionManager.start() + this.rendezvous && this.rendezvous.start() + // Peer discovery await this._setupPeerDiscovery() diff --git a/test/discovery/rendezvous.spec.js b/test/discovery/rendezvous.spec.js new file mode 100644 index 0000000000..53a8ee7fc0 --- /dev/null +++ b/test/discovery/rendezvous.spec.js @@ -0,0 +1,111 @@ +'use strict' +/* eslint-env mocha */ + +const { expect } = require('aegir/utils/chai') +const pWaitFor = require('p-wait-for') + +const Envelope = require('../../src/record/envelope') +const PeerRecord = require('../../src/record/peer-record') + +const { + rendezvousClientOptions, + rendezvousServerOptions, + listenAddrs +} = require('./utils') +const peerUtils = require('../utils/creators/peer') + +describe('libp2p.rendezvous', () => { + let libp2p, remoteLibp2p, rendezvousLibp2p + + // Create Rendezvous server node + before(async () => { + [rendezvousLibp2p] = await peerUtils.createPeer({ + number: 1, + fixture: false, + config: { + ...rendezvousServerOptions, + addresses: { + listen: listenAddrs + } + } + }) + }) + + // Create libp2p nodes to act as rendezvous clients + before(async () => { + [libp2p, remoteLibp2p] = await peerUtils.createPeer({ + number: 2, + populateAddressBooks: false, + config: { + ...rendezvousClientOptions, + addresses: { + listen: listenAddrs + }, + config: { + peerDiscovery: { + bootstrap: { // Bootstrap rendezvous server + enabled: true, + list: [ + `${rendezvousLibp2p.multiaddrs[0]}/p2p/${rendezvousLibp2p.peerId.toB58String()}` + ] + } + } + } + } + }) + }) + + // Wait for bootstrap peer connected and identified as rendezvous server + before(async () => { + await pWaitFor(() => Boolean(rendezvousLibp2p.connectionManager.get(libp2p.peerId)) && + Boolean(rendezvousLibp2p.connectionManager.get(remoteLibp2p.peerId)) + ) + + await pWaitFor(() => libp2p.rendezvous._rendezvousPoints.size === 1 && + remoteLibp2p.rendezvous._rendezvousPoints.size === 1 + ) + }) + + after(() => { + return Promise.all([libp2p, remoteLibp2p, rendezvousLibp2p].map(node => node.stop())) + }) + + it('should have rendezvous libp2p node as rendezvous server', () => { + expect(libp2p.rendezvous._rendezvousPoints.get(rendezvousLibp2p.peerId.toB58String())).to.exist() + expect(remoteLibp2p.rendezvous._rendezvousPoints.get(rendezvousLibp2p.peerId.toB58String())).to.exist() + }) + + it('should discover remoteLibp2p when it registers on a namespace', async () => { + const namespace = '/test-namespace' + const registers = [] + + // libp2p does not discovery any peer registered + for await (const reg of libp2p.rendezvous.discover(namespace)) { // eslint-disable-line + throw new Error('no registers should exist') + } + + // remoteLibp2p register itself on namespace + await remoteLibp2p.rendezvous.register(namespace) + + // libp2p discover remote libp2p + for await (const reg of libp2p.rendezvous.discover(namespace)) { // eslint-disable-line + registers.push(reg) + } + + expect(registers).to.have.lengthOf(1) + expect(registers[0].signedPeerRecord).to.exist() + expect(registers[0].ns).to.eql(namespace) + + // Validate peer + const envelope = await Envelope.openAndCertify(registers[0].signedPeerRecord, PeerRecord.DOMAIN) + expect(envelope.peerId.equals(remoteLibp2p.peerId)).to.eql(true) + + // Validate multiaddrs + const rec = PeerRecord.createFromProtobuf(envelope.payload) + expect(rec.multiaddrs.length).to.eql(remoteLibp2p.multiaddrs.length) + + rec.multiaddrs.forEach((ma, index) => { + expect(ma).to.eql(remoteLibp2p.multiaddrs[index]) + }) + }) +}) diff --git a/test/discovery/utils.js b/test/discovery/utils.js new file mode 100644 index 0000000000..37ffb3ed57 --- /dev/null +++ b/test/discovery/utils.js @@ -0,0 +1,41 @@ +'use strict' + +const Bootstrap = require('libp2p-bootstrap') +const Rendezvous = require('libp2p-rendezvous') + +const mergeOptions = require('merge-options') +const { isNode } = require('ipfs-utils/src/env') +const baseOptions = require('../utils/base-options.browser') +const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser') + +module.exports.baseOptions = baseOptions + +module.exports.listenAddrs = isNode + ? ['/ip4/127.0.0.1/tcp/0/ws'] : [`${MULTIADDRS_WEBSOCKETS[0]}/p2p-circuit`] + +module.exports.rendezvousClientOptions = mergeOptions(baseOptions, { + modules: { + rendezvous: Rendezvous, + peerDiscovery: [Bootstrap] + }, + config: { + rendezvous: { + server: { + enabled: false + } + } + } +}) + +module.exports.rendezvousServerOptions = mergeOptions(baseOptions, { + modules: { + rendezvous: Rendezvous + }, + config: { + rendezvous: { + server: { + enabled: true + } + } + } +}) diff --git a/test/relay/auto-relay.node.js b/test/relay/auto-relay.node.js index 881a8e8754..8d0cdfd27e 100644 --- a/test/relay/auto-relay.node.js +++ b/test/relay/auto-relay.node.js @@ -462,24 +462,15 @@ describe('auto-relay', () => { }) describe('discovery', () => { -<<<<<<< HEAD let local let remote -======= - let libp2p - let libp2p2 ->>>>>>> feat: auto relay network query for new relays let relayLibp2p beforeEach(async () => { const peerIds = await createPeerId({ number: 3 }) // Create 2 nodes, and turn HOP on for the relay -<<<<<<< HEAD ;[local, remote, relayLibp2p] = peerIds.map((peerId, index) => { -======= - ;[libp2p, libp2p2, relayLibp2p] = peerIds.map((peerId, index) => { ->>>>>>> feat: auto relay network query for new relays const delegate = new DelegatedContentRouter(peerId, ipfsHttpClient({ host: '0.0.0.0', protocol: 'http', @@ -495,12 +486,8 @@ describe('auto-relay', () => { relay: { advertise: { bootDelay: 1000, -<<<<<<< HEAD ttl: 1000, enabled: true -======= - ttl: 1000 ->>>>>>> feat: auto relay network query for new relays }, hop: { enabled: index === 2 @@ -546,11 +533,7 @@ describe('auto-relay', () => { ]) // Start each node -<<<<<<< HEAD await Promise.all([local, remote, relayLibp2p].map(libp2p => libp2p.start())) -======= - await Promise.all([libp2p, libp2p2, relayLibp2p].map(libp2p => libp2p.start())) ->>>>>>> feat: auto relay network query for new relays // Should provide on start await pWaitFor(() => relayLibp2p.contentRouting.provide.callCount === 1)