Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: auto relay example #795

Merged
merged 5 commits into from
Nov 20, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,12 @@ jobs:
- npm install
- LIBP2P_JS=${TRAVIS_BUILD_DIR}/src/index.js npx aegir test -t node --bail

- stage: test
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should examples just run on PR? Ideally I think they'd only run on a release but we dont have that flow atm. Running on branches seems excessive.

name: example - auto-relay
script:
- cd examples
- npm install
- npm run test -- auto-relay

notifications:
email: false
192 changes: 192 additions & 0 deletions examples/auto-relay/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# Auto relay

Auto Relay enables libp2p nodes to dynamically find and bind to relays on the network. Once binding (listening) is done, the node can and should advertise its addresses on the network, allowing any other node to dial it over its bound relay(s).
While direct connections to nodes are preferable, it's not always possible to do so due to NATs or browser limitations.

## 0. Setup the example

Before moving into the examples, you should run `npm install` on the top level `js-libp2p` folder, in order to install all the dependencies needed for this example.

This example comes with 3 main files. A `relay.js` file to be used in the first step, a `auto-relay.js` file to be used in the second step and a `other-node.js` file to be used on the third step. All of this scripts will run their own libp2p node, which will interact with the previous ones. This way, you need to have all of them running as you proceed.
vasco-santos marked this conversation as resolved.
Show resolved Hide resolved

## 1. Set up a relay node

Aiming to support nodes with connectivity issues, you will need to set up a relay node for the former nodes to bind.
vasco-santos marked this conversation as resolved.
Show resolved Hide resolved

The relay node will need to have its relay subsystem enabled, as well as its HOP capability. It can be configured as follows:

```js
const Libp2p = require('libp2p')
const Websockets = require('libp2p-websockets')
const { NOISE } = require('libp2p-noise')
const MPLEX = require('libp2p-mplex')

const node = await Libp2p.create({
modules: {
transport: [Websockets],
connEncryption: [NOISE],
streamMuxer: [MPLEX]
},
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0/ws']
// TODO check "What is next?" section
// announce: ['/dns4/auto-relay.libp2p.io/tcp/443/wss/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3']
// announceFilter: (addresses) => addresses
},
config: {
relay: {
enabled: true,
hop: {
enabled: true
},
advertise: {
enabled: true,
}
}
}
})

await node.start()

console.log(`Node started: ${node.peerId.toB58String()}`)
console.log('Listening on:')
node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`))
```

The Relay HOP advertise functionality is **NOT** required to be enabled. However, if you are interested in advertising on the network that this node is available to be used as a HOP Relay you can enable it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with this example is that the node has no way of actually advertising. A content router has to be included for this to work. I would disable it for this example and mention here that a router is required. Ideally this should link to available routers so we can just update that section with Rendezvous is ready.


You should now run the following to start the relay node:

```sh
node relay.js
jacobheun marked this conversation as resolved.
Show resolved Hide resolved
```

This should print out something similar to the following:

```sh
Node started: QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3
Listening on:
/ip4/127.0.0.1/tcp/61592/ws/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3
/ip4/192.168.1.120/tcp/61592/ws/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3
```

TODO: Docker Image with a repo
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this for?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should create a simple Docker Image with relay and HOP enabled to enable people to easily deploy Relays. I will remove this from now, while I think in the best solution to also include the announce addresses logic


## 2. Set up a node with Auto Relay Enabled

One of the typical use cases for Auto Relay is nodes behind a NAT or browser nodes thanks to their limitations regarding listening for new connections. For running a libp2p node that automatically binds itself to connected HOP relays, you can see the following:
vasco-santos marked this conversation as resolved.
Show resolved Hide resolved

```js
const Libp2p = require('libp2p')
const Websockets = require('libp2p-websockets')
const { NOISE } = require('libp2p-noise')
const MPLEX = require('libp2p-mplex')

const pWaitFor = require('p-wait-for')

