diff --git a/README.md b/README.md index 67bfa31c87..25ca9e05ea 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ - [Bundles](#bundles) - [Usage](#usage) - [Install](#install) + - [Usage](#usage) - [API](#api) - [Development](#development) - [Tests](#tests) @@ -46,7 +47,7 @@ We are in the process of writting better documentation, blog posts, tutorials an - [libp2p.io](https://libp2p.io) - [Specification (WIP)](https://github.com/libp2p/specs) - Talks - - [`libp2p <3 ethereum` at DEVCON2](https://ethereumfoundation.org/devcon/?session=libp2p) [video](https://www.youtube.com/watch?v=HxueJbeMVG4) [slides](https://ethereumfoundation.org/devcon/wp-content/uploads/2016/10/libp2p-HEART-devp2p-IPFS-PLUS-Ethereum-networking.pdf) [demo-1](https://ethereumfoundation.org/devcon/wp-content/uploads/2016/10/libp2p_demo1-1.mp4) [demo-2](https://ethereumfoundation.org/devcon/wp-content/uploads/2016/10/libp2p_demo2-1.mp4) + - [`libp2p <3 ethereum` at DEVCON2](https://ethereumfoundation.org/devcon/?session=libp2p) [📼 video](https://www.youtube.com/watch?v=HxueJbeMVG4) [slides](https://ethereumfoundation.org/devcon/wp-content/uploads/2016/10/libp2p-HEART-devp2p-IPFS-PLUS-Ethereum-networking.pdf) [📼 demo-1](https://ethereumfoundation.org/devcon/wp-content/uploads/2016/10/libp2p_demo1-1.mp4) [📼 demo-2](https://ethereumfoundation.org/devcon/wp-content/uploads/2016/10/libp2p_demo2-1.mp4) - Articles - [The overview of libp2p](https://github.com/libp2p/libp2p#description) @@ -71,6 +72,10 @@ npm install --save libp2p ## Usage +### [Tutorials and Examples](/examples) + +You can find multiple examples on the [examples folder](/examples) that will guide you through using libp2p for several scenarions. + ### Extending libp2p skeleton libp2p becomes very simple and basically acts as a glue for every module that compose this library. Since it can be highly customized, it requires some setup. What we recommend is to have a libp2p build for the system you are developing taking into account in your needs (e.g. for a browser working version of libp2p that acts as the network layer of IPFS, we have a built and minified version that browsers can require). diff --git a/examples/README.md b/examples/README.md index 0102dd3682..6b8917e67c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,8 +4,23 @@ In this folder, you can find a variety of examples to help you get started in us Let us know if you find any issue or if you want to contribute and add a new tutorial, feel welcome to submit a PR, thank you! -## Examples +## Understanding how libp2p works +- [Transports](./transports) +- [Protocol and Stream Muxing](./protocol-and-stream-muxing) +- [Encrypted Communications](./encrypted-communications) +- [Discovery Mechanisms](./discovery-mechanisms) +- [Peer Routing](./peer-routing) +- [Content Routing](./content-routing) +- [PubSub](./pubsub) +- [NAT Traversal](./nat-traversal) +- Circuit Relay (future) +- Naming (future) + +## Other examples + +- [Running libp2p in the Browser](./libp2p-in-the-browser) +- Running libp2p in the Electron (future) - [The standard echo net example with libp2p](./echo) - [A simple chat app with](./chat) - [See other nodes in the network using WebRTC Star discovery mechanism](./see-nodes) diff --git a/examples/discovery-mechanisms/README.md b/examples/discovery-mechanisms/README.md new file mode 100644 index 0000000000..38f33ceff9 --- /dev/null +++ b/examples/discovery-mechanisms/README.md @@ -0,0 +1,2 @@ +# WIP - This example is still in the works +![](http://1.bp.blogspot.com/-tNvSnCW0KlQ/U-KOKGVoJkI/AAAAAAAAA3Q/aiSLMeSJFtw/s1600/WIP-sign.jpg) diff --git a/examples/encrypted-communications/1.js b/examples/encrypted-communications/1.js new file mode 100644 index 0000000000..7d20b383f4 --- /dev/null +++ b/examples/encrypted-communications/1.js @@ -0,0 +1,59 @@ +'use strict' + +const libp2p = require('libp2p') +const TCP = require('libp2p-tcp') +const SPDY = require('libp2p-spdy') +const SECIO = require('libp2p-secio') +const PeerInfo = require('peer-info') +const waterfall = require('async/waterfall') +const parallel = require('async/parallel') +const pull = require('pull-stream') + +class MyBundle extends libp2p { + constructor (peerInfo) { + const modules = { + transport: [new TCP()], + connection: { + muxer: [SPDY], + crypto: [SECIO] + } + } + super(modules, peerInfo) + } +} + +function createNode (callback) { + let node + + waterfall([ + (cb) => PeerInfo.create(cb), + (peerInfo, cb) => { + peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0') + node = new MyBundle(peerInfo) + node.start(cb) + } + ], (err) => callback(err, node)) +} + +parallel([ + (cb) => createNode(cb), + (cb) => createNode(cb) +], (err, nodes) => { + if (err) { throw err } + + const node1 = nodes[0] + const node2 = nodes[1] + + node2.handle('/a-protocol', (protocol, conn) => { + pull( + conn, + pull.map((v) => v.toString()), + pull.log() + ) + }) + + node1.dial(node2.peerInfo, '/a-protocol', (err, conn) => { + if (err) { throw err } + pull(pull.values(['This information is sent out encrypted to the other peer']), conn) + }) +}) diff --git a/examples/encrypted-communications/README.md b/examples/encrypted-communications/README.md new file mode 100644 index 0000000000..5247dc57d5 --- /dev/null +++ b/examples/encrypted-communications/README.md @@ -0,0 +1,39 @@ +# Encrypted Communications + +libp2p can leverage the encrypted communications from the transports it uses (i.e WebRTC). To ensure that every connection is encrypted, independently of how it was set up, libp2p also supports a set of modules that encrypt every communication established. + +We call this usage a _connection upgrade_ where given a connection between peer A to peer B, a protocol handshake can be performed that gives that connection new properties. + +A byproduct of having these encrypted communications modules is that we can authenticate the peers we are dialing to. You might have noticed that every time we dial to a peer in libp2p space, we always use its PeerId at the end (e.g /ip4/127.0.0.1/tcp/89765/ipfs/QmWCbVw1XZ8hiYBwwshPce2yaTDYTqTaP7GCHGpry3ykWb), this PeerId is generated by hashing the Public Key of the peer. With this, we can create a crypto challenge when dialing to another peer and prove that peer is the owner of a PrivateKey that matches the Public Key we know. + +# 1. Set up encrypted communications with SECIO + +We will build this example on top of example for [Protocol and Stream Multiplexing](../protocol-and-stream-multiplexing). You will need the module `libp2p-secio` to complete it, go ahead and `npm install libp2p-secio`. + +SECIO is the crypto channel developed for IPFS, it is a TLS 1.3 like crypto channel that established an encrypted communication channel between two peers. + +To add it to your libp2p bundle, all you have to do is: + +```JavaScript +const SECIO = require('libp2p-secio') + +class MyBundle extends libp2p { + constructor (peerInfo) { + const modules = { + transport: [new TCP()], + connection: { + muxer: [SPDY], + // Attach secio as the crypto channel to use + crypto: [SECIO] + } + } + super(modules, peerInfo) + } +} +``` + +And that's it, from now on, all your libp2p communications are encrypted. Try running the exampme [1.js](./1.js) to see it working. + +If you want to want to learn more about how SECIO works, you can read the [great write up done by Dominic Tarr](https://github.com/auditdrivencrypto/secure-channel/blob/master/prior-art.md#ipfss-secure-channel). + +Importante note: SECIO hasn't been audited and so, we do not recommend to trust its security. We intent to move to TLS 1.3 once the specification is finalized and an implementation exists that we can use. diff --git a/examples/libp2p-in-the-browser/README.md b/examples/libp2p-in-the-browser/README.md new file mode 100644 index 0000000000..38f33ceff9 --- /dev/null +++ b/examples/libp2p-in-the-browser/README.md @@ -0,0 +1,2 @@ +# WIP - This example is still in the works +![](http://1.bp.blogspot.com/-tNvSnCW0KlQ/U-KOKGVoJkI/AAAAAAAAA3Q/aiSLMeSJFtw/s1600/WIP-sign.jpg) diff --git a/examples/nat-traversal/README.md b/examples/nat-traversal/README.md new file mode 100644 index 0000000000..38f33ceff9 --- /dev/null +++ b/examples/nat-traversal/README.md @@ -0,0 +1,2 @@ +# WIP - This example is still in the works +![](http://1.bp.blogspot.com/-tNvSnCW0KlQ/U-KOKGVoJkI/AAAAAAAAA3Q/aiSLMeSJFtw/s1600/WIP-sign.jpg) diff --git a/examples/peer-routing/README.md b/examples/peer-routing/README.md new file mode 100644 index 0000000000..38f33ceff9 --- /dev/null +++ b/examples/peer-routing/README.md @@ -0,0 +1,2 @@ +# WIP - This example is still in the works +![](http://1.bp.blogspot.com/-tNvSnCW0KlQ/U-KOKGVoJkI/AAAAAAAAA3Q/aiSLMeSJFtw/s1600/WIP-sign.jpg) diff --git a/examples/protocol-and-stream-muxing/1.js b/examples/protocol-and-stream-muxing/1.js new file mode 100644 index 0000000000..1976ae54ce --- /dev/null +++ b/examples/protocol-and-stream-muxing/1.js @@ -0,0 +1,96 @@ +'use strict' + +const libp2p = require('libp2p') +const TCP = require('libp2p-tcp') +const PeerInfo = require('peer-info') +const waterfall = require('async/waterfall') +const parallel = require('async/parallel') +const pull = require('pull-stream') + +class MyBundle extends libp2p { + constructor (peerInfo) { + const modules = { + transport: [new TCP()] + } + super(modules, peerInfo) + } +} + +function createNode (callback) { + let node + + waterfall([ + (cb) => PeerInfo.create(cb), + (peerInfo, cb) => { + peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0') + node = new MyBundle(peerInfo) + node.start(cb) + } + ], (err) => callback(err, node)) +} + +parallel([ + (cb) => createNode(cb), + (cb) => createNode(cb) +], (err, nodes) => { + if (err) { throw err } + + const node1 = nodes[0] + const node2 = nodes[1] + + // exact matching + node2.handle('/your-protocol', (protocol, conn) => { + pull( + conn, + pull.map((v) => v.toString()), + pull.log() + ) + }) + + // semver matching + /* + node2.handle('/another-protocol/1.0.1', (protocol, conn) => { + pull( + conn, + pull.map((v) => v.toString()), + pull.log() + ) + }) + */ + + // custom func matching + /* + node2.handle('/custom-match-func', (protocol, conn) => { + pull( + conn, + pull.map((v) => v.toString()), + pull.log() + ) + }, (myProtocol, requestedProtocol, callback) => { + if (myProtocol.indexOf(requestedProtocol)) { + callback(null, true) + } else { + callback(null, false) + } + }) + */ + + node1.dial(node2.peerInfo, '/your-protocol', (err, conn) => { + if (err) { throw err } + pull(pull.values(['my own protocol, wow!']), conn) + }) + + /* + node1.dial(node2.peerInfo, '/another-protocol/1.0.0', (err, conn) => { + if (err) { throw err } + pull(pull.values(['semver me please']), conn) + }) + */ + + /* + node1.dial(node2.peerInfo, '/custom-match-func/some-query', (err, conn) => { + if (err) { throw err } + pull(pull.values(['do I fall into your criteria?']), conn) + }) + */ +}) diff --git a/examples/protocol-and-stream-muxing/2.js b/examples/protocol-and-stream-muxing/2.js new file mode 100644 index 0000000000..0dd421703a --- /dev/null +++ b/examples/protocol-and-stream-muxing/2.js @@ -0,0 +1,79 @@ +'use strict' + +const libp2p = require('libp2p') +const TCP = require('libp2p-tcp') +const SPDY = require('libp2p-spdy') +const PeerInfo = require('peer-info') +const waterfall = require('async/waterfall') +const parallel = require('async/parallel') +const series = require('async/series') +const pull = require('pull-stream') + +class MyBundle extends libp2p { + constructor (peerInfo) { + const modules = { + transport: [new TCP()], + connection: { + muxer: [SPDY] + } + } + super(modules, peerInfo) + } +} + +function createNode (callback) { + let node + + waterfall([ + (cb) => PeerInfo.create(cb), + (peerInfo, cb) => { + peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0') + node = new MyBundle(peerInfo) + node.start(cb) + } + ], (err) => callback(err, node)) +} + +parallel([ + (cb) => createNode(cb), + (cb) => createNode(cb) +], (err, nodes) => { + if (err) { throw err } + + const node1 = nodes[0] + const node2 = nodes[1] + + node2.handle('/a', (protocol, conn) => { + pull( + conn, + pull.map((v) => v.toString()), + pull.log() + ) + }) + + node2.handle('/b', (protocol, conn) => { + pull( + conn, + pull.map((v) => v.toString()), + pull.log() + ) + }) + + series([ + (cb) => node1.dial(node2.peerInfo, '/a', (err, conn) => { + if (err) { throw err } + pull(pull.values(['protocol (a)']), conn) + cb() + }), + (cb) => node1.dial(node2.peerInfo, '/b', (err, conn) => { + if (err) { throw err } + pull(pull.values(['protocol (b)']), conn) + cb() + }), + (cb) => node1.dial(node2.peerInfo, '/b', (err, conn) => { + if (err) { throw err } + pull(pull.values(['another conn on protocol (b)']), conn) + cb() + }) + ]) +}) diff --git a/examples/protocol-and-stream-muxing/3.js b/examples/protocol-and-stream-muxing/3.js new file mode 100644 index 0000000000..b9d66c666d --- /dev/null +++ b/examples/protocol-and-stream-muxing/3.js @@ -0,0 +1,83 @@ +'use strict' + +const libp2p = require('libp2p') +const TCP = require('libp2p-tcp') +const SPDY = require('libp2p-spdy') +const PeerInfo = require('peer-info') +const waterfall = require('async/waterfall') +const parallel = require('async/parallel') +const series = require('async/series') +const pull = require('pull-stream') + +class MyBundle extends libp2p { + constructor (peerInfo) { + const modules = { + transport: [new TCP()], + connection: { + muxer: [SPDY] + } + } + super(modules, peerInfo) + } +} + +function createNode (callback) { + let node + + waterfall([ + (cb) => PeerInfo.create(cb), + (peerInfo, cb) => { + peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0') + node = new MyBundle(peerInfo) + node.start(cb) + } + ], (err) => callback(err, node)) +} + +parallel([ + (cb) => createNode(cb), + (cb) => createNode(cb) +], (err, nodes) => { + if (err) { throw err } + + const node1 = nodes[0] + const node2 = nodes[1] + + node1.handle('/node-1', (protocol, conn) => { + pull( + conn, + pull.map((v) => v.toString()), + pull.log() + ) + }) + + node2.handle('/node-2', (protocol, conn) => { + pull( + conn, + pull.map((v) => v.toString()), + pull.log() + ) + }) + + series([ + (cb) => node1.dial(node2.peerInfo, '/node-2', (err, conn) => { + if (err) { throw err } + pull(pull.values(['from 1 to 2']), conn) + cb() + }), + (cb) => node2.dial(node1.peerInfo, '/node-1', (err, conn) => { + if (err) { throw err } + pull(pull.values(['from 2 to 1']), conn) + cb() + }) + ], (err) => { + if (err) { throw err } + console.log('Addresses by which both peers are connected') + node1.peerBook + .getAllArray() + .forEach((peer) => console.log('node 1 to node 2:', peer.isConnected().toString())) + node2.peerBook + .getAllArray() + .forEach((peer) => console.log('node 2 to node 1:', peer.isConnected().toString())) + }) +}) diff --git a/examples/protocol-and-stream-muxing/README.md b/examples/protocol-and-stream-muxing/README.md new file mode 100644 index 0000000000..d361cc8f7c --- /dev/null +++ b/examples/protocol-and-stream-muxing/README.md @@ -0,0 +1,174 @@ +# Protocol and Stream Multiplexing (aka muxing) + +One of the specialties of libp2p is solving the bane of protocol discovery and handshake between machines. Before libp2p, you would have to assign a listener to a port and then through some process of formal specification you would assign ports to special protocols so that other hosts would know before hand which port to dial (e.g ssh (22), http (80), https (443), ftp (21), etc). With libp2p you don't need to do that anymore, not only you don't have to assign ports before hand, you don't even need to think about ports at all since all the protocol handshaking happens in the wire! + +The feature of agreeing on a protocol over an established connection is what we call _protocol multiplexing_ and it is possible through [multistream-select](https://github.com/multiformats/multistream), another protocol that lets you agree per connection (or stream) which protocol is going to be talked over that connection (select), it also enables you to request the other end to tell you which protocols it supports (ls). You can learn more about multistream-select at its [specification repo](https://github.com/multiformats/multistream). + +# 1. Handle multiple protocols + +Let's see _protocol multiplexing_ in action! You will need the following modules for this example: `libp2p`, `libp2p-tcp`, `peer-info`, `async` and `pull-stream`. This example reuses the base left by the [Transports](../transports) example. You can see the complete solution at [1.js](./1.js). + +After creating the nodes, we need to tell libp2p which protocols to handle. + +```JavaScript +// ... +const node1 = nodes[0] +const node2 = nodes[1] + +// Here we are telling libp2p that is someone dials this node to talk with the `/your-protocol` +// multicodec, the protocol identifier, please call this callback and give it the connection +// so that incomming data can be handled +node2.handle('/your-protocol', (protocol, conn) => { + pull( + conn, + pull.map((v) => v.toString()), + pull.log() + ) +}) +``` + +After the protocol is _handled_, now we can dial to it. + +```JavaScript +node1.dial(node2.peerInfo, '/your-protocol', (err, conn) => { + if (err) { throw err } + pull(pull.values(['my own protocol, wow!']), conn) +}) +``` + +You might have seen this in the [Transports](../transports) examples. However, what it was not explained is that you can do more than exact string matching, for example, you can use semver. + +```JavaScript +node2.handle('/another-protocol/1.0.1', (protocol, conn) => { + pull( + conn, + pull.map((v) => v.toString()), + pull.log() + ) +}) +// ... +node1.dial(node2.peerInfo, '/another-protocol/1.0.0', (err, conn) => { + if (err) { throw err } + pull(pull.values(['semver me please']), conn) +}) +``` + +This feature is super power for network protocols. It works in the same way as versioning your RPC/REST API, but for anything that goes in the wire. We had to use this feature to upgrade protocols within the IPFS Stack (i.e Bitswap) and we successfully managed to do so without any network splits. + +There is still one last feature, you can create your custom match functions. + +```JavaScript +node2.handle('/custom-match-func', (protocol, conn) => { + pull( + conn, + pull.map((v) => v.toString()), + pull.log() + ) +}, (myProtocol, requestedProtocol, callback) => { + // This is all custom. I'm checking the base path matches, think of this + // as a HTTP routing table. + if (myProtocol.indexOf(requestedProtocol)) { + callback(null, true) + } else { + callback(null, false) + } +}) +// ... +node1.dial(node2.peerInfo, '/custom-match-func/some-query', (err, conn) => { + if (err) { throw err } + pull(pull.values(['do I fall into your criteria?']), conn) +}) +``` + +Try all of this out by executing [1.js](./1.js). + +# 2. Reuse existing connection + +The example above would require a node to create a whole new connection for every time it dials in one of the protocols, this is a waste of resources and also it might be simply not possible (e.g lack of file descriptors, not enough ports being open, etc). What we really want is to dial a connection once and then multiplex several virtual connections (stream) over a single connection, this is where _stream multiplexing_ comes into play. + +Stream multiplexing is a old concept, in fact it happens in many of the layers of the [OSI System](https://en.wikipedia.org/wiki/OSI_model), in libp2p we make this feature to our avail by letting the user pick which module for stream multiplexing to use. + +Currently, we have two available [libp2p-spdy](https://github.com/libp2p/js-libp2p-spdy) and [libp2p-multiplex](https://github.com/libp2p/js-libp2p-multiplex) and pluging them in is as easy as adding another transport. Let's revisit our libp2p bundle. + +```JavaScript +const SPDY = require('libp2p-spdy') +//... +class MyBundle extends libp2p { + constructor (peerInfo) { + const modules = { + transport: [new TCP()], + // Here we are adding the SPDY muxer to our libp2p bundle. + // Thanks to protocol muxing, a libp2p bundle can support multiple Stream Muxers at the same + // time and pick the right one when dialing to a node + connection: { + muxer: [SPDY] + } + } + super(modules, peerInfo) + } +} +``` + +With this, we can dial as many times as we want to a peer and always reuse the same established underlying connection. + +```JavaScript +node2.handle('/a', (protocol, conn) => { + pull( + conn, + pull.map((v) => v.toString()), + pull.log() + ) +}) + +node2.handle('/b', (protocol, conn) => { + pull( + conn, + pull.map((v) => v.toString()), + pull.log() + ) +}) + +series([ + (cb) => node1.dial(node2.peerInfo, '/a', (err, conn) => { + if (err) { throw err } + pull(pull.values(['protocol (a)']), conn) + cb() + }), + (cb) => node1.dial(node2.peerInfo, '/b', (err, conn) => { + if (err) { throw err } + pull(pull.values(['protocol (b)']), conn) + cb() + }), + (cb) => node1.dial(node2.peerInfo, '/b', (err, conn) => { + if (err) { throw err } + pull(pull.values(['another conn on protocol (b)']), conn) + cb() + }) +]) +``` + +By running [2.js](./2.js) you should see the following result: + +``` +> node 2.js +protocol (a) +protocol (b) +another protocol (b) +``` + +# 3. Bidirectional connections + +There is one last trick on _protocol and stream multiplexing_ that libp2p uses to make everyone's life easier and that is _biderectional connection_. + +With the aid of both mechanisms, we can reuse an incomming connection to dial streams out too, this is specially useful when you are behind tricky NAT, firewalls or if you are running in a browser, where you can have listening addrs, but you can dial out. By dialing out, you enable other peers to talk with you in Protocols that they want, simply by opening a new multiplexed stream. + +You can see this working on example [3.js](./3.js). The result should look like the following: + +```Bash +> node 3.js +from 1 to 2 +Addresses by which both peers are connected +node 1 to node 2: /ip4/127.0.0.1/tcp/50629/ipfs/QmZwMKTo6wG4Te9A6M2eJnWDpR8uhsGed4YRegnV5DcKiv +node 2 to node 1: /ip4/127.0.0.1/tcp/50630/ipfs/QmRgormJQeDyXhDKma11eUtksoh8vWmeBoxghVt4meauW9 +from 2 to 1 +``` diff --git a/examples/pubsub/README.md b/examples/pubsub/README.md new file mode 100644 index 0000000000..38f33ceff9 --- /dev/null +++ b/examples/pubsub/README.md @@ -0,0 +1,2 @@ +# WIP - This example is still in the works +![](http://1.bp.blogspot.com/-tNvSnCW0KlQ/U-KOKGVoJkI/AAAAAAAAA3Q/aiSLMeSJFtw/s1600/WIP-sign.jpg) diff --git a/examples/transports/1.js b/examples/transports/1.js new file mode 100644 index 0000000000..f7bb6a6713 --- /dev/null +++ b/examples/transports/1.js @@ -0,0 +1,32 @@ +'use strict' + +const libp2p = require('libp2p') +const TCP = require('libp2p-tcp') +const PeerInfo = require('peer-info') +const waterfall = require('async/waterfall') + +class MyBundle extends libp2p { + constructor (peerInfo) { + const modules = { + transport: [new TCP()] + } + super(modules, peerInfo) + } +} + +let node + +waterfall([ + (cb) => PeerInfo.create(cb), + (peerInfo, cb) => { + peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0') + node = new MyBundle(peerInfo) + node.start(cb) + } +], (err) => { + if (err) { throw err } + + console.log('node has started (true/false):', node.isOn()) + console.log('listening on:') + node.peerInfo.multiaddrs.forEach((ma) => console.log(ma.toString())) +}) diff --git a/examples/transports/2.js b/examples/transports/2.js new file mode 100644 index 0000000000..17c0e0a671 --- /dev/null +++ b/examples/transports/2.js @@ -0,0 +1,62 @@ +'use strict' + +const libp2p = require('libp2p') +const TCP = require('libp2p-tcp') +const PeerInfo = require('peer-info') +const waterfall = require('async/waterfall') +const parallel = require('async/parallel') +const pull = require('pull-stream') + +class MyBundle extends libp2p { + constructor (peerInfo) { + const modules = { + transport: [new TCP()] + } + super(modules, peerInfo) + } +} + +function createNode (callback) { + let node + + waterfall([ + (cb) => PeerInfo.create(cb), + (peerInfo, cb) => { + peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0') + node = new MyBundle(peerInfo) + node.start(cb) + } + ], (err) => callback(err, node)) +} + +function printAddrs (node, number) { + console.log('node %s is listening on:', number) + node.peerInfo.multiaddrs.forEach((ma) => console.log(ma.toString())) +} + +parallel([ + (cb) => createNode(cb), + (cb) => createNode(cb) +], (err, nodes) => { + if (err) { throw err } + + const node1 = nodes[0] + const node2 = nodes[1] + + printAddrs(node1, '1') + printAddrs(node2, '2') + + node2.handle('/print', (protocol, conn) => { + pull( + conn, + pull.map((v) => v.toString()), + pull.log() + ) + }) + + node1.dial(node2.peerInfo, '/print', (err, conn) => { + if (err) { throw err } + + pull(pull.values(['Hello', ' ', 'p2p', ' ', 'world', '!']), conn) + }) +}) diff --git a/examples/transports/3.js b/examples/transports/3.js new file mode 100644 index 0000000000..7f9817cea8 --- /dev/null +++ b/examples/transports/3.js @@ -0,0 +1,86 @@ +'use strict' + +const libp2p = require('libp2p') +const TCP = require('libp2p-tcp') +const WebSockets = require('libp2p-websockets') +const PeerInfo = require('peer-info') +const waterfall = require('async/waterfall') +const parallel = require('async/parallel') +const pull = require('pull-stream') + +class MyBundle extends libp2p { + constructor (peerInfo) { + const modules = { + transport: [new TCP(), new WebSockets()] + } + super(modules, peerInfo) + } +} + +function createNode (addrs, callback) { + if (!Array.isArray(addrs)) { + addrs = [addrs] + } + + let node + + waterfall([ + (cb) => PeerInfo.create(cb), + (peerInfo, cb) => { + addrs.forEach((addr) => peerInfo.multiaddrs.add(addr)) + node = new MyBundle(peerInfo) + node.start(cb) + } + ], (err) => callback(err, node)) +} + +function printAddrs (node, number) { + console.log('node %s is listening on:', number) + node.peerInfo.multiaddrs.forEach((ma) => console.log(ma.toString())) +} + +function print (protocol, conn) { + pull( + conn, + pull.map((v) => v.toString()), + pull.log() + ) +} + +parallel([ + (cb) => createNode('/ip4/0.0.0.0/tcp/0', cb), + (cb) => createNode(['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws'], cb), + (cb) => createNode('/ip4/127.0.0.1/tcp/20000/ws', cb) +], (err, nodes) => { + if (err) { throw err } + + const node1 = nodes[0] + const node2 = nodes[1] + const node3 = nodes[2] + + printAddrs(node1, '1') + printAddrs(node2, '2') + printAddrs(node3, '3') + + node1.handle('/print', print) + node2.handle('/print', print) + node3.handle('/print', print) + + node1.dial(node2.peerInfo, '/print', (err, conn) => { + if (err) { throw err } + + pull(pull.values(['node 1 dialed to node 2 successfully']), conn) + }) + + node2.dial(node3.peerInfo, '/print', (err, conn) => { + if (err) { throw err } + + pull(pull.values(['node 2 dialed to node 3 successfully']), conn) + }) + + node3.dial(node1.peerInfo, '/print', (err, conn) => { + if (err) { + console.log('node 3 failed to dial to node 1 with:', err.message) + } + }) +}) diff --git a/examples/transports/README.md b/examples/transports/README.md new file mode 100644 index 0000000000..07fb387211 --- /dev/null +++ b/examples/transports/README.md @@ -0,0 +1,305 @@ +# [Transports](http://libp2p.io/implementations/#transports) + +libp2p doesn't make assumptions for you, instead, it enables you as the developer of the application to pick the modules you need to run your application, which can vary depending on the runtime you are executing. A libp2p node can use one or more Transports to dial and listen for Connections. These transports are modules that offer a clean interface for dialing and listening, defined by the [interface-transport](https://github.com/libp2p/interface-transport) specification. Some examples of possible transports are: TCP, UTP, WebRTC, QUIC, HTTP, Pigeon and so on. + +A more complete definition of what is a transport can be found on the [interface-transport](https://github.com/libp2p/interface-transport) specification. A way to recognize a candidate transport is through the badge: + +[![](https://raw.githubusercontent.com/diasdavid/interface-transport/master/img/badge.png)](https://raw.githubusercontent.com/diasdavid/interface-transport/master/img/badge.png) + +## 1. Creating a libp2p Bundle with TCP + +When using libp2p, you always want to create your own libp2p Bundle, that is, pick your set of modules and create your network stack with the properties you need. In this example, we will create a bundle with TCP. You can find the complete solution on the file [1.js](/1.js). + +You will need 4 deps total, so go ahead and install all of them with: + +``` +> npm install libp2p libp2p-tcp peer-info async +``` + +Then, on your favorite text editor create a file with the `.js` extension. I've called mine `1.js`. + +First thing is to create our own bundle! Insert: + +```JavaScript +'use strict' + +const libp2p = require('libp2p') +const TCP = require('libp2p-tcp') +const PeerInfo = require('peer-info') +const waterfall = require('async/waterfall') + +// This MyBundle class is your libp2p bundle packed with TCP +class MyBundle extends libp2p { + constructor (peerInfo) { + // modules is a JS object that will describe the components + // we want for our libp2p bundle + const modules = { + transport: [new TCP()] + } + super(modules, peerInfo) + } +} +``` + +Now that we have our own MyBundle class that extends libp2p, let's create a node with it. We will use `async/waterfall` just for code structure, but you don't need to. Append to the same file: + +```JavaScript +let node + +waterfall([ + // First we create a PeerInfo object, which will pack the + // info about our peer. Creating a PeerInfo is an async + // operation because we use the WebCrypto API + // (yeei Universal JS) + (cb) => PeerInfo.create(cb), + (peerInfo, cb) => { + // To signall the addresses we want to be available, we use + // the multiaddr format, a self describable address + peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0') + // Now we can create a node with that PeerInfo object + node = new MyBundle(peerInfo) + // Last, we start the node! + node.start(cb) + } +], (err) => { + if (err) { throw err } + + // At this point the node has started + console.log('node has started (true/false):', node.isOn()) + // And we can print the now listening addresses. + // If you are familiar with TCP, you might have noticed + // that we specified the node to listen in 0.0.0.0 and port + // 0, which means "listen in any network interface and pick + // a port for me + console.log('listening on:') + node.peerInfo.multiaddrs.forEach((ma) => console.log(ma.toString())) +}) +``` + +Running this should result in somehting like: + +```bash +> node 1.js +node has started (true/false): true +listening on: +/ip4/127.0.0.1/tcp/61329/ipfs/QmW2cKTakTYqbQkUzBTEGXgWYFj1YEPeUndE1YWs6CBzDQ +/ip4/192.168.2.156/tcp/61329/ipfs/QmW2cKTakTYqbQkUzBTEGXgWYFj1YEPeUndE1YWs6CBzDQ +``` + +That `QmW2cKTakTYqbQkUzBTEGXgWYFj1YEPeUndE1YWs6CBzDQ` is the PeerId that was created during the PeerInfo generation. + +## 2. Dialing from one node to another node + +Now that we have our bundle, let's create two nodes and make them dial to each other! You can find the complete solution at [2.js](/2.js). + +For this step, we will need one more dependency. + +```bash +> npm install pull-stream +``` + +We are going to reuse the MyBundle class from step 1, but this time to make things simpler, we will create two functions, one to create nodes and another to print the addrs to avoid duplicating code. + +```JavaScript +function createNode (callback) { + let node + + waterfall([ + (cb) => PeerInfo.create(cb), + (peerInfo, cb) => { + peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0') + node = new MyBundle(peerInfo) + node.start(cb) + } + ], (err) => callback(err, node)) +} + +function printAddrs (node, number) { + console.log('node %s is listening on:', number) + node.peerInfo.multiaddrs.forEach((ma) => console.log(ma.toString())) +} +``` + +Now we are going to use `async/parallel` to create two nodes, print their addresses and dial from one node to the other. + +``` +parallel([ + (cb) => createNode(cb), + (cb) => createNode(cb) +], (err, nodes) => { + if (err) { throw err } + + const node1 = nodes[0] + const node2 = nodes[1] + + printAddrs(node1, '1') + printAddrs(node2, '2') + + node2.handle('/print', (protocol, conn) => { + pull( + conn, + pull.map((v) => v.toString()), + pull.log() + ) + }) + + node1.dial(node2.peerInfo, '/print', (err, conn) => { + if (err) { throw err } + + pull(pull.values(['Hello', ' ', 'p2p', ' ', 'world', '!']), conn) + }) +}) +``` + +The result should be look like: + +```bash +> node 2.js +node 1 is listening on: +/ip4/127.0.0.1/tcp/62279/ipfs/QmeM4wNWv1uci7UJjUXZYfvcy9uqAbw7G9icuxdqy88Mj9 +/ip4/192.168.2.156/tcp/62279/ipfs/QmeM4wNWv1uci7UJjUXZYfvcy9uqAbw7G9icuxdqy88Mj9 +node 2 is listening on: +/ip4/127.0.0.1/tcp/62278/ipfs/QmWp58xJgzbouNJcyiNNTpZuqQCJU8jf6ixc7TZT9xEZhV +/ip4/192.168.2.156/tcp/62278/ipfs/QmWp58xJgzbouNJcyiNNTpZuqQCJU8jf6ixc7TZT9xEZhV +Hello p2p world! +``` + +## 3. Using multiple transports + +Next, we want to be available in multiple transports to increase our chances of having common transports in the network. A simple scenario, a node running in the browser only has access to HTTP, WebSockets and WebRTC since the browser doesn't let you open any other kind of transport, for this node to dial to some other node, that other node needs to share a common transport. + +What we are going to do in this step is to create 3 nodes, one with TCP, another with TCP+WebSockets and another one with just WebSockets. The full solution can be found on [3.js](3.js). + +In this example, we will need to also install `libp2p-websockets`, go ahead and install: + +```sh +> npm install libp2p-websockets +``` + +We want to create 3 nodes, one with TCP, one with TCP+WebSockets and one with just WebSockets. We need to update our `MyBundle` class to contemplate WebSockets as well: + +```JavaScript +const WebSockets = require('libp2p-websockets') +// ... + +class MyBundle extends libp2p { + constructor (peerInfo) { + const modules = { + transport: [new TCP(), new WebSockets()] + } + super(modules, peerInfo) + } +} +``` + +Now that we have our bundle ready, let's upgrade our createNode function to enable us to pick the addrs in which a node will start a listener. + +```JavaScript +function createNode (addrs, callback) { + if (!Array.isArray(addrs)) { + addrs = [addrs] + } + + let node + + waterfall([ + (cb) => PeerInfo.create(cb), + (peerInfo, cb) => { + addrs.forEach((addr) => peerInfo.multiaddrs.add(addr)) + node = new MyBundle(peerInfo) + node.start(cb) + } + ], (err) => callback(err, node)) +} +``` + +As a rule, a libp2p node will only be capable of using a transport if: a) it has the module for it and b) it was given a multiaddr to listen on. The only exception to this rule is WebSockets in the browser, where a node can dial out, but unfortunately cannot open a socket. + +Let's update our flow to create nodes and see how they behave when dialing to each other: + +```JavaScript +parallel([ + (cb) => createNode('/ip4/0.0.0.0/tcp/0', cb), + // Here we add an extra multiaddr that has a /ws at the end, this means that we want + // to create a TCP socket, but mount it as WebSockets instead. + (cb) => createNode(['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws'], cb), + (cb) => createNode('/ip4/127.0.0.1/tcp/20000/ws', cb) +], (err, nodes) => { + if (err) { throw err } + + const node1 = nodes[0] + const node2 = nodes[1] + const node3 = nodes[2] + + printAddrs(node1, '1') + printAddrs(node2, '2') + printAddrs(node3, '3') + + node1.handle('/print', print) + node2.handle('/print', print) + node3.handle('/print', print) + + // node 1 (TCP) dials to node 2 (TCP+WebSockets) + node1.dial(node2.peerInfo, '/print', (err, conn) => { + if (err) { throw err } + + pull(pull.values(['node 1 dialed to node 2 successfully']), conn) + }) + + // node 2 (TCP+WebSockets) dials to node 2 (WebSockets) + node2.dial(node3.peerInfo, '/print', (err, conn) => { + if (err) { throw err } + + pull(pull.values(['node 2 dialed to node 3 successfully']), conn) + }) + + // node 3 (WebSockets) attempts to dial to node 1 (TCP) + node3.dial(node1.peerInfo, '/print', (err, conn) => { + if (err) { + console.log('node 3 failed to dial to node 1 with:', err.message) + } + }) +}) +``` + +`print` is a function created using the code from 2.js, but factored into its own function to save lines, here it is: + +```JavaScript +function print (protocol, conn) { + pull( + conn, + pull.map((v) => v.toString()), + pull.log() + ) +} +``` + +If everything was set correctly, you now should see the following after you run the script: + +```Bash +> node 3.js +node 1 is listening on: +/ip4/127.0.0.1/tcp/62620/ipfs/QmWpWmcVJkF6EpmAaVDauku8g1uFGuxPsGP35XZp9GYEqs +/ip4/192.168.2.156/tcp/62620/ipfs/QmWpWmcVJkF6EpmAaVDauku8g1uFGuxPsGP35XZp9GYEqs +node 2 is listening on: +/ip4/127.0.0.1/tcp/10000/ws/ipfs/QmWAQtWdzWXibgfyc7WRHhhv6MdqVKzXvyfSTnN2aAvixX +/ip4/127.0.0.1/tcp/62619/ipfs/QmWAQtWdzWXibgfyc7WRHhhv6MdqVKzXvyfSTnN2aAvixX +/ip4/192.168.2.156/tcp/62619/ipfs/QmWAQtWdzWXibgfyc7WRHhhv6MdqVKzXvyfSTnN2aAvixX +node 3 is listening on: +/ip4/127.0.0.1/tcp/20000/ws/ipfs/QmVq1PWh3VSDYdFqYMtqp4YQyXcrH27N7968tGdM1VQPj1 +node 3 failed to dial to node 1 with: No available transport to dial to +node 1 dialed to node 2 successfully +node 2 dialed to node 3 successfully +``` + +As expected, we created 3 nodes, node 1 with TCP, node 2 with TCP+WebSockets and node 3 with just WebSockets. node 1 -> node 2 and node 2 -> node 3 managed to dial correctly because they shared a common transport, however, node 3 -> node 1 failed because they didn't share any. + +## 4. How to create a new libp2p transport + +Today there are already 3 transports available, one in the works and plenty to come, you can find these at [interface-transport implementations](https://github.com/libp2p/interface-transport#modules-that-implement-the-interface) list. + +Adding more transports is done through the same way as you added TCP and WebSockets. Some transports might offer extra functionalities but for what is libp2p concern, as long as it follows the interface defined at the [spec](https://github.com/libp2p/interface-transport#api), it will be able to use it. + +If you decide to implement a transport yourself, please consider adding to the list so that others can use it as well. + +Hope this tutorial was useful. We are always looking to improve it, contributions are welcome! diff --git a/package.json b/package.json index f65862d25f..c9c39386ea 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "libp2p", "version": "0.10.0", - "description": "JavaScript Skeleton for libp2p bundles", + "description": "JavaScript base class for libp2p bundles", "main": "src/index.js", "scripts": { "test": "gulp test", @@ -79,4 +79,4 @@ "greenkeeperio-bot ", "mayerwin " ] -} \ No newline at end of file +} diff --git a/src/index.js b/src/index.js index b9a09dcf19..8a25710dd9 100644 --- a/src/index.js +++ b/src/index.js @@ -33,7 +33,7 @@ class Node extends EventEmitter { this.swarm = new Swarm(this.peerInfo, this.peerBook) // Attach stream multiplexers - if (this.modules.connection.muxer) { + if (this.modules.connection && this.modules.connection.muxer) { let muxers = this.modules.connection.muxer muxers = Array.isArray(muxers) ? muxers : [muxers] muxers.forEach((muxer) => this.swarm.connection.addStreamMuxer(muxer)) @@ -54,7 +54,7 @@ class Node extends EventEmitter { } // Attach crypto channels - if (this.modules.connection.crypto) { + if (this.modules.connection && this.modules.connection.crypto) { let cryptos = this.modules.connection.crypto cryptos = Array.isArray(cryptos) ? cryptos : [cryptos] cryptos.forEach((crypto) => {