Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
b8bf044
feat: rendezvous protocol full implementation
vasco-santos May 20, 2020
d7290df
chore: interface-peer-discovery compliance
vasco-santos Jul 10, 2020
b080670
feat: garbage collector
vasco-santos Jul 13, 2020
ebb22d1
feat: cookie for discovery
vasco-santos Jul 13, 2020
9765a95
chore: cleanup
vasco-santos Jul 15, 2020
b6edaf3
chore: update aegir
vasco-santos Jul 17, 2020
0e304f9
chore: convert to seconds in the wire
vasco-santos Jul 17, 2020
b248924
chore: remove unregister comments for response
vasco-santos Jul 20, 2020
47641f7
feat: use signed peer records to exchange multiaddrs
vasco-santos Jul 22, 2020
b668c8a
chore: tests
vasco-santos Jul 22, 2020
7e3c541
chore: change readme
vasco-santos Jul 27, 2020
1a1590d
chore: update deps
vasco-santos Sep 22, 2020
b357829
chore: remove peer discovery interface as we will be creating libp2p.…
vasco-santos Sep 22, 2020
4abd363
chore: use uint8array instead of buffer
vasco-santos Sep 22, 2020
7763df2
chore: update libp2p integration doc
vasco-santos Sep 28, 2020
63d607b
chore: fix register ttl param return
vasco-santos Sep 28, 2020
5f45c6f
chore: update docs
vasco-santos Sep 29, 2020
640b64f
chore: remove enabled property from libp2p integration doc
vasco-santos Oct 5, 2020
8f6e148
chore: apply suggestions from code review
vasco-santos Nov 16, 2020
894ad2e
chore: separate server and client rendezvous
vasco-santos Nov 17, 2020
ee10d69
chore: add docker
vasco-santos Nov 17, 2020
3798fbb
fix: changed default values and moved them into the server with prope…
vasco-santos Nov 17, 2020
cdf2f6b
chore: update docs and constants
vasco-santos Nov 17, 2020
9fe0691
chore: add tests for protocol with direct connection to server
vasco-santos Nov 18, 2020
52fa2bd
chore: DoS protection with max registrations
vasco-santos Nov 19, 2020
06d53ac
chore: refactor client
vasco-santos Nov 21, 2020
d501d97
chore: add datastore and types
vasco-santos Dec 8, 2020
a2d5f83
chore: fix build
vasco-santos Dec 13, 2020
83cd4b7
chore: run with mysql
vasco-santos Dec 21, 2020
9b294f5
feat: gc
vasco-santos Dec 24, 2020
9bf5bcb
chore: add gc tests
vasco-santos Dec 24, 2020
7a569c7
chore: review docs and binary
vasco-santos Dec 24, 2020
c297156
chore: add datastore docs and model picture
vasco-santos Dec 28, 2020
e1cd224
chore: add library docs
vasco-santos Dec 28, 2020
264ce2a
chore: add docker setup docks
vasco-santos Dec 28, 2020
01ec7bc
chore: remove client code and move server into src
vasco-santos Jan 4, 2021
5f025d3
chore: use connection pool
vasco-santos Jan 11, 2021
2840251
fix: bin stdout addresses and ports correctly
vasco-santos Jan 15, 2021
9da390f
chore: add benchmarks
vasco-santos Jan 11, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
chore: review docs and binary
  • Loading branch information
vasco-santos committed Dec 24, 2020
commit 7a569c7c55b6eac5ccd926fffe30a162a43bafb4
13 changes: 7 additions & 6 deletions .aegir.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ const WebSockets = require('libp2p-websockets')
const Muxer = require('libp2p-mplex')
const { NOISE: Crypto } = require('libp2p-noise')

const { isNode } = require('ipfs-utils/src/env')
const delay = require('delay')
const execa = require('execa')
const pWaitFor = require('p-wait-for')
Expand Down Expand Up @@ -44,8 +43,10 @@ const before = async () => {

await libp2p.start()

// CI runs datastore service
if (isCI || !isNode) {
// TODO: if not running test suite in Node, can also stop here
// https://github.com/ipfs/aegir/issues/707
// CI runs own datastore service
if (isCI) {
return
}

Expand All @@ -64,14 +65,14 @@ const before = async () => {
}, {
interval: 5000
})
// Some more time waiting
// Some more time waiting to guarantee the container is really ready
await delay(12e3)
}