const relayAddr = process.argv[2]
if (!relayAddr) {
throw new Error('the relay address needs to be specified as a parameter')
}

const node = await Libp2p.create({
modules: {
transport: [Websockets],
connEncryption: [NOISE],
streamMuxer: [MPLEX]
},
config: {
relay: {
enabled: true,
autoRelay: {
enabled: true,
maxListeners: 2
}
}
}
})

await node.start()
console.log(`Node started: ${node.peerId.toB58String()}`)

await node.dial(relayAddr)

// Wait for connection and relay to be bind for the example purpose
vasco-santos marked this conversation as resolved.
Show resolved Hide resolved
await pWaitFor(() => node.multiaddrs.length > 0)

console.log('connected to the HOP relay')
console.log('Listening on:')
node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`))
```

As you can see in the code, we need to provide the `relayAddr` as a process argument. This node will dial the relay and automatically bind to the relay.
vasco-santos marked this conversation as resolved.
Show resolved Hide resolved

You should now run the following to start the relay node:
vasco-santos marked this conversation as resolved.
Show resolved Hide resolved

```sh
node auto-relay.js /ip4/192.168.1.120/tcp/58941/ws/p2p/QmQKCBm87HQMbFqy14oqC85pMmnRrj6iD46ggM6reqNpsd
```

This should print out something similar to the following:

```sh
Node started: QmerrWofKF358JE6gv3z74cEAyL7z1KqhuUoVfGEynqjRm
connected to the HOP relay
Listening on:
/ip4/192.168.1.120/tcp/61592/ws/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3/p2p-circuit/p2p/QmerrWofKF358JE6gv3z74cEAyL7z1KqhuUoVfGEynqjRm
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider cleaning the logging up a bit to be a bit clearer, something like:

Suggested change
Node started: QmerrWofKF358JE6gv3z74cEAyL7z1KqhuUoVfGEynqjRm
connected to the HOP relay
Listening on:
/ip4/192.168.1.120/tcp/61592/ws/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3/p2p-circuit/p2p/QmerrWofKF358JE6gv3z74cEAyL7z1KqhuUoVfGEynqjRm
Node started with id QmerrWofKF358JE6gv3z74cEAyL7z1KqhuUoVfGEynqjRm
Connected to the HOP relay QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3
Advertising with a relay address of /ip4/192.168.1.120/tcp/61592/ws/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3/p2p-circuit/p2p/QmerrWofKF358JE6gv3z74cEAyL7z1KqhuUoVfGEynqjRm

```

Per the address, it is possible to verify that the auto relay node is listening on the circuit relay node address.

Instead of dialing this relay manually, you could set up this node with the Bootstrap module and provide it in the bootstrap list. Moreover, you can use other `peer-discovery` modules to discover peers in the network and the node will automatically bind to the relays that support HOP until reaching the maximum number of listeners.

## 3. Set up another node for testing connectivity

Now that you have a relay node and a node bound to that relay, you can test connecting to the auto relay node via the relay.

```js
const Libp2p = require('libp2p')
const Websockets = require('libp2p-websockets')
const { NOISE } = require('libp2p-noise')
const MPLEX = require('libp2p-mplex')

const autoRelayNodeAddr = process.argv[2]
if (!autoRelayNodeAddr) {
throw new Error('the auto relay node address needs to be specified')
}

const node = await Libp2p.create({
modules: {
transport: [Websockets],
connEncryption: [NOISE],
streamMuxer: [MPLEX]
}
})

await node.start()
console.log(`Node started: ${node.peerId.toB58String()}`)

const conn = await node.dial(autoRelayNodeAddr)
console.log(`Connected to the auto relay node via ${conn.remoteAddr.toString()}`)
```

You should now run the following to start the relay node using the listen address from step 2:

```sh
node other-node.js /ip4/192.168.1.120/tcp/58941/ws/p2p/QmQKCBm87HQMbFqy14oqC85pMmnRrj6iD46ggM6reqNpsd
```

Once you start your test node, it should print out something similar to the following:

```sh
Node started: Qme7iEzDxFoFhhkrsrkHkMnM11aPYjysaehP4NZeUfVMKG
Connected to the auto relay node via /ip4/192.168.1.120/tcp/61592/ws/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3/p2p-circuit/p2p/QmerrWofKF358JE6gv3z74cEAyL7z1KqhuUoVfGEynqjRm
```

As you can see from the output, the remote address of the established connection uses the relayed connection.

## 4. What is next?

Before moving into production, there are a few things that you should take into account.

A relay node should not advertise its private address in a real world scenario, as the node would not be reachable by others. You should provide an array of public addresses in the libp2p `addresses.announce` option. If you are using websockets, bear in mind that due to browser’s security policies you cannot establish unencrypted connection from secure context. The simplest solution is to setup SSL with nginx and proxy to the node and setup a domain name for the certificate.
44 changes: 44 additions & 0 deletions examples/auto-relay/auto-relay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict'

const Libp2p = require('libp2p')
const Websockets = require('libp2p-websockets')
const { NOISE } = require('libp2p-noise')
const MPLEX = require('libp2p-mplex')

const pWaitFor = require('p-wait-for')

const relayAddr = process.argv[2]
if (!relayAddr) {
throw new Error('the relay address needs to be specified as a parameter')
}

;(async () => {
const node = await Libp2p.create({
modules: {
transport: [Websockets],
connEncryption: [NOISE],
streamMuxer: [MPLEX]
},
config: {
relay: {
enabled: true,
autoRelay: {
enabled: true,
maxListeners: 2
}
}
}
})

await node.start()
console.log(`Node started: ${node.peerId.toB58String()}`)

await node.dial(relayAddr)

// Wait for connection and relay to be bind for the example purpose
await pWaitFor(() => node.multiaddrs.length > 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of pWaitFor perhaps it would be best to listen on the Peer Store for our own address change. This also ensures we're only using libp2p deps in this example.


console.log('connected to the HOP relay')
console.log('Listening on:')
node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`))
})()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: instead of a self executing anonymous function it might be helpful to put these in a function like main and then execute that. Same for the other files.

27 changes: 27 additions & 0 deletions examples/auto-relay/other-node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use strict'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of other-node perhaps we should use the same format as other examples: dialer, listener?


const Libp2p = require('libp2p')
const Websockets = require('libp2p-websockets')
const { NOISE } = require('libp2p-noise')
const MPLEX = require('libp2p-mplex')

const autoRelayNodeAddr = process.argv[2]
if (!autoRelayNodeAddr) {
throw new Error('the auto relay node address needs to be specified')
}

;(async () => {
const node = await Libp2p.create({
modules: {
transport: [Websockets],
connEncryption: [NOISE],
streamMuxer: [MPLEX]
}
})

await node.start()
console.log(`Node started: ${node.peerId.toB58String()}`)

const conn = await node.dial(autoRelayNodeAddr)
console.log(`Connected to the auto relay node via ${conn.remoteAddr.toString()}`)
})()
37 changes: 37 additions & 0 deletions examples/auto-relay/relay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use strict'

const Libp2p = require('libp2p')
const Websockets = require('libp2p-websockets')
const { NOISE } = require('libp2p-noise')
const MPLEX = require('libp2p-mplex')

;(async () => {
const node = await Libp2p.create({
modules: {
transport: [Websockets],
connEncryption: [NOISE],
streamMuxer: [MPLEX]
},
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0/ws']
// announceFilter: TODO check production section
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What production section?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed, it was the What's next section where I mention that the announceAddrs should be changed for production

},
config: {
relay: {
enabled: true,
hop: {
enabled: true
},
advertise: {
enabled: true,
}
}
}
})

await node.start()

console.log(`Node started: ${node.peerId.toB58String()}`)
console.log('Listening on:')
node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`))
})()
Loading