const after = async () => {
await libp2p.stop()

if (isCI || !isNode) {
if (isCI) {
return
}

Expand All @@ -80,7 +81,7 @@ const after = async () => {
}

module.exports = {
bundlesize: { maxSize: '100kB' },
bundlesize: { maxSize: '80kB' },
hooks: {
pre: before,
post: after
Expand Down
14 changes: 4 additions & 10 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM node:lts-buster
FROM node:lts-alpine

# Install deps
RUN apt-get update && apt-get install -y
RUN apk add --update git build-base python3

# Get dumb-init to allow quit running interactively
RUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64 && chmod +x /usr/local/bin/dumb-init
Expand All @@ -21,14 +21,8 @@ RUN npm install --production
COPY --chown=node:node ./src ./src
COPY --chown=node:node ./README.md ./

# rendezvous defaults to 15002
EXPOSE 15002

# metrics defaults to 8003
EXPOSE 8003
ENV DEBUG libp2p*

# Available overrides (defaults shown):
# --disableMetrics=false
# Server logging can be enabled via the DEBUG environment variable:
# DEBUG=libp2p:rendezvous:*
# Server logging can be enabled via the DEBUG environment variable
CMD [ "/usr/local/bin/dumb-init", "node", "src/server/bin.js"]
20 changes: 14 additions & 6 deletions LIBP2P.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ const Libp2p = require('libp2p')

const node = await Libp2p.create({
rendezvous: {
enabled: true
enabled: true,
rendezvousPoints: ['/dnsaddr/rendezvous.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJP']
}
})
```

## Libp2p Flow

When a libp2p node with the rendezvous protocol enabled starts, it should start by connecting to a rendezvous server. The rendezvous server can be added to the bootstrap nodes or manually dialed. When a rendezvous server is connected, the node can ask for nodes in given namespaces. An example of a namespace could be a relay namespace, so that undialable nodes can register themselves as reachable through that relay.
When a libp2p node with the rendezvous protocol enabled starts, it should start by connecting to the given rendezvous servers. When a rendezvous server is connected, the node can ask for nodes in given namespaces. An example of a namespace could be a relay namespace, so that undialable nodes can register themselves as reachable through that relay.

When a libp2p node running the rendezvous protocol is stopping, it will unregister from all the namespaces previously registered.

Expand All @@ -34,16 +35,17 @@ This API allows users to register new rendezvous namespaces, unregister from pre
|------|------|-------------|
| options | `object` | rendezvous parameters |
| options.enabled | `boolean` | is rendezvous enabled |
| options.rendezvousPoints | `Multiaddr[]` | list of multiaddrs of running rendezvous servers |

### rendezvous.start

Register the rendezvous protocol topology into libp2p.
Start the rendezvous client in the libp2p node.

`rendezvous.start()`

### rendezvous.stop

Unregister the rendezvous protocol and the streams with other peers will be closed.
Clear the rendezvous state and unregister from namespaces.

`rendezvous.stop()`

Expand Down Expand Up @@ -117,7 +119,7 @@ Discovers peers registered under a given namespace.

| Type | Description |
|------|-------------|
| `AsyncIterable<{ signedPeerRecord: Envelope, ns: string, ttl: number }>` | Async Iterable registrations |
| `AsyncIterable<{ signedPeerRecord: Uint8Array, ns: string, ttl: number }>` | Async Iterable registrations |

#### Example

Expand All @@ -128,4 +130,10 @@ await rendezvous.register(namespace)
for await (const reg of rendezvous.discover(namespace)) {
console.log(reg.signedPeerRecord, reg.ns, reg.ttl)
}
```
```

## Future Work

- Libp2p can handle re-registers when properly configured
- Rendezvous client should be able to register namespaces given in configuration on startup
- Not supported at the moment, as we would need to deal with re-register over time
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
- [Overview](#overview)
- [Usage](#usage)
- [Install](#install)
- [Testing](#testing)
- [CLI](#cli)
- [Docker Setup](#docker-setup)
- [Garbage Collector](#garbage-collector)
- [Contribute](#contribute)
- [License](#license)

Expand All @@ -37,7 +39,7 @@ See the [SPEC](https://github.com/libp2p/specs/tree/master/rendezvous) for more
> npm install --global libp2p-rendezvous
```

Now you can use the cli command `libp2p-rendezvous-server` to spawn a libp2p rendezvous server. Bear in mind that a MySQL database is required to run the rendezvous server.
Now you can use the cli command `libp2p-rendezvous-server` to spawn a libp2p rendezvous server. Bear in mind that a MySQL database is required to run the rendezvous server. You can also use this module as a library and implement your own datastore to use a different database. A datastore `interface` is provided in this repository.

### Testing

Expand Down Expand Up @@ -67,7 +69,7 @@ Once a MySQL database is running, you can run the rendezvous server by providing
libp2p-rendezvous-server --datastoreHost 'localhost' --datastoreUser 'root' --datastorePassword 'your-secret-pw' --datastoreDatabase 'libp2p_rendezvous_db'
```

⚠️ For testing purposes you can skip using MySQL and use a memory datastore. This must not be used in production! For this you just need to provide the `--enableMemoryDatabase` option.
⚠️ For testing purposes you can skip using MySQL and use a memory datastore. **This must not be used in production!**. For this you just need to provide the `--enableMemoryDatabase` option.

#### PeerId

Expand Down Expand Up @@ -106,6 +108,8 @@ libp2p-rendezvous-server --disableMetrics

### Docker Setup

TODO: Finish docker setup

```yml
version: '3.1'
services:
Expand All @@ -124,11 +128,18 @@ volumes:
mysql-db:
```

## Library
### Library

TODO: How to use this module as a library
- Datastores

## Garbage Collector

The rendezvous server has a built in garbage collector (GC) that removes persisted data over time, as it is expired.

TODO
The GC job has two different triggers. It will run over time according to the configurable `gcBootDelay` and `gcInterval` options, and it will run if it reaches a configurable `gcMaxRegistrations` threshold.

Datastores
Taking into account the GC performance, two other factors are considered before the GC interacts with the Datastore. If a configurable number of minimum registrations `gcMinRegistrations` are not stored, the GC job will not act in this GC cycle. Moreover, to avoid multiple attempts of GC when the max threshold is reached, but no records are yet expired, a minimum interval between each job can also be configured with `gcMinInterval`.

## Contribute

Expand Down
4 changes: 0 additions & 4 deletions mysql/docker-compose.yml → mysql-test/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ services:
MYSQL_USER: libp2p
MYSQL_PASSWORD: my-secret-pw
MYSQL_DATABASE: libp2p_rendezvous_db
# MYSQL_DATABASE: ${DATABASE}
# MYSQL_ROOT_PASSWORD: ${ROOT_PASSWORD}
# MYSQL_USER: ${USER}
# MYSQL_PASSWORD: ${PASSWORD}
ports:
- "3306:3306"
volumes:
Expand Down
6 changes: 0 additions & 6 deletions mysql/.env

This file was deleted.

11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
{
"name": "libp2p-rendezvous",
"version": "0.0.0",
"description": "Javascript implementation of the rendezvous protocol for libp2p",
"description": "Javascript implementation of the rendezvous protocol server for libp2p",
"leadMaintainer": "Vasco Santos <[email protected]>",
"main": "src/index.js",
"types": "dist/src/index.d.ts",
"typesVersions": {
"*": {
"src/*": [
"dist/src/*",
"dist/src/*/index"
]
}
},
"bin": {
"libp2p-rendezvous-server": "src/server/bin.js"
},
Expand Down
28 changes: 0 additions & 28 deletions sql

This file was deleted.

11 changes: 8 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class Rendezvous {
}

/**
* Register the rendezvous protocol in the libp2p node.
* Start the rendezvous client in the libp2p node.
*
* @returns {void}
*/
Expand All @@ -73,7 +73,7 @@ class Rendezvous {
}

/**
* Clear the rendezvous state and remove listeners.
* Clear the rendezvous state and unregister from namespaces.
*
* @returns {void}
*/
Expand All @@ -85,6 +85,8 @@ class Rendezvous {
this._isStarted = false
this._cookies.clear()
log('stopped')

// TODO: should unregister from the namespaces registered
}

/**
Expand Down Expand Up @@ -156,7 +158,7 @@ class Rendezvous {
}

// Return first ttl
// TODO: consider pAny
// TODO: consider pAny instead of Promise.all?
const [returnTtl] = await Promise.all(registerTasks)

return returnTtl
Expand Down Expand Up @@ -225,6 +227,9 @@ class Rendezvous {
* @returns {AsyncIterable<{ signedPeerRecord: Uint8Array, ns: string, ttl: number }>}
*/
async * discover (ns, limit = MAX_DISCOVER_LIMIT) {
// TODO: consider opening the envelope in the dicover
// This would store the addresses in the AddressBook

// Are there available rendezvous servers?
if (!this._rendezvousPoints || !this._rendezvousPoints.length) {
throw errCode(new Error('no rendezvous servers connected'), errCodes.NO_CONNECTED_RENDEZVOUS_SERVERS)
Expand Down
21 changes: 15 additions & 6 deletions src/server/bin.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

'use strict'

// Usage: $0 [--peerId <jsonFilePath>] [--listenMultiaddrs <ma> ... <ma>] [--announceMultiaddrs <ma> ... <ma>] [--metricsPort <port>] [--disableMetrics]
// Usage: $0 [--datastoreHost <hostname>] [--datastoreUser <username>] [datastorePassword <password>] [datastoreDatabase <name>] [--enableMemoryDatabase]
// [--peerId <jsonFilePath>] [--listenMultiaddrs <ma> ... <ma>] [--announceMultiaddrs <ma> ... <ma>] [--metricsPort <port>] [--disableMetrics]

/* eslint-disable no-console */

Expand All @@ -23,9 +24,17 @@ const PeerId = require('peer-id')

const RendezvousServer = require('./index')
const Datastore = require('./datastores/mysql')
const DatastoreMemory = require('./datastores/memory')
const { getAnnounceAddresses, getListenAddresses } = require('./utils')

async function main () {
// Datastore
const memoryDatabase = (argv.enableMemoryDatabase || argv.emd || process.env.DISABLE_METRICS)
const host = argv.datastoreHost || argv.dh || process.env.DATASTORE_HOST || 'localhost'
const user = argv.datastoreUser || argv.du || process.env.DATASTORE_USER || 'root'
const password = argv.datastorePassword || argv.dp || process.env.DATASTORE_PASSWORD || 'test-secret-pw'
const database = argv.datastoreDatabase || argv.dd || process.env.DATASTORE_DATABASE || 'libp2p_rendezvous_db'

// Metrics
let metricsServer
const metrics = !(argv.disableMetrics || process.env.DISABLE_METRICS)
Expand All @@ -46,11 +55,11 @@ async function main () {
log('If you want to keep the same address for the server you should provide a peerId with --peerId <jsonFilePath>')
}

const datastore = new Datastore({
host: 'localhost',
user: 'root',
password: 'test-secret-pw',
database: 'libp2p_rendezvous_db'
const datastore = memoryDatabase ? new DatastoreMemory() : new Datastore({
host,
user,
password,
database
})

// Create Rendezvous server
Expand Down
4 changes: 1 addition & 3 deletions src/server/datastores/mysql.js
Original file line number Diff line number Diff line change
Expand Up @@ -329,10 +329,8 @@ class Mysql {
namespace varchar(255),
reg_id INT UNSIGNED,
peer_id varchar(255) NOT NULL,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id, namespace, reg_id),
FOREIGN KEY (reg_id) REFERENCES registration(id) ON DELETE CASCADE,
INDEX (created_at)
FOREIGN KEY (reg_id) REFERENCES registration(id) ON DELETE CASCADE
);
`, (err) => {
if (err) {
Expand Down
6 changes: 0 additions & 6 deletions src/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,11 @@ const { fallbackNullish } = require('./utils')
/**
* @typedef {import('./datastores/interface').Datastore} Datastore
* @typedef {import('./datastores/interface').Registration} Registration
*
* @typedef {Object} NamespaceRegistration
* @property {string} id random generated id to map cookies
* @property {number} expiration
*/

/**
* @typedef {Object} RendezvousServerOptions
* @property {Datastore} datastore
* @property {number} [gcDelay = 3e5] garbage collector delay (default: 5 minutes)
* @property {number} [gcInterval = 7.2e6] garbage collector interval (default: 2 hours)
* @property {number} [minTtl = MIN_TTL] minimum acceptable ttl to store a registration
* @property {number} [maxTtl = MAX_TTL] maxium acceptable ttl to store a registration
* @property {number} [maxNsLength = MAX_NS_LENGTH] maxium acceptable namespace length
Expand Down