Compare commits

..

18 Commits

Author SHA1 Message Date
ddca720b2a chore: travis cache 2019-11-15 16:03:29 +01:00
1ca2d87287 chore: address review 2019-11-15 15:48:14 +01:00
340edf53e3 chore: address review 2019-11-15 15:15:12 +01:00
4cc9736485 chore: use topology interface 2019-11-14 19:13:45 +01:00
cdfd02306f chore: address review 2019-11-14 15:34:35 +01:00
dcd127b7a5 refactor: pubsub subsystem 2019-11-14 15:34:35 +01:00
751bc00f1b chore: address review 2019-11-14 15:33:12 +01:00
ec5319a50e chore: apply suggestions from code review
Co-Authored-By: Jacob Heun <jacobheun@gmail.com>
2019-11-14 15:33:12 +01:00
dca4727112 feat: peer-store v0 2019-11-14 15:33:12 +01:00
695d71e44f chore: update it-length-prefixed (#476)
fix: decode.fromReader usage
2019-11-13 16:38:07 +01:00
0fc6668321 refactor: async identify and identify push (#473)
* chore: add missing dep

* feat: import from identify push branch

https://github.com/libp2p/js-libp2p-identify/tree/feat/identify-push

* feat: add the connection to stream handlers

* refactor: identify to async/await

* chore: fix lint

* test: add identify tests

* refactor: add identify to the dialer flow

* feat: connect identify to the registrar

* fix: resolve review feedback

* fix: perform identify push when our protocols change
2019-11-07 12:11:50 +01:00
d7c38d3fec feat: registrar (#471)
* feat: peer-store v0

* feat: registrar

* chore: apply suggestions from code review

Co-Authored-By: Jacob Heun <jacobheun@gmail.com>

* chore: address review

* chore: support multiple conns

* chore: address review

* fix: no remote peer from topology on disconnect
2019-11-06 15:47:11 +01:00
53bcd2d745 feat: peer store (#470)
* feat: peer-store v0

* chore: apply suggestions from code review

Co-Authored-By: Jacob Heun <jacobheun@gmail.com>
2019-11-06 15:11:13 +01:00
fba058370a refactor: crypto and pnet (#469)
* feat: add initial plaintext 2 module

* refactor: initial refactor of pnet

* chore: fix lint

* fix: update plaintext api usage

* test: use plaintext for test crypto

* chore: update deps

test: update dialer suite scope

* feat: add connection protection to the upgrader

* refactor: cleanup and lint fix

* chore: remove unncessary transforms

* chore: temporarily disable bundlesize

* chore: add missing dep

* fix: use it-handshake to prevent overreading

* chore(fix): PR feedback updates

* chore: apply suggestions from code review

Co-Authored-By: Vasco Santos <vasco.santos@moxy.studio>
2019-11-04 14:05:58 +01:00
a23d4d23cb refactor(async): add dialer and upgrader (#462)
* chore(deps): update connection and multistream

* feat: add basic dial support for addresses and peers

* test: automatically require all node test files

* fix: dont catch and log in the wrong place

* test: add direct spec test

fix: improve dial error consistency

* feat: add dial timeouts and concurrency

Queue timeouts will result in aborts of the dials

* chore: fix linting

* test: verify dialer defaults

* feat: add initial upgrader

* fix: add more test coverage and fix bugs

* feat: libp2p creates the upgrader

* feat: hook up handle to the upgrader

* feat: hook up the dialer to libp2p

test: add node dialer libp2p tests

* feat: add connection listeners to upgrader

* feat: emit connect and disconnect events

* chore: use libp2p-interfaces

* fix: address review feedback

* fix: correct import

* refactor: dedupe connection creation code
2019-10-21 16:53:58 +02:00
6ecc9b80c3 docs: add stream wrapping example (#466)
* docs: add duplex wrapping example

docs: add iterable types from @alanshaw's gist

* docs(fix): add feedback fix

Co-Authored-By: Vasco Santos <vasco.santos@moxy.studio>

* docs: clean up based on feedback
2019-10-21 13:36:05 +02:00
cd97abfcc3 refactor(async): update transports subsystem (#461)
* test: remove all tests for a clean slate

The refactor will require a large number of updates to the tests. In order
to ensure we have done a decent deduplication, and have a cleaner suite of tests
we've removed all tests. This will also allow us to more easily see tests
for the refactored systems.

We have a record of the latest test suites in master, so we are not losing any history.

* chore: update tcp and websockets
* chore: remove other transports until they are converted
* chore: use mafmt and multiaddr async versions
* chore: add and fix dependencies
* chore: clean up travis file
* feat: add new transport manager
* docs: add constructor jsdocs
* refactor(config): check that transports exist
This also removes the other logic, it can be added when those subsystems are refactored

* chore(deps): use async peer-id and peer-info
* feat: wire up the transport manager with libp2p
* chore: remove superstruct dep
2019-10-02 13:31:28 +02:00
c1da30bc74 Add streaming iterables guide (#459)
* docs: add streaming iterables guide placeholder

* chore: move peer discovery readme to doc fold:wqer

* docs: add link to async refactor issue
2019-09-30 18:20:25 +02:00
141 changed files with 7800 additions and 7488 deletions

View File

@ -7,7 +7,7 @@ const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const WebSockets = require('libp2p-websockets')
const Muxer = require('libp2p-mplex')
const Crypto = require('libp2p-secio')
const Crypto = require('./src/insecure/plaintext')
const pipe = require('it-pipe')
let libp2p
@ -23,15 +23,6 @@ const before = async () => {
transport: [WebSockets],
streamMuxer: [Muxer],
connEncryption: [Crypto]
},
config: {
relay: {
enabled: true,
hop: {
enabled: true,
active: false
}
}
}
})
// Add the echo protocol

View File

@ -20,9 +20,8 @@ jobs:
include:
- stage: check
script:
- npx aegir build --bundlesize
# Remove pull libs once ping is async
- npx aegir dep-check -- -i pull-handshake -i pull-stream
# - npx aegir build --bundlesize
- npx aegir dep-check -- -i wrtc -i electron-webrtc
- npm run lint
- stage: test

View File

@ -1,60 +1,3 @@
<a name="0.27.0-pre.2"></a>
# [0.27.0-pre.2](https://github.com/libp2p/js-libp2p/compare/v0.27.0-pre.1...v0.27.0-pre.2) (2020-01-07)
### Bug Fixes
* conn mngr min/max connection values ([#528](https://github.com/libp2p/js-libp2p/issues/528)) ([a1717da](https://github.com/libp2p/js-libp2p/commit/a1717da))
* make dialer configurable ([#521](https://github.com/libp2p/js-libp2p/issues/521)) ([24c3ce6](https://github.com/libp2p/js-libp2p/commit/24c3ce6))
* upgrader should not need muxers ([#517](https://github.com/libp2p/js-libp2p/issues/517)) ([56a1825](https://github.com/libp2p/js-libp2p/commit/56a1825))
### Features
* add libp2p.connections getter ([#522](https://github.com/libp2p/js-libp2p/issues/522)) ([6ca19c5](https://github.com/libp2p/js-libp2p/commit/6ca19c5))
* allow transport options to be passed on creation ([#524](https://github.com/libp2p/js-libp2p/issues/524)) ([0d4b2bd](https://github.com/libp2p/js-libp2p/commit/0d4b2bd))
<a name="0.27.0-pre.1"></a>
# [0.27.0-pre.1](https://github.com/libp2p/js-libp2p/compare/v0.27.0-pre.0...v0.27.0-pre.1) (2019-12-15)
### Features
* coalescing dial support ([#518](https://github.com/libp2p/js-libp2p/issues/518)) ([4a871bb](https://github.com/libp2p/js-libp2p/commit/4a871bb))
<a name="0.27.0-pre.0"></a>
# [0.27.0-pre.0](https://github.com/libp2p/js-libp2p/compare/v0.26.2...v0.27.0-pre.0) (2019-12-12)
### Bug Fixes
* clean up peer discovery flow ([#494](https://github.com/libp2p/js-libp2p/issues/494)) ([f3eb1f1](https://github.com/libp2p/js-libp2p/commit/f3eb1f1))
* clean up pending dials abort per feedback ([7c3371b](https://github.com/libp2p/js-libp2p/commit/7c3371b))
* correct release readme ([c4bc00b](https://github.com/libp2p/js-libp2p/commit/c4bc00b))
* examples readme typos ([#481](https://github.com/libp2p/js-libp2p/issues/481)) ([35ac02d](https://github.com/libp2p/js-libp2p/commit/35ac02d))
* performance bottleneck in stat.js ([#463](https://github.com/libp2p/js-libp2p/issues/463)) ([93a1e42](https://github.com/libp2p/js-libp2p/commit/93a1e42))
* release tokens as soon as they are available ([43440aa](https://github.com/libp2p/js-libp2p/commit/43440aa))
* replace peerInfo addresses with listen addresses ([#485](https://github.com/libp2p/js-libp2p/issues/485)) ([acbbc0f](https://github.com/libp2p/js-libp2p/commit/acbbc0f))
* token release logic ([1838a64](https://github.com/libp2p/js-libp2p/commit/1838a64))
### Features
* abort all pending dials on stop ([754fbc2](https://github.com/libp2p/js-libp2p/commit/754fbc2))
* add early token recycling in ([24c6037](https://github.com/libp2p/js-libp2p/commit/24c6037))
* add token based dialer ([f8540fa](https://github.com/libp2p/js-libp2p/commit/f8540fa))
* discovery modules ([#486](https://github.com/libp2p/js-libp2p/issues/486)) ([997ee16](https://github.com/libp2p/js-libp2p/commit/997ee16))
* discovery modules from transports should be added ([#510](https://github.com/libp2p/js-libp2p/issues/510)) ([af96dcc](https://github.com/libp2p/js-libp2p/commit/af96dcc))
* peer store ([#470](https://github.com/libp2p/js-libp2p/issues/470)) ([f3e276e](https://github.com/libp2p/js-libp2p/commit/f3e276e))
* registrar ([#471](https://github.com/libp2p/js-libp2p/issues/471)) ([797d8f0](https://github.com/libp2p/js-libp2p/commit/797d8f0))
* support peer-id instances in peer store operations ([#491](https://github.com/libp2p/js-libp2p/issues/491)) ([11ed6bd](https://github.com/libp2p/js-libp2p/commit/11ed6bd))
<a name="0.26.2"></a>
## [0.26.2](https://github.com/libp2p/js-libp2p/compare/v0.26.1...v0.26.2) (2019-09-24)

544
README.md
View File

@ -45,11 +45,12 @@ We've come a long way, but this project is still in Alpha, lots of development i
## Table of Contents
- [Background](#background)
- [Install](#install)
- [Bundles](#bundles)
- [Usage](#usage)
- [Configuration](#configuration)
- [Install](#install)
- [Usage](#usage)
- [API](#api)
- [Tutorials and Examples](#tutorials-and-examples)
- [Events](#events)
- [Development](#development)
- [Tests](#tests)
- [Packages](#packages)
@ -58,7 +59,7 @@ We've come a long way, but this project is still in Alpha, lots of development i
## Background
libp2p is the product of a long and arduous quest to understand the evolution of the Internet networking stack. In order to build P2P applications, devs have long had to make custom ad-hoc solutions to fit their needs, sometimes making some hard assumptions about their runtimes and the state of the network at the time of their development. Today, looking back more than 20 years, we see a clear pattern in the types of mechanisms built around the Internet Protocol, IP, which can be found throughout many layers of the OSI layer system, libp2p distils these mechanisms into flat categories and defines clear interfaces that once exposed, enable other protocols and applications to use and swap them, enabling upgradability and adaptability for the runtime, without breaking the API.
libp2p is the product of a long and arduous quest to understand the evolution of the Internet networking stack. In order to build P2P applications, devs have long had to made custom ad-hoc solutions to fit their needs, sometimes making some hard assumptions about their runtimes and the state of the network at the time of their development. Today, looking back more than 20 years, we see a clear pattern in the types of mechanisms built around the Internet Protocol, IP, which can be found throughout many layers of the OSI layer system, libp2p distils these mechanisms into flat categories and defines clear interfaces that once exposed, enable other protocols and applications to use and swap them, enabling upgradability and adaptability for the runtime, without breaking the API.
We are in the process of writing better documentation, blog posts, tutorials and a formal specification. Today you can find:
@ -73,33 +74,464 @@ We are in the process of writing better documentation, blog posts, tutorials and
To sum up, libp2p is a "network stack" -- a protocol suite -- that cleanly separates concerns, and enables sophisticated applications to only use the protocols they absolutely need, without giving up interoperability and upgradeability. libp2p grew out of IPFS, but it is built so that lots of people can use it, for lots of different projects.
## Bundles
With its modular nature, libp2p can be found being used in different projects with different sets of features, while preserving the same top level API. `js-libp2p` is only a skeleton and should not be installed directly, if you are looking for a prebundled libp2p stack, please check:
- [libp2p-ipfs-nodejs](https://github.com/ipfs/js-ipfs/tree/master/src/core/runtime/libp2p-nodejs.js) - The libp2p build used by js-ipfs when run in Node.js
- [libp2p-ipfs-browser](https://github.com/ipfs/js-ipfs/tree/master/src/core/runtime/libp2p-browser.js) - The libp2p build used by js-ipfs when run in a Browser (that supports WebRTC)
If you have developed a libp2p bundle, please consider submitting it to this list so that it can be found easily by the users of libp2p.
## Install
Again, as noted above, this module is only a skeleton and should not be used directly other than libp2p bundle implementors that want to extend its code.
```sh
npm install libp2p
npm install --save libp2p
```
## Usage
### Configuration
**IMPORTANT NOTE**: We are currently on the way of migrating all our `libp2p` modules to use `async await` and `async iterators`, instead of callbacks and `pull-streams`. As a consequence, when you start a new libp2p project, we must check which versions of the modules you should use. For now, it is required to use the modules using callbacks with `libp2p`, while we are working on getting the remaining modules ready for a full migration. For more details, you can have a look at [libp2p/js-libp2p#266](https://github.com/libp2p/js-libp2p/issues/266).
For all the information on how you can configure libp2p see [CONFIGURATION.md](./doc/CONFIGURATION.md).
### [Tutorials and Examples](/examples)
You can find multiple examples on the [examples folder](/examples) that will guide you through using libp2p for several scenarios.
### Creating your own libp2p bundle
The libp2p module acts as a glue for every libp2p module that you can use to create your own libp2p bundle. Creating your own libp2p bundle gives you a lot of freedom when it comes to customize it with features and default setup. We recommend creating your own libp2p bundle for the app you are developing that takes in account your needs (e.g. for a browser working version of libp2p that acts as the network layer of IPFS, we have a built one that leverages the Browser transports).
**Example:**
```JavaScript
// Creating a bundle that adds:
// transport: websockets + tcp
// stream-muxing: spdy & mplex
// crypto-channel: secio
// discovery: multicast-dns
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const WS = require('libp2p-websockets')
const SPDY = require('libp2p-spdy')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const MulticastDNS = require('libp2p-mdns')
const DHT = require('libp2p-kad-dht')
const GossipSub = require('libp2p-gossipsub')
const defaultsDeep = require('@nodeutils/defaults-deep')
const Protector = require('libp2p-pnet')
const DelegatedPeerRouter = require('libp2p-delegated-peer-routing')
const DelegatedContentRouter = require('libp2p-delegated-content-routing')
class Node extends Libp2p {
constructor (_options) {
const peerInfo = _options.peerInfo
const defaults = {
// The libp2p modules for this libp2p bundle
modules: {
transport: [
TCP,
new WS() // It can take instances too!
],
streamMuxer: [
SPDY,
MPLEX
],
connEncryption: [
SECIO
],
/** Encryption for private networks. Needs additional private key to work **/
// connProtector: new Protector(/*protector specific opts*/),
/** Enable custom content routers, such as delegated routing **/
// contentRouting: [
// new DelegatedContentRouter(peerInfo.id)
// ],
/** Enable custom peer routers, such as delegated routing **/
// peerRouting: [
// new DelegatedPeerRouter()
// ],
peerDiscovery: [
MulticastDNS
],
dht: DHT, // DHT enables PeerRouting, ContentRouting and DHT itself components
pubsub: GossipSub
},
// libp2p config options (typically found on a config.json)
config: { // The config object is the part of the config that can go into a file, config.json.
peerDiscovery: {
autoDial: true, // Auto connect to discovered peers (limited by ConnectionManager minPeers)
mdns: { // mdns options
interval: 1000, // ms
enabled: true
},
webrtcStar: { // webrtc-star options
interval: 1000, // ms
enabled: false
}
// .. other discovery module options.
},
relay: { // Circuit Relay options
enabled: true,
hop: {
enabled: false,
active: false
}
},
dht: {
kBucketSize: 20,
enabled: true,
randomWalk: {
enabled: true, // Allows to disable discovery (enabled by default)
interval: 300e3,
timeout: 10e3
}
},
pubsub: {
enabled: true,
emitSelf: true, // whether the node should emit to self on publish, in the event of the topic being subscribed
signMessages: true, // if messages should be signed
strictSigning: true // if message signing should be required
}
}
}
// overload any defaults of your bundle using https://github.com/nodeutils/defaults-deep
super(defaultsDeep(_options, defaults))
}
}
// Now all the nodes you create, will have TCP, WebSockets, SPDY, MPLEX, SECIO and MulticastDNS support.
```
### API
The specification is available on [API.md](./doc/API.md).
**IMPORTANT NOTE**: All the methods listed in the API section that take a callback are also now Promisified. Libp2p is migrating away from callbacks to async/await, and in a future release (that will be announced in advance), callback support will be removed entirely. You can follow progress of the async/await endeavor at https://github.com/ipfs/js-ipfs/issues/1670.
### Tutorials and Examples
#### Create a Node - `Libp2p.create(options)`
You can find multiple examples on the [examples folder](./examples) that will guide you through using libp2p for several scenarios.
> Behaves exactly like `new Libp2p(options)`, but doesn't require a PeerInfo. One will be generated instead
```js
const { create } = require('libp2p')
const libp2p = await create(options)
await libp2p.start()
```
- `options`: Object of libp2p configuration options
#### Create a Node alternative - `new Libp2p(options)`
> Creates an instance of Libp2p with a custom `PeerInfo` provided via `options.peerInfo`.
Required keys in the `options` object:
- `peerInfo`: instance of [PeerInfo][] that contains the [PeerId][], Keys and [multiaddrs][multiaddr] of the libp2p Node.
- `modules.transport`: An array that must include at least 1 transport, such as `libp2p-tcp`.
#### `libp2p.start(callback)`
> Start the libp2p Node.
`callback` following signature `function (err) {}`, where `err` is an Error in case starting the node fails.
#### `libp2p.stop(callback)`
> Stop the libp2p Node.
`callback` following signature `function (err) {}`, where `err` is an Error in case stopping the node fails.
#### `libp2p.dial(peer, callback)`
> Dials to another peer in the network, establishes the connection.
- `peer`: can be an instance of [PeerInfo][], [PeerId][], [multiaddr][], or a multiaddr string
- `callback` following signature `function (err, conn) {}`, where `err` is an Error in of failure to dial the connection and `conn` is a [Connection][] instance in case of a protocol selected, if not it is undefined.
#### `libp2p.dialProtocol(peer, protocol, callback)`
> Dials to another peer in the network and selects a protocol to talk with that peer.
- `peer`: can be an instance of [PeerInfo][], [PeerId][], [multiaddr][], or a multiaddr string
- `protocol`: String that defines the protocol (e.g '/ipfs/bitswap/1.1.0')
- `callback`: Function with signature `function (err, conn) {}`, where `conn` is a [Connection](https://github.com/libp2p/interface-connection) object
`callback` following signature `function (err, conn) {}`, where `err` is an Error in of failure to dial the connection and `conn` is a [Connection][] instance in case of a protocol selected, if not it is undefined.
#### `libp2p.dialFSM(peer, protocol, callback)`
> Behaves like `.dial` and `.dialProtocol` but calls back with a Connection State Machine
- `peer`: can be an instance of [PeerInfo][], [PeerId][], [multiaddr][], or a multiaddr string
- `protocol`: an optional String that defines the protocol (e.g '/ipfs/bitswap/1.1.0')
- `callback`: following signature `function (err, connFSM) {}`, where `connFSM` is a [Connection State Machine](https://github.com/libp2p/js-libp2p-switch#connection-state-machine)
#### `libp2p.hangUp(peer, callback)`
> Closes an open connection with a peer, graciously.
- `peer`: can be an instance of [PeerInfo][], [PeerId][] or [multiaddr][]
`callback` following signature `function (err) {}`, where `err` is an Error in case stopping the node fails.
#### `libp2p.peerRouting.findPeer(id, options, callback)`
> Looks up for multiaddrs of a peer in the DHT
- `id`: instance of [PeerId][]
- `options`: object of options
- `options.maxTimeout`: Number milliseconds
#### `libp2p.contentRouting.findProviders(key, options, callback)`
- `key`: Buffer
- `options`: object of options
- `options.maxTimeout`: Number milliseconds
- `options.maxNumProviders` maximum number of providers to find
#### `libp2p.contentRouting.provide(key, callback)`
- `key`: Buffer
#### `libp2p.handle(protocol, handlerFunc [, matchFunc])`
> Handle new protocol
- `protocol`: String that defines the protocol (e.g '/ipfs/bitswap/1.1.0')
- `handlerFunc`: following signature `function (protocol, conn) {}`, where `conn` is a [Connection](https://github.com/libp2p/interface-connection) object
- `matchFunc`: Function for matching on protocol (exact matching, semver, etc). Default to exact match.
#### `libp2p.unhandle(protocol)`
> Stop handling protocol
- `protocol`: String that defines the protocol (e.g '/ipfs/bitswap/1.1.0')
#### Events
##### `libp2p.on('start', () => {})`
> Libp2p has started, along with all its services.
##### `libp2p.on('stop', () => {})`
> Libp2p has stopped, along with all its services.
##### `libp2p.on('error', (err) => {})`
> An error has occurred
- `err`: instance of `Error`
##### `libp2p.on('peer:discovery', (peer) => {})`
> Peer has been discovered.
If `autoDial` is `true`, applications should **not** attempt to connect to the peer
unless they are performing a specific action. See [peer discovery and auto dial](./doc/PEER_DISCOVERY.md) for more information.
- `peer`: instance of [PeerInfo][]
##### `libp2p.on('peer:connect', (peer) => {})`
> We have a new muxed connection to a peer
- `peer`: instance of [PeerInfo][]
##### `libp2p.on('peer:disconnect', (peer) => {})`
> We have closed a connection to a peer
- `peer`: instance of [PeerInfo][]
##### `libp2p.on('connection:start', (peer) => {})`
> We created a new connection to a peer
- `peer`: instance of [PeerInfo][]
##### `libp2p.on('connection:end', (peer) => {})`
> We closed a connection to a peer
- `peer`: instance of [PeerInfo][]
#### `libp2p.isStarted()`
> Check if libp2p is started
#### `libp2p.ping(peer [, options], callback)`
> Ping a node in the network
#### `libp2p.peerBook`
> PeerBook instance of the node
#### `libp2p.peerInfo`
> PeerInfo instance of the node
#### `libp2p.pubsub`
> Same API as IPFS PubSub, defined in the [CORE API Spec](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/PUBSUB.md). Just replace `ipfs` by `libp2p` and you are golden.
---------------------
`DHT methods also exposed for the time being`
#### `libp2p.dht.put(key, value, callback)`
- `key`: Buffer
- `value`: Buffer
#### `libp2p.dht.get(key, options, callback)`
- `key`: Buffer
- `options`: object of options
- `options.maxTimeout`: Number milliseconds
#### `libp2p.dht.getMany(key, nVals, options, callback)`
- `key`: Buffer
- `nVals`: Number
- `options`: object of options
- `options.maxTimeout`: Number milliseconds
[PeerInfo]: https://github.com/libp2p/js-peer-info
[PeerId]: https://github.com/libp2p/js-peer-id
[PeerBook]: https://github.com/libp2p/js-peer-book
[multiaddr]: https://github.com/multiformats/js-multiaddr
[Connection]: https://github.com/libp2p/interface-connection
-------
### Switch Stats API
##### `libp2p.stats.emit('update')`
Every time any stat value changes, this object emits an `update` event.
#### Global stats
##### `libp2p.stats.global.snapshot`
Should return a stats snapshot, which is an object containing the following keys and respective values:
- dataSent: amount of bytes sent, [Big](https://github.com/MikeMcl/big.js#readme) number
- dataReceived: amount of bytes received, [Big](https://github.com/MikeMcl/big.js#readme) number
##### `libp2p.stats.global.movingAverages`
Returns an object containing the following keys:
- dataSent
- dataReceived
Each one of them contains an object that has a key for each interval (`60000`, `300000` and `900000` miliseconds).
Each one of these values is [an exponential moving-average instance](https://github.com/pgte/moving-average#readme).
#### Per-transport stats
##### `libp2p.stats.transports()`
Returns an array containing the tags (string) for each observed transport.
##### `libp2p.stats.forTransport(transportTag).snapshot`
Should return a stats snapshot, which is an object containing the following keys and respective values:
- dataSent: amount of bytes sent, [Big](https://github.com/MikeMcl/big.js#readme) number
- dataReceived: amount of bytes received, [Big](https://github.com/MikeMcl/big.js#readme) number
##### `libp2p.stats.forTransport(transportTag).movingAverages`
Returns an object containing the following keys:
dataSent
dataReceived
Each one of them contains an object that has a key for each interval (`60000`, `300000` and `900000` miliseconds).
Each one of these values is [an exponential moving-average instance](https://github.com/pgte/moving-average#readme).
#### Per-protocol stats
##### `libp2p.stats.protocols()`
Returns an array containing the tags (string) for each observed protocol.
##### `libp2p.stats.forProtocol(protocolTag).snapshot`
Should return a stats snapshot, which is an object containing the following keys and respective values:
- dataSent: amount of bytes sent, [Big](https://github.com/MikeMcl/big.js#readme) number
- dataReceived: amount of bytes received, [Big](https://github.com/MikeMcl/big.js#readme) number
##### `libp2p.stats.forProtocol(protocolTag).movingAverages`
Returns an object containing the following keys:
- dataSent
- dataReceived
Each one of them contains an object that has a key for each interval (`60000`, `300000` and `900000` miliseconds).
Each one of these values is [an exponential moving-average instance](https://github.com/pgte/moving-average#readme).
#### Per-peer stats
##### `libp2p.stats.peers()`
Returns an array containing the peerIDs (B58-encoded string) for each observed peer.
##### `libp2p.stats.forPeer(peerId:String).snapshot`
Should return a stats snapshot, which is an object containing the following keys and respective values:
- dataSent: amount of bytes sent, [Big](https://github.com/MikeMcl/big.js#readme) number
- dataReceived: amount of bytes received, [Big](https://github.com/MikeMcl/big.js#readme) number
##### `libp2p.stats.forPeer(peerId:String).movingAverages`
Returns an object containing the following keys:
- dataSent
- dataReceived
Each one of them contains an object that has a key for each interval (`60000`, `300000` and `900000` miliseconds).
Each one of these values is [an exponential moving-average instance](https://github.com/pgte/moving-average#readme).
#### Stats update interval
Stats are not updated in real-time. Instead, measurements are buffered and stats are updated at an interval. The maximum interval can be defined through the `Switch` constructor option `stats.computeThrottleTimeout`, defined in miliseconds.
### Private Networks
#### Enforcement
Libp2p provides support for connection protection, such as for private networks. You can enforce network protection by setting the environment variable `LIBP2P_FORCE_PNET=1`. When this variable is on, if no protector is set via `options.connProtector`, Libp2p will throw an error upon creation.
#### Protectors
Some available network protectors:
* [libp2p-pnet](https://github.com/libp2p/js-libp2p-pnet)
## Development
**Clone and install dependencies:**
```sh
> git clone https://github.com/libp2p/js-libp2p.git
> cd js-libp2p
> git clone https://github.com/ipfs/js-ipfs.git
> cd js-ipfs
> npm install
```
@ -126,50 +558,50 @@ List of packages currently in existence for libp2p
| Package | Version | Deps | CI | Coverage | Lead Maintainer |
| ---------|---------|---------|---------|---------|--------- |
| **libp2p** |
| [`libp2p`](//github.com/libp2p/js-libp2p) | [![npm](https://img.shields.io/npm/v/libp2p.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| [`libp2p-daemon`](//github.com/libp2p/js-libp2p-daemon) | [![npm](https://img.shields.io/npm/v/libp2p-daemon.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-daemon/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-daemon.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-daemon) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-daemon.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-daemon) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-daemon/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-daemon) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| [`libp2p-daemon-client`](//github.com/libp2p/js-libp2p-daemon-client) | [![npm](https://img.shields.io/npm/v/libp2p-daemon-client.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-daemon-client/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-daemon-client.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-daemon-client) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-daemon-client.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-daemon-client) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-daemon-client/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-daemon-client) | [Vasco Santos](mailto:santos.vasco10@gmail.com) |
| [`libp2p-interfaces`](//github.com/libp2p/js-interfaces) | [![npm](https://img.shields.io/npm/v/libp2p-interfaces.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-interfaces/releases) | [![Deps](https://david-dm.org/libp2p/js-interfaces.svg?style=flat-square)](https://david-dm.org/libp2p/js-interfaces) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-interfaces.svg?branch=master)](https://travis-ci.com/libp2p/js-interfaces) | [![codecov](https://codecov.io/gh/libp2p/js-interfaces/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-interfaces) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| [`interop-libp2p`](//github.com/libp2p/interop) | [![npm](https://img.shields.io/npm/v/interop-libp2p.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/interop/releases) | [![Deps](https://david-dm.org/libp2p/interop.svg?style=flat-square)](https://david-dm.org/libp2p/interop) | [![Travis CI](https://flat.badgen.net/travis/libp2p/interop.svg?branch=master)](https://travis-ci.com/libp2p/interop) | [![codecov](https://codecov.io/gh/libp2p/interop/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/interop) | [Vasco Santos](mailto:santos.vasco10@gmail.com) |
| **transports** |
| [`libp2p-tcp`](//github.com/libp2p/js-libp2p-tcp) | [![npm](https://img.shields.io/npm/v/libp2p-tcp.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-tcp/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-tcp.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-tcp) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-tcp.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-tcp) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-tcp/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-tcp) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| [`libp2p-utp`](//github.com/libp2p/js-libp2p-utp) | [![npm](https://img.shields.io/npm/v/libp2p-utp.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-utp/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-utp.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-utp) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-utp.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-utp) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-utp/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-utp) | [Vasco Santos](mailto:santos.vasco10@gmail.com) |
| [`libp2p-webrtc-direct`](//github.com/libp2p/js-libp2p-webrtc-direct) | [![npm](https://img.shields.io/npm/v/libp2p-webrtc-direct.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-webrtc-direct/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-webrtc-direct.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-webrtc-direct) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-webrtc-direct.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-webrtc-direct) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-webrtc-direct/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-webrtc-direct) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
| [`libp2p-webrtc-star`](//github.com/libp2p/js-libp2p-webrtc-star) | [![npm](https://img.shields.io/npm/v/libp2p-webrtc-star.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-webrtc-star/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-webrtc-star.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-webrtc-star) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-webrtc-star.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-webrtc-star) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-webrtc-star/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-webrtc-star) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
| [`libp2p-websockets`](//github.com/libp2p/js-libp2p-websockets) | [![npm](https://img.shields.io/npm/v/libp2p-websockets.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-websockets/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-websockets.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-websockets) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-websockets.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-websockets) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-websockets/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-websockets) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| [`libp2p-websocket-star`](//github.com/libp2p/js-libp2p-websocket-star) | [![npm](https://img.shields.io/npm/v/libp2p-websocket-star.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-websocket-star/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-websocket-star.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-websocket-star) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-websocket-star.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-websocket-star) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-websocket-star/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-websocket-star) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| **secure channels** |
| [`libp2p-secio`](//github.com/libp2p/js-libp2p-secio) | [![npm](https://img.shields.io/npm/v/libp2p-secio.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-secio/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-secio.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-secio) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-secio.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-secio) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-secio/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-secio) | [Friedel Ziegelmayer](mailto:dignifiedquire@gmail.com) |
| **stream multiplexers** |
| [`libp2p-mplex`](//github.com/libp2p/js-libp2p-mplex) | [![npm](https://img.shields.io/npm/v/libp2p-mplex.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-mplex/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-mplex.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-mplex) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-mplex.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-mplex) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-mplex/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-mplex) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
| [`libp2p-spdy`](//github.com/libp2p/js-libp2p-spdy) | [![npm](https://img.shields.io/npm/v/libp2p-spdy.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-spdy/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-spdy.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-spdy) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-spdy.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-spdy) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-spdy/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-spdy) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| **peer discovery** |
| [`libp2p-bootstrap`](//github.com/libp2p/js-libp2p-bootstrap) | [![npm](https://img.shields.io/npm/v/libp2p-bootstrap.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-bootstrap/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-bootstrap.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-bootstrap) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-bootstrap.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-bootstrap) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-bootstrap/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-bootstrap) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
| [`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.svg?branch=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) |
| [`libp2p-mdns`](//github.com/libp2p/js-libp2p-mdns) | [![npm](https://img.shields.io/npm/v/libp2p-mdns.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-mdns/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-mdns.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-mdns) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-mdns.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-mdns) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-mdns/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-mdns) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| [`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.svg?branch=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) |
| [`libp2p-webrtc-star`](//github.com/libp2p/js-libp2p-webrtc-star) | [![npm](https://img.shields.io/npm/v/libp2p-webrtc-star.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-webrtc-star/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-webrtc-star.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-webrtc-star) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-webrtc-star.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-webrtc-star) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-webrtc-star/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-webrtc-star) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
| [`libp2p-websocket-star`](//github.com/libp2p/js-libp2p-websocket-star) | [![npm](https://img.shields.io/npm/v/libp2p-websocket-star.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-websocket-star/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-websocket-star.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-websocket-star) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-websocket-star.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-websocket-star) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-websocket-star/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-websocket-star) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| **content routing** |
| [`libp2p-delegated-content-routing`](//github.com/libp2p/js-libp2p-delegated-content-routing) | [![npm](https://img.shields.io/npm/v/libp2p-delegated-content-routing.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-delegated-content-routing/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-delegated-content-routing.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-delegated-content-routing) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-delegated-content-routing.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-delegated-content-routing) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-delegated-content-routing/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-delegated-content-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.svg?branch=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) |
| **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.svg?branch=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.svg?branch=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) |
| **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.svg?branch=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.svg?branch=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) |
| **data types** |
| [`peer-id`](//github.com/libp2p/js-peer-id) | [![npm](https://img.shields.io/npm/v/peer-id.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-peer-id/releases) | [![Deps](https://david-dm.org/libp2p/js-peer-id.svg?style=flat-square)](https://david-dm.org/libp2p/js-peer-id) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-peer-id.svg?branch=master)](https://travis-ci.com/libp2p/js-peer-id) | [![codecov](https://codecov.io/gh/libp2p/js-peer-id/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-peer-id) | [Vasco Santos](mailto:santos.vasco10@gmail.com) |
| [`peer-info`](//github.com/libp2p/js-peer-info) | [![npm](https://img.shields.io/npm/v/peer-info.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-peer-info/releases) | [![Deps](https://david-dm.org/libp2p/js-peer-info.svg?style=flat-square)](https://david-dm.org/libp2p/js-peer-info) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-peer-info.svg?branch=master)](https://travis-ci.com/libp2p/js-peer-info) | [![codecov](https://codecov.io/gh/libp2p/js-peer-info/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-peer-info) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
| **pubsub** |
| [`libp2p-pubsub`](//github.com/libp2p/js-libp2p-pubsub) | [![npm](https://img.shields.io/npm/v/libp2p-pubsub.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-pubsub/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-pubsub.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-pubsub) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-pubsub.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-pubsub) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-pubsub/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-pubsub) | [Vasco Santos](mailto:santos.vasco10@gmail.com) |
| [`libp2p-floodsub`](//github.com/libp2p/js-libp2p-floodsub) | [![npm](https://img.shields.io/npm/v/libp2p-floodsub.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-floodsub/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-floodsub.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-floodsub) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-floodsub.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-floodsub) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-floodsub/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-floodsub) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
| [`libp2p-gossipsub`](//github.com/ChainSafe/gossipsub-js) | [![npm](https://img.shields.io/npm/v/libp2p-gossipsub.svg?maxAge=86400&style=flat-square)](//github.com/ChainSafe/gossipsub-js/releases) | [![Deps](https://david-dm.org/ChainSafe/gossipsub-js.svg?style=flat-square)](https://david-dm.org/ChainSafe/gossipsub-js) | [![Travis CI](https://flat.badgen.net/travis/ChainSafe/gossipsub-js.svg?branch=master)](https://travis-ci.com/ChainSafe/gossipsub-js) | [![codecov](https://codecov.io/gh/ChainSafe/gossipsub-js/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ChainSafe/gossipsub-js) | [Cayman Nava](mailto:caymannava@gmail.com) |
| **extensions** |
| [`libp2p-nat-mgnr`](//github.com/libp2p/js-libp2p-nat-mgnr) | [![npm](https://img.shields.io/npm/v/libp2p-nat-mgnr.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-nat-mgnr/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-nat-mgnr.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-nat-mgnr) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-nat-mgnr.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-nat-mgnr) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-nat-mgnr/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-nat-mgnr) | N/A |
| [`libp2p-utils`](//github.com/libp2p/js-libp2p-utils) | [![npm](https://img.shields.io/npm/v/libp2p-utils.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-utils/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-utils.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-utils) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-utils.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-utils) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-utils/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-utils) | [Vasco Santos](mailto:santos.vasco10@gmail.com) |
| **Libp2p** |
| [`interface-libp2p`](//github.com/libp2p/interface-libp2p) | [![npm](https://img.shields.io/npm/v/interface-libp2p.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/interface-libp2p/releases) | [![Deps](https://david-dm.org/libp2p/interface-libp2p.svg?style=flat-square)](https://david-dm.org/libp2p/interface-libp2p) | [![Travis CI](https://travis-ci.com/libp2p/interface-libp2p.svg?branch=master)](https://travis-ci.com/libp2p/interface-libp2p) | [![codecov](https://codecov.io/gh/libp2p/interface-libp2p/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/interface-libp2p) | N/A |
| [`libp2p`](//github.com/libp2p/js-libp2p) | [![npm](https://img.shields.io/npm/v/libp2p.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p) | [![Travis CI](https://travis-ci.com/libp2p/js-libp2p.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| **Connection** |
| [`interface-connection`](//github.com/libp2p/interface-connection) | [![npm](https://img.shields.io/npm/v/interface-connection.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/interface-connection/releases) | [![Deps](https://david-dm.org/libp2p/interface-connection.svg?style=flat-square)](https://david-dm.org/libp2p/interface-connection) | [![Travis CI](https://travis-ci.com/libp2p/interface-connection.svg?branch=master)](https://travis-ci.com/libp2p/interface-connection) | [![codecov](https://codecov.io/gh/libp2p/interface-connection/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/interface-connection) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| **Transport** |
| [`interface-transport`](//github.com/libp2p/interface-transport) | [![npm](https://img.shields.io/npm/v/interface-transport.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/interface-transport/releases) | [![Deps](https://david-dm.org/libp2p/interface-transport.svg?style=flat-square)](https://david-dm.org/libp2p/interface-transport) | [![Travis CI](https://travis-ci.com/libp2p/interface-transport.svg?branch=master)](https://travis-ci.com/libp2p/interface-transport) | [![codecov](https://codecov.io/gh/libp2p/interface-transport/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/interface-transport) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| [`libp2p-tcp`](//github.com/libp2p/js-libp2p-tcp) | [![npm](https://img.shields.io/npm/v/libp2p-tcp.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-tcp/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-tcp.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-tcp) | [![Travis CI](https://travis-ci.com/libp2p/js-libp2p-tcp.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-tcp) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-tcp/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-tcp) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| [`libp2p-utp`](//github.com/libp2p/js-libp2p-utp) | [![npm](https://img.shields.io/npm/v/libp2p-utp.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-utp/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-utp.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-utp) | [![Travis CI](https://travis-ci.com/libp2p/js-libp2p-utp.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-utp) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-utp/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-utp) | N/A |
| [`libp2p-webrtc-direct`](//github.com/libp2p/js-libp2p-webrtc-direct) | [![npm](https://img.shields.io/npm/v/libp2p-webrtc-direct.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-webrtc-direct/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-webrtc-direct.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-webrtc-direct) | [![Travis CI](https://travis-ci.com/libp2p/js-libp2p-webrtc-direct.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-webrtc-direct) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-webrtc-direct/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-webrtc-direct) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
| [`libp2p-webrtc-star`](//github.com/libp2p/js-libp2p-webrtc-star) | [![npm](https://img.shields.io/npm/v/libp2p-webrtc-star.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-webrtc-star/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-webrtc-star.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-webrtc-star) | [![Travis CI](https://travis-ci.com/libp2p/js-libp2p-webrtc-star.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-webrtc-star) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-webrtc-star/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-webrtc-star) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
| [`libp2p-websockets`](//github.com/libp2p/js-libp2p-websockets) | [![npm](https://img.shields.io/npm/v/libp2p-websockets.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-websockets/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-websockets.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-websockets) | [![Travis CI](https://travis-ci.com/libp2p/js-libp2p-websockets.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-websockets) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-websockets/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-websockets) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| [`libp2p-websocket-star`](//github.com/libp2p/js-libp2p-websocket-star) | [![npm](https://img.shields.io/npm/v/libp2p-websocket-star.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-websocket-star/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-websocket-star.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-websocket-star) | [![Travis CI](https://travis-ci.com/libp2p/js-libp2p-websocket-star.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-websocket-star) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-websocket-star/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-websocket-star) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| **Crypto Channels** |
| [`libp2p-secio`](//github.com/libp2p/js-libp2p-secio) | [![npm](https://img.shields.io/npm/v/libp2p-secio.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-secio/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-secio.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-secio) | [![Travis CI](https://travis-ci.com/libp2p/js-libp2p-secio.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-secio) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-secio/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-secio) | [Friedel Ziegelmayer](mailto:dignifiedquire@gmail.com) |
| **Stream Muxers** |
| [`interface-stream-muxer`](//github.com/libp2p/interface-stream-muxer) | [![npm](https://img.shields.io/npm/v/interface-stream-muxer.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/interface-stream-muxer/releases) | [![Deps](https://david-dm.org/libp2p/interface-stream-muxer.svg?style=flat-square)](https://david-dm.org/libp2p/interface-stream-muxer) | [![Travis CI](https://travis-ci.com/libp2p/interface-stream-muxer.svg?branch=master)](https://travis-ci.com/libp2p/interface-stream-muxer) | [![codecov](https://codecov.io/gh/libp2p/interface-stream-muxer/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/interface-stream-muxer) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| [`libp2p-mplex`](//github.com/libp2p/js-libp2p-mplex) | [![npm](https://img.shields.io/npm/v/libp2p-mplex.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-mplex/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-mplex.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-mplex) | [![Travis CI](https://travis-ci.com/libp2p/js-libp2p-mplex.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-mplex) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-mplex/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-mplex) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
| [`libp2p-spdy`](//github.com/libp2p/js-libp2p-spdy) | [![npm](https://img.shields.io/npm/v/libp2p-spdy.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-spdy/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-spdy.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-spdy) | [![Travis CI](https://travis-ci.com/libp2p/js-libp2p-spdy.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-spdy) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-spdy/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-spdy) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| **Discovery** |
| [`interface-peer-discovery`](//github.com/libp2p/interface-peer-discovery) | [![npm](https://img.shields.io/npm/v/interface-peer-discovery.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/interface-peer-discovery/releases) | [![Deps](https://david-dm.org/libp2p/interface-peer-discovery.svg?style=flat-square)](https://david-dm.org/libp2p/interface-peer-discovery) | [![Travis CI](https://travis-ci.com/libp2p/interface-peer-discovery.svg?branch=master)](https://travis-ci.com/libp2p/interface-peer-discovery) | [![codecov](https://codecov.io/gh/libp2p/interface-peer-discovery/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/interface-peer-discovery) | N/A |
| [`libp2p-bootstrap`](//github.com/libp2p/js-libp2p-bootstrap) | [![npm](https://img.shields.io/npm/v/libp2p-bootstrap.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-bootstrap/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-bootstrap.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-bootstrap) | [![Travis CI](https://travis-ci.com/libp2p/js-libp2p-bootstrap.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-bootstrap) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-bootstrap/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-bootstrap) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
| [`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://travis-ci.com/libp2p/js-libp2p-kad-dht.svg?branch=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)](https://codecov.io/gh/libp2p/js-libp2p-kad-dht) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
| [`libp2p-mdns`](//github.com/libp2p/js-libp2p-mdns) | [![npm](https://img.shields.io/npm/v/libp2p-mdns.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-mdns/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-mdns.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-mdns) | [![Travis CI](https://travis-ci.com/libp2p/js-libp2p-mdns.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-mdns) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-mdns/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-mdns) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| [`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://travis-ci.com/libp2p/js-libp2p-rendezvous.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-rendezvous) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-rendezvous/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-rendezvous) | N/A |
| [`libp2p-webrtc-star`](//github.com/libp2p/js-libp2p-webrtc-star) | [![npm](https://img.shields.io/npm/v/libp2p-webrtc-star.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-webrtc-star/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-webrtc-star.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-webrtc-star) | [![Travis CI](https://travis-ci.com/libp2p/js-libp2p-webrtc-star.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-webrtc-star) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-webrtc-star/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-webrtc-star) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
| [`libp2p-websocket-star`](//github.com/libp2p/js-libp2p-websocket-star) | [![npm](https://img.shields.io/npm/v/libp2p-websocket-star.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-websocket-star/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-websocket-star.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-websocket-star) | [![Travis CI](https://travis-ci.com/libp2p/js-libp2p-websocket-star.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-websocket-star) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-websocket-star/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-websocket-star) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| **Content Routing** |
| [`interface-content-routing`](//github.com/libp2p/interface-content-routing) | [![npm](https://img.shields.io/npm/v/interface-content-routing.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/interface-content-routing/releases) | [![Deps](https://david-dm.org/libp2p/interface-content-routing.svg?style=flat-square)](https://david-dm.org/libp2p/interface-content-routing) | [![Travis CI](https://travis-ci.com/libp2p/interface-content-routing.svg?branch=master)](https://travis-ci.com/libp2p/interface-content-routing) | [![codecov](https://codecov.io/gh/libp2p/interface-content-routing/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/interface-content-routing) | N/A |
| [`libp2p-delegated-content-routing`](//github.com/libp2p/js-libp2p-delegated-content-routing) | [![npm](https://img.shields.io/npm/v/libp2p-delegated-content-routing.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-delegated-content-routing/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-delegated-content-routing.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-delegated-content-routing) | [![Travis CI](https://travis-ci.com/libp2p/js-libp2p-delegated-content-routing.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-delegated-content-routing) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-delegated-content-routing/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-delegated-content-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://travis-ci.com/libp2p/js-libp2p-kad-dht.svg?branch=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)](https://codecov.io/gh/libp2p/js-libp2p-kad-dht) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
| **Peer Routing** |
| [`interface-peer-routing`](//github.com/libp2p/interface-peer-routing) | [![npm](https://img.shields.io/npm/v/interface-peer-routing.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/interface-peer-routing/releases) | [![Deps](https://david-dm.org/libp2p/interface-peer-routing.svg?style=flat-square)](https://david-dm.org/libp2p/interface-peer-routing) | [![Travis CI](https://travis-ci.com/libp2p/interface-peer-routing.svg?branch=master)](https://travis-ci.com/libp2p/interface-peer-routing) | [![codecov](https://codecov.io/gh/libp2p/interface-peer-routing/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/interface-peer-routing) | N/A |
| [`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://travis-ci.com/libp2p/js-libp2p-delegated-peer-routing.svg?branch=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)](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://travis-ci.com/libp2p/js-libp2p-kad-dht.svg?branch=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)](https://codecov.io/gh/libp2p/js-libp2p-kad-dht) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
| **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://travis-ci.com/libp2p/js-libp2p-crypto.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-crypto) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-crypto/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-crypto) | [Friedel Ziegelmayer](mailto:dignifiedquire@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://travis-ci.com/libp2p/js-libp2p-crypto-secp256k1.svg?branch=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)](https://codecov.io/gh/libp2p/js-libp2p-crypto-secp256k1) | [Friedel Ziegelmayer](mailto:dignifiedquire@gmail.com) |
| **Data Types** |
| [`peer-book`](//github.com/libp2p/js-peer-book) | [![npm](https://img.shields.io/npm/v/peer-book.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-peer-book/releases) | [![Deps](https://david-dm.org/libp2p/js-peer-book.svg?style=flat-square)](https://david-dm.org/libp2p/js-peer-book) | [![Travis CI](https://travis-ci.com/libp2p/js-peer-book.svg?branch=master)](https://travis-ci.com/libp2p/js-peer-book) | [![codecov](https://codecov.io/gh/libp2p/js-peer-book/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-peer-book) | [Pedro Teixeira](mailto:i@pgte.me) |
| [`peer-id`](//github.com/libp2p/js-peer-id) | [![npm](https://img.shields.io/npm/v/peer-id.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-peer-id/releases) | [![Deps](https://david-dm.org/libp2p/js-peer-id.svg?style=flat-square)](https://david-dm.org/libp2p/js-peer-id) | [![Travis CI](https://travis-ci.com/libp2p/js-peer-id.svg?branch=master)](https://travis-ci.com/libp2p/js-peer-id) | [![codecov](https://codecov.io/gh/libp2p/js-peer-id/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-peer-id) | [Pedro Teixeira](mailto:i@pgte.me) |
| [`peer-info`](//github.com/libp2p/js-peer-info) | [![npm](https://img.shields.io/npm/v/peer-info.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-peer-info/releases) | [![Deps](https://david-dm.org/libp2p/js-peer-info.svg?style=flat-square)](https://david-dm.org/libp2p/js-peer-info) | [![Travis CI](https://travis-ci.com/libp2p/js-peer-info.svg?branch=master)](https://travis-ci.com/libp2p/js-peer-info) | [![codecov](https://codecov.io/gh/libp2p/js-peer-info/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-peer-info) | [Pedro Teixeira](mailto:i@pgte.me) |
| **Extensions** |
| [`libp2p-floodsub`](//github.com/libp2p/js-libp2p-floodsub) | [![npm](https://img.shields.io/npm/v/libp2p-floodsub.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-floodsub/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-floodsub.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-floodsub) | [![Travis CI](https://travis-ci.com/libp2p/js-libp2p-floodsub.svg?branch=master)](https://travis-ci.com/libp2p/js-libp2p-floodsub) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-floodsub/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-floodsub) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
## Contribute

View File

@ -50,8 +50,8 @@ Would you like to contribute to the libp2p project and don't know how? Well, the
- Check the issues with the `help wanted` label in the [libp2p repo](https://github.com/libp2p/js-libp2p/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22)
- Join an IPFS All Hands, introduce yourself and let us know where you would like to contribute - https://github.com/ipfs/team-mgmt#all-hands-call
- Hack with IPFS and show us what you made! The All Hands call is also the perfect venue for demos, join in and show us what you built
- Join the discussion at http://discuss.libp2p.io/ and help users finding their answers.
- Join the [⚡️libp2p Weekly Sync 🙌🏽](https://github.com/libp2p/team-mgmt/issues/16) and be part of the Sprint action!
- Join the discussion at http://discuss.ipfs.io/ and help users finding their answers.
- Join the [⚡️ⒿⓈ Core Dev Team Weekly Sync 🙌🏽 ](https://github.com/ipfs/team-mgmt/issues/650) and be part of the Sprint action!
# ⁉️ Do you have questions?

View File

@ -1,801 +0,0 @@
# API
* [Static Functions](#static-functions)
* [`create`](#create)
* [Instance Methods](#instance-methods)
* [`start`](#start)
* [`stop`](#stop)
* [`dial`](#dial)
* [`dialProtocol`](#dialprotocol)
* [`hangUp`](#hangup)
* [`handle`](#handle)
* [`unhandle`](#unhandle)
* [`ping`](#ping)
* [`peerRouting.findPeer`](#peerroutingfindpeer)
* [`contentRouting.findProviders`](#contentroutingfindproviders)
* [`contentRouting.provide`](#contentroutingprovide)
* [`contentRouting.put`](#contentroutingput)
* [`contentRouting.get`](#contentroutingget)
* [`contentRouting.getMany`](#contentroutinggetmany)
* [`pubsub.getSubscribers`](#pubsubgetsubscribers)
* [`pubsub.getTopics`](#pubsubgettopics)
* [`pubsub.publish`](#pubsubpublish)
* [`pubsub.subscribe`](#pubsubsubscribe)
* [`pubsub.unsubscribe`](#pubsubunsubscribe)
* [`connectionManager.setPeerValue`](#connectionmanagersetpeervalue)
* [`metrics.global`](#metricsglobal)
* [`metrics.peers`](#metricspeers)
* [`metrics.protocols`](#metricsprotocols)
* [`metrics.forPeer`](#metricsforpeer)
* [`metrics.forProtocol`](#metricsforprotocol)
* [Types](#types)
* [`Stats`](#stats)
## Static Functions
### create
Creates an instance of Libp2p.
`create(options)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| options | `Object` | libp2p options |
| options.modules | `Array<Object>` | libp2p modules to use |
| [options.config] | `Object` | libp2p modules configuration and core configuration |
| [options.connectionManager] | `Object` | libp2p Connection Manager configuration |
| [options.datastore] | `Object` | must implement [ipfs/interface-datastore](https://github.com/ipfs/interface-datastore) (in memory datastore will be used if not provided) |
| [options.dialer] | `Object` | libp2p Dialer configuration
| [options.metrics] | `Object` | libp2p Metrics configuration
| [options.peerInfo] | [PeerInfo](https://github.com/libp2p/js-peer-info) | peerInfo instance (it will be created if not provided) |
For Libp2p configurations and modules details read the [Configuration Document](./CONFIGURATION.md).
#### Returns
| Type | Description |
|------|-------------|
| `Promise<Libp2p>` | Promise resolves with the Libp2p instance |
#### Example
```js
const Libp2p = require('libp2p')
// specify options
const options = {}
// create libp2p
const libp2p = await Libp2p.create(options)
```
Note: The `PeerInfo` option is not required and will be generated if it is not provided.
<details><summary>Alternative</summary>
As an alternative, it is possible to create a Libp2p instance with the constructor:
#### Example
```js
const Libp2p = require('libp2p')
// specify options
const options = {}
// create libp2p
const libp2p = new Libp2p(options)
```
Required keys in the `options` object:
- `peerInfo`: instance of [PeerInfo][] that contains the [PeerId][], Keys and [multiaddrs][multiaddr] of the libp2p Node (optional when using `.create`).
- `modules.transport`: An array that must include at least 1 compliant transport. See [modules that implement the transport interface](https://github.com/libp2p/js-interfaces/tree/master/src/transport#modules-that-implement-the-interface).
</details>
Once you have a libp2p instance, you are able to listen to several events it emits, so that you can be noticed of relevant network events.
<details><summary>Events</summary>
#### An error has occurred
`libp2p.on('error', (err) => {})`
- `err`: instance of `Error`
#### A peer has been discovered
`libp2p.on('peer:discovery', (peer) => {})`
If `autoDial` option is `true`, applications should **not** attempt to connect to the peer
unless they are performing a specific action. See [peer discovery and auto dial](./PEER_DISCOVERY.md) for more information.
- `peer`: instance of [PeerInfo][https://github.com/libp2p/js-peer-info]
#### We have a new connection to a peer
`libp2p.on('peer:connect', (peer) => {})`
- `peer`: instance of [PeerInfo][https://github.com/libp2p/js-peer-info]
#### We have closed a connection to a peer
`libp2p.on('peer:disconnect', (peer) => {})`
- `peer`: instance of [PeerInfo][https://github.com/libp2p/js-peer-info]
</details>
## Libp2p Instance Methods
### start
Starts the libp2p node.
`libp2p.start()`
#### Returns
| Type | Description |
|------|-------------|
| `Promise` | Promise resolves when the node is ready |
#### Example
```js
const Libp2p = require('libp2p')
// ...
const libp2p = await Libp2p.create(options)
// start libp2p
await libp2p.start()
```
### stop
Stops the libp2p node.
`libp2p.stop()`
#### Returns
| Type | Description |
|------|-------------|
| `Promise` | Promise resolves when the node is fully stopped |
#### Example
```js
const Libp2p = require('libp2p')
// ...
const libp2p = await Libp2p.create(options)
// ...
// stop libp2p
await libp2p.stop()
```
### connections
A Getter that returns a Map of the current Connections libp2p has to other peers.
`libp2p.connections`
#### Returns
| Type | Description |
|------|-------------|
| `Map<string, Array<Connection>>` | A map of [`PeerId`][peer-id] strings to [`Connection`][connection] Arrays |
#### Example
```js
for (const [peerId, connections] of libp2p.connections) {
for (const connection of connections) {
console.log(peerId, connection.remoteAddr.toString())
// Logs the PeerId string and the observed remote multiaddr of each Connection
}
}
```
### dial
Dials to another peer in the network and establishes the connection.
`dial(peer, options)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| peer | [PeerInfo](https://github.com/libp2p/js-peer-info), [PeerId][peer-id], [multiaddr](https://github.com/multiformats/js-multiaddr), `string` | peer to dial |
| [options] | `Object` | dial options |
| [options.signal] | [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) | An `AbortSignal` instance obtained from an [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) that can be used to abort the connection before it completes |
#### Returns
| Type | Description |
|------|-------------|
| `Promise<Connection>` | Promise resolves with the [Connection][connection] instance |
#### Example
```js
// ...
const conn = await libp2p.dial(remotePeerInfo)
// create a new stream within the connection
const { stream, protocol } = await conn.newStream(['/echo/1.1.0', '/echo/1.0.0'])
// protocol negotiated: 'echo/1.0.0' means that the other party only supports the older version
// ...
await conn.close()
```
### dialProtocol
Dials to another peer in the network and selects a protocol to communicate with that peer. The stream between both parties is returned, together with the negotiated protocol.
`dialProtocol(peer, protocols, options)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| peer | [PeerInfo](https://github.com/libp2p/js-peer-info), [PeerId][peer-id], [multiaddr](https://github.com/multiformats/js-multiaddr), `string` | peer to dial |
| protocols | `String|Array<String>` | A list of protocols (or single protocol) to negotiate with. Protocols are attempted in order until a match is made. (e.g '/ipfs/bitswap/1.1.0') |
| [options] | `Object` | dial options |
| [options.signal] | [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) | An `AbortSignal` instance obtained from an [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) that can be used to abort the connection before it completes |
#### Returns
| Type | Description |
|------|-------------|
| `Promise<{ stream:*, protocol:string }>` | Promise resolves with a [duplex stream](https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#duplex-it) and the protocol used |
#### Example
```js
// ...
const pipe = require('it-pipe')
const { stream, protocol } = await libp2p.dialProtocol(remotePeerInfo, protocols)
// Use this new stream like any other duplex stream
pipe([1, 2, 3], stream, consume)
```
### hangUp
Attempts to gracefully close an open connection to the given peer. If the connection is not closed in the grace period, it will be forcefully closed.
`hangUp(peer)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| peer | [PeerInfo](https://github.com/libp2p/js-peer-info), [PeerId][peer-id], [multiaddr](https://github.com/multiformats/js-multiaddr), `string` | peer to hang up |
#### Returns
| Type | Description |
|------|-------------|
| `Promise<void>` | Promise resolves once connection closes |
#### Example
```js
// ...
await libp2p.hangUp(remotePeerInfo)
```
### handle
Sets up [multistream-select routing](https://github.com/multiformats/multistream-select) of protocols to their application handlers. Whenever a stream is opened on one of the provided protocols, the handler will be called. `handle` must be called in order to register a handler and support for a given protocol. This also informs other peers of the protocols you support.
`libp2p.handle(protocols, handler)`
In the event of a new handler for the same protocol being added, the first one is discarded.
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| protocols | `Array<String>|String` | protocols to register |
| handler | `function({ connection:*, stream:*, protocol:string })` | handler to call |
#### Example
```js
// ...
const handler = ({ connection, stream, protocol }) => {
// use stream or connection according to the needs
}
libp2p.handle('/echo/1.0.0', handler)
```
### unhandle
Unregisters all handlers with the given protocols
`libp2p.unhandle(protocols)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| protocols | `Array<String>|String` | protocols to unregister |
#### Example
```js
// ...
libp2p.unhandle(['/echo/1.0.0'])
```
### ping
Pings a given peer and get the operation's latency.
`libp2p.ping(peer)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| peer | `PeerInfo|PeerId|Multiaddr|string` | peer to ping |
#### Returns
| Type | Description |
|------|-------------|
| `Promise<number>` | Latency of the operation in ms |
#### Example
```js
// ...
const latency = await libp2p.ping(otherPeerId)
```
### peerRouting.findPeer
Iterates over all peer routers in series to find the given peer. If the DHT is enabled, it will be tried first.
`libp2p.peerRouting.findPeer(peerId, options)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| peerId | [`PeerId`][peer-id] | ID of the peer to find |
| options | `Object` | operation options |
| options.timeout | `number` | maximum time the query should run |
#### Returns
| Type | Description |
|------|-------------|
| `Promise<PeerInfo>` | Peer info of a known peer |
#### Example
```js
// ...
const peerInfo = await libp2p.peerRouting.findPeer(peerId, options)
```
### contentRouting.findProviders
Iterates over all content routers in series to find providers of the given key.
Once a content router succeeds, the iteration will stop. If the DHT is enabled, it will be queried first.
`libp2p.contentRouting.findProviders(cid, options)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| cid | [`CID`](https://github.com/multiformats/js-cid) | cid to find |
| options | `Object` | operation options |
| options.timeout | `number` | maximum time the query should run |
| options.maxNumProviders | `number` | maximum number of providers to find |
#### Returns
| Type | Description |
|------|-------------|
| `AsyncIterator<PeerInfo>` | Async iterator for [`PeerInfo`](https://github.com/libp2p/js-peer-info) |
#### Example
```js
// Iterate over the providers found for the given cid
for await (const provider of libp2p.contentRouting.findProviders(cid)) {
console.log(provider)
}
```
### contentRouting.provide
Iterates over all content routers in parallel, in order to notify it is a provider of the given key.
`libp2p.contentRouting.provide(cid)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| cid | [`CID`](https://github.com/multiformats/js-cid) | cid to provide |
#### Returns
| Type | Description |
|------|-------------|
| `Promise<void>` | Promise resolves once notifications are sent |
#### Example
```js
// ...
await libp2p.contentRouting.provide(cid)
```
### contentRouting.put
Writes a value to a key in the DHT.
`libp2p.contentRouting.put(key, value, options)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| key | `String` | key to add to the dht |
| value | `Buffer` | value to add to the dht |
| [options] | `Object` | put options |
| [options.minPeers] | `number` | minimum number of peers required to successfully put (default: closestPeers.length) |
#### Returns
| Type | Description |
|------|-------------|
| `Promise<void>` | Promise resolves once value is stored |
#### Example
```js
// ...
const key = '/key'
const value = Buffer.from('oh hello there')
await libp2p.contentRouting.put(key, value)
```
### contentRouting.get
Queries the DHT for a value stored for a given key.
`libp2p.contentRouting.get(key, options)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| key | `String` | key to get from the dht |
| [options] | `Object` | get options |
| [options.timeout] | `number` | maximum time the query should run |
#### Returns
| Type | Description |
|------|-------------|
| `Promise<Buffer>` | Value obtained from the DHT |
#### Example
```js
// ...
const key = '/key'
const value = await libp2p.contentRouting.get(key)
```
### contentRouting.getMany
Queries the DHT for the n values stored for the given key (without sorting).
`libp2p.contentRouting.getMany(key, nvals, options)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| key | `String` | key to get from the dht |
| nvals | `number` | number of values aimed |
| [options] | `Object` | get options |
| [options.timeout] | `number` | maximum time the query should run |
#### Returns
| Type | Description |
|------|-------------|
| `Promise<Array<{from: PeerId, val: Buffer}>>` | Array of records obtained from the DHT |
#### Example
```js
// ...
const key = '/key'
const { from, val } = await libp2p.contentRouting.get(key)
```
### pubsub.getSubscribers
Gets a list of the peer-ids that are subscribed to one topic.
`libp2p.pubsub.getSubscribers(topic)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| topic | `string` | topic to publish |
#### Returns
| Type | Description |
|------|-------------|
| `Array<String>` | peer-id subscribed to the topic |
#### Example
```js
const peerIds = libp2p.pubsub.getSubscribers(topic)
```
### pubsub.getTopics
Gets a list of topics the node is subscribed to.
`libp2p.pubsub.getTopics()`
#### Returns
| Type | Description |
|------|-------------|
| `Array<String>` | topics the node is subscribed to |
#### Example
```js
const topics = libp2p.pubsub.getTopics()
```
### pubsub.publish
Publishes messages to the given topics.
`libp2p.pubsub.publish(topic, data)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| topic | `string` | topic to publish |
| data | `Buffer` | data to publish |
#### Returns
| Type | Description |
|------|-------------|
| `Promise` | publish success |
#### Example
```js
const topic = 'topic'
const data = Buffer.from('data')
await libp2p.pubsub.publish(topic, data)
```
### pubsub.subscribe
Subscribes the given handler to a pubsub topic.
`libp2p.pubsub.subscribe(topic, handler)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| topic | `string` | topic to subscribe |
| handler | `function({ from: String, data: Buffer, seqno: Buffer, topicIDs: Array<String>, signature: Buffer, key: Buffer })` | handler for new data on topic |
#### Returns
| Type | Description |
|------|-------------|
| `void` | |
#### Example
```js
const topic = 'topic'
const handler = (msg) => {
// msg.data - pubsub data received
}
libp2p.pubsub.subscribe(topic, handler)
```
### pubsub.unsubscribe
Unsubscribes the given handler from a pubsub topic. If no handler is provided, all handlers for the topic are removed.
`libp2p.pubsub.unsubscribe(topic, handler)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| topic | `string` | topic to unsubscribe |
| handler | `function(<Object>)` | handler subscribed |
#### Returns
| Type | Description |
|------|-------------|
| `void` | |
#### Example
```js
const topic = 'topic'
const handler = (msg) => {
// msg.data - pubsub data received
}
libp2p.pubsub.unsubscribe(topic, handler)
```
### connectionManager.setPeerValue
Enables users to change the value of certain peers in a range of 0 to 1. Peers with the lowest values will have their Connections pruned first, if any Connection Manager limits are exceeded. See [./CONFIGURATION.md#configuring-connection-manager](./CONFIGURATION.md#configuring-connection-manager) for details on how to configure these limits.
`libp2p.connectionManager.setPeerValue(peerId, value)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| peerId | `PeerId` | The peer to set the value for |
| value | `number` | The value of the peer from 0 to 1 |
#### Returns
| Type | Description |
|------|-------------|
| `void` | |
#### Example
```js
libp2p.connectionManager.setPeerValue(highPriorityPeerId, 1)
libp2p.connectionManager.setPeerValue(lowPriorityPeerId, 0)
```
### metrics.global
A [`Stats`](#stats) object of tracking the global bandwidth of the libp2p node.
#### Example
```js
const peerIdStrings = libp2p.metrics.peers
```
### metrics.peers
An array of `PeerId` strings of each peer currently being tracked.
#### Example
```js
const peerIdStrings = libp2p.metrics.peers
```
### metrics.protocols
An array of protocol strings that are currently being tracked.
#### Example
```js
const protocols = libp2p.metrics.protocols
```
### metrics.forPeer
Returns the [`Stats`](#stats) object for a given `PeerId` if it is being tracked.
`libp2p.metrics.forPeer(peerId)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| peerId | `PeerId` | The peer to get stats for |
#### Returns
| Type | Description |
|------|-------------|
| [`Stats`](#stats) | The bandwidth stats of the peer |
#### Example
```js
const peerStats = libp2p.metrics.forPeer(peerInfo)
console.log(peerStats.toJSON())
```
### metrics.forProtocol
Returns the [`Stats`](#stats) object for a given protocol if it is being tracked.
`libp2p.metrics.forProtocol(protocol)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| protocol | `string` | The protocol to get stats for |
#### Returns
| Type | Description |
|------|-------------|
| [`Stats`](#stats) | The bandwidth stats of the protocol across all peers |
#### Example
```js
const peerStats = libp2p.metrics.forProtocol('/meshsub/1.0.0')
console.log(peerStats.toJSON())
```
## Types
### Stats
- `Stats`
- `toJSON<function()>`: Returns a JSON snapshot of the stats.
- `dataReceived<string>`: The stringified value of total incoming data for this stat.
- `dataSent<string>`: The stringified value of total outgoing data for this stat.
- `movingAverages<object>`: The properties are dependent on the configuration of the moving averages interval. Defaults are listed here.
- `['60000']<Number>`: The calculated moving average at a 1 minute interval.
- `['300000']<Number>`: The calculated moving average at a 5 minute interval.
- `['900000']<Number>`: The calculated moving average at a 15 minute interval.
- `snapshot<object>`: A getter that returns a clone of the raw stats.
- `dataReceived<BigNumber>`: A [`BigNumber`](https://github.com/MikeMcl/bignumber.js/) of the amount of incoming data
- `dataSent<BigNumber>`: A [`BigNumber`](https://github.com/MikeMcl/bignumber.js/) of the amount of outgoing data
- `movingAverages<object>`: A getter that returns a clone of the raw [moving averages](https://www.npmjs.com/package/moving-averages) stats. **Note**: The properties of this are dependent on configuration. The defaults are shown here.
- `['60000']<MovingAverage>`: The [MovingAverage](https://www.npmjs.com/package/moving-averages) at a 1 minute interval.
- `['300000']<MovingAverage>`: The [MovingAverage](https://www.npmjs.com/package/moving-averages) at a 5 minute interval.
- `['900000']<MovingAverage>`: The [MovingAverage](https://www.npmjs.com/package/moving-averages) at a 15 minute interval.
[connection]: https://github.com/libp2p/js-interfaces/tree/master/src/connection
[peer-id]: https://github.com/libp2p/js-peer-id

View File

@ -1,541 +0,0 @@
# Configuration
- [Configuration](#configuration)
- [Overview](#overview)
- [Modules](#modules)
- [Transport](#transport)
- [Stream Multiplexing](#stream-multiplexing)
- [Connection Encryption](#connection-encryption)
- [Peer Discovery](#peer-discovery)
- [Content Routing](#content-routing)
- [Peer Routing](#peer-routing)
- [DHT](#dht)
- [Pubsub](#pubsub)
- [Customizing libp2p](#customizing-libp2p)
- [Examples](#examples)
- [Basic setup](#basic-setup)
- [Customizing Peer Discovery](#customizing-peer-discovery)
- [Setup webrtc transport and discovery](#setup-webrtc-transport-and-discovery)
- [Customizing Pubsub](#customizing-pubsub)
- [Customizing DHT](#customizing-dht)
- [Setup with Content and Peer Routing](#setup-with-content-and-peer-routing)
- [Setup with Relay](#setup-with-relay)
- [Configuring Dialing](#configuring-dialing)
- [Configuring Connection Manager](#configuring-connection-manager)
- [Configuring Metrics](#configuring-metrics)
- [Customizing Transports](#customizing-transports)
- [Configuration examples](#configuration-examples)
## Overview
libp2p is a modular networking stack. It's designed to be able to suit a variety of project needs. The configuration of libp2p is a key part of its structure. It enables you to bring exactly what you need, and only what you need. This document is a guide on how to configure libp2p for your particular project. Check out the [Configuration examples](#configuration-examples) section if you're just looking to leverage an existing configuration.
Regardless of how you configure libp2p, the top level [API](./API.md) will always remain the same. **Note**: if some modules are not configured, like Content Routing, using those methods will throw errors.
## Modules
`js-libp2p` acts as the composer for this modular p2p networking stack using libp2p compatible modules as its subsystems. For getting an instance of `js-libp2p` compliant with all types of networking requirements, it is possible to specify the following subsystems:
- Transports
- Multiplexers
- Connection encryption mechanisms
- Peer discovery protocols
- Content routing protocols
- Peer routing protocols
- DHT implementation
- Pubsub router
The libp2p ecosystem contains at least one module for each of these subsystems. The user should install and import the modules that are relevant for their requirements. Moreover, thanks to the existing interfaces it is easy to create a libp2p compatible module and use it.
After selecting the modules to use, it is also possible to configure each one according to your needs.
Bear in mind that only a **transport** and **connection encryption** are required, while all the other subsystems are optional.
### Transport
> In a p2p system, we need to interact with other peers in the network. Transports are used to establish connections between peers. The libp2p transports to use should be decided according to the environment where your node will live, as well as other requirements that you might have.
Some available transports are:
- [libp2p/js-libp2p-tcp](https://github.com/libp2p/js-libp2p-tcp)
- [libp2p/js-libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star)
- [libp2p/js-libp2p-webrtc-direct](https://github.com/libp2p/js-libp2p-webrtc-direct)
- [libp2p/js-libp2p-websockets](https://github.com/libp2p/js-libp2p-websockets)
- [libp2p/js-libp2p-utp](https://github.com/libp2p/js-libp2p-utp) (Work in Progress)
You should take into consideration that `js-libp2p-tcp` and `js-libp2p-utp` are not available in a **browser** environment.
If none of the available transports fulfills your needs, you can create a libp2p compatible transport. A libp2p transport just needs to be compliant with the [Transport Interface](https://github.com/libp2p/js-interfaces/tree/master/src/transport).
If you want to know more about libp2p transports, you should read the following content:
- https://docs.libp2p.io/concepts/transport
- https://github.com/libp2p/specs/tree/master/connections
### Stream Multiplexing
> Libp2p peers will need to communicate with each other through several protocols during their life. Stream multiplexing allows multiple independent logical streams to share a common underlying transport medium, instead of creating a new connection with the same peer per needed protocol.
Some available stream multiplexers are:
- [libp2p/js-libp2p-mplex](https://github.com/libp2p/js-libp2p-mplex)
If none of the available stream multiplexers fulfills your needs, you can create a libp2p compatible stream multiplexer. A libp2p multiplexer just needs to be compliant with the [Stream Muxer Interface](https://github.com/libp2p/js-interfaces/tree/master/src/stream-muxer).
If you want to know more about libp2p stream multiplexing, you should read the following content:
- https://docs.libp2p.io/concepts/stream-multiplexing
- https://github.com/libp2p/specs/tree/master/connections
- https://github.com/libp2p/specs/tree/master/mplex
### Connection Encryption
> A connection encryption mechanism must be used, in order to ensure all exchanged data between two peers is encrypted.
Some available connection encryption protocols:
- [libp2p/js-libp2p-secio](https://github.com/libp2p/js-libp2p-secio)
If none of the available connection encryption mechanisms fulfills your needs, you can create a libp2p compatible one. A libp2p connection encryption protocol just needs to be compliant with the [Crypto Interface](https://github.com/libp2p/js-interfaces/tree/master/src/crypto).
If you want to know more about libp2p connection encryption, you should read the following content:
- https://docs.libp2p.io/concepts/secure-comms
- https://github.com/libp2p/specs/tree/master/connections
### Peer Discovery
> In a p2p network, peer discovery is critical to a functioning system.
Some available peer discovery modules are:
- [js-libp2p-mdns](https://github.com/libp2p/js-libp2p-mdns)
- [js-libp2p-bootstrap](https://github.com/libp2p/js-libp2p-bootstrap)
- [js-libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht)
- [js-libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star)
**Note**: `peer-discovery` services within transports (such as `js-libp2p-webrtc-star`) are automatically gathered from the `transport`, via it's `discovery` property. As such, they do not need to be added in the discovery modules. However, these transports can also be configured and disabled as the other ones.
If none of the available peer discovery protocols fulfills your needs, you can create a libp2p compatible one. A libp2p peer discovery protocol just needs to be compliant with the [Peer Discovery Interface](https://github.com/libp2p/js-interfaces/tree/master/src/peer-discovery).
If you want to know more about libp2p peer discovery, you should read the following content:
- https://github.com/libp2p/specs/blob/master/discovery/mdns.md
### Content Routing
> Content routing provides a way to find where content lives in the network. It works in two steps: 1) Peers provide (announce) to the network that they are holders of specific content and 2) Peers issue queries to find where that content lives. A Content Routing mechanism could be as complex as a DHT or as simple as a registry somewhere in the network.
Some available content routing modules are:
- [js-libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht)
- [js-libp2p-delegated-peer-routing](https://github.com/libp2p/js-libp2p-delegated-peer-routing)
If none of the available content routing protocols fulfills your needs, you can create a libp2p compatible one. A libp2p content routing protocol just needs to be compliant with the [Content Routing Interface](https://github.com/libp2p/js-interfaces/tree/master/src/content-routing). **(WIP: This module is not yet implemented)**
If you want to know more about libp2p content routing, you should read the following content:
- https://docs.libp2p.io/concepts/content-routing
### Peer Routing
> Peer Routing offers a way to find other peers in the network by issuing queries using a Peer Routing algorithm, which may be iterative or recursive. If the algorithm is unable to find the target peer, it will return the peers that are "closest" to the target peer, using a distance metric defined by the algorithm.
Some available peer routing modules are:
- [js-libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht)
- [js-libp2p-delegated-peer-routing](https://github.com/libp2p/js-libp2p-delegated-peer-routing)
If none of the available peer routing protocols fulfills your needs, you can create a libp2p compatible one. A libp2p peer routing protocol just needs to be compliant with the [Peer Routing Interface](https://github.com/libp2p/js-interfaces/tree/master/src/peer-routing). **(WIP: This module is not yet implemented)**
If you want to know more about libp2p peer routing, you should read the following content:
- https://docs.libp2p.io/concepts/peer-routing
### DHT
> A DHT can provide content and peer routing capabilities in a p2p system, as well as peer discovery capabilities.
The DHT implementation currently available is [libp2p/js-libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht). This implementation is largely based on the Kademlia whitepaper, augmented with notions from S/Kademlia, Coral and mainlineDHT.
If this DHT implementation does not fulfill your needs and you want to create or use your own implementation, please get in touch with us through a github issue. We plan to work on improving the ability to bring your own DHT in a future release.
If you want to know more about libp2p DHT, you should read the following content:
- https://docs.libp2p.io/concepts/protocols/#kad-dht
- https://github.com/libp2p/specs/pull/108
### Pubsub
> Publish/Subscribe is a system where peers congregate around topics they are interested in. Peers interested in a topic are said to be subscribed to that topic and should receive the data published on it from other peers.
Some available pubsub routers are:
- [libp2p/js-libp2p-floodsub](https://github.com/libp2p/js-libp2p-floodsub)
- [ChainSafe/gossipsub-js](https://github.com/ChainSafe/gossipsub-js)
If none of the available pubsub routers fulfills your needs, you can create a libp2p compatible one. A libp2p pubsub router just needs to be created on top of [libp2p/js-libp2p-pubsub](https://github.com/libp2p/js-libp2p-pubsub), which ensures `js-libp2p` API expectations.
If you want to know more about libp2p pubsub, you should read the following content:
- https://docs.libp2p.io/concepts/publish-subscribe
- https://github.com/libp2p/specs/tree/master/pubsub
## Customizing libp2p
When [creating a libp2p node](./API.md#create), the modules needed should be specified as follows:
```js
const modules = {
transport: [],
streamMuxer: [],
connEncryption: [],
contentRouting: [],
peerRouting: [],
peerDiscovery: [],
dht: dhtImplementation,
pubsub: pubsubImplementation
}
```
Moreover, the majority of the modules can be customized via option parameters. This way, it is also possible to provide this options through a `config` object. This config object should have the property name of each building block to configure, the same way as the modules specification.
Besides the `modules` and `config`, libp2p allows other internal options and configurations:
- `datastore`: an instance of [ipfs/interface-datastore](https://github.com/ipfs/interface-datastore/) modules.
- This is used in modules such as the DHT. If it is not provided, `js-libp2p` will use an in memory datastore.
- `peerInfo`: a previously created instance of [libp2p/js-peer-info](https://github.com/libp2p/js-peer-info).
- This is particularly useful if you want to reuse the same `peer-id`, as well as for modules like `libp2p-delegated-content-routing`, which need a `peer-id` in their instantiation.
### Examples
#### Basic setup
```js
// Creating a libp2p node with:
// transport: websockets + tcp
// stream-muxing: mplex
// crypto-channel: secio
// discovery: multicast-dns
// dht: kad-dht
// pubsub: gossipsub
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const WS = require('libp2p-websockets')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const MulticastDNS = require('libp2p-mdns')
const DHT = require('libp2p-kad-dht')
const GossipSub = require('libp2p-gossipsub')
const node = await Libp2p.create({
modules: {
transport: [
TCP,
new WS() // It can take instances too!
],
streamMuxer: [MPLEX],
connEncryption: [SECIO],
peerDiscovery: [MulticastDNS],
dht: DHT,
pubsub: GossipSub
}
})
```
#### Customizing Peer Discovery
```js
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const MulticastDNS = require('libp2p-mdns')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO],
peerDiscovery: [MulticastDNS]
},
config: {
peerDiscovery: {
autoDial: true, // Auto connect to discovered peers (limited by ConnectionManager minPeers)
// The `tag` property will be searched when creating the instance of your Peer Discovery service.
// The associated object, will be passed to the service when it is instantiated.
[MulticastDNS.tag]: {
interval: 1000,
enabled: true
}
// .. other discovery module options.
}
}
})
```
#### Setup webrtc transport and discovery
```js
const Libp2p = require('libp2p')
const WS = require('libp2p-websockets')
const WebRTCStar = require('libp2p-webrtc-star')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const node = await Libp2p.create({
modules: {
transport: [
WS,
WebRTCStar
],
streamMuxer: [MPLEX],
connEncryption: [SECIO],
},
config: {
peerDiscovery: {
webRTCStar: {
enabled: true
}
}
}
})
```
#### Customizing Pubsub
```js
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const GossipSub = require('libp2p-gossipsub')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO],
pubsub: GossipSub
},
config: {
pubsub: { // The pubsub options (and defaults) can be found in the pubsub router documentation
enabled: true,
emitSelf: true, // whether the node should emit to self on publish
signMessages: true, // if messages should be signed
strictSigning: true // if message signing should be required
}
}
})
```
#### Customizing DHT
```js
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const DHT = require('libp2p-kad-dht')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO],
dht: DHT
},
config: {
dht: { // The DHT options (and defaults) can be found in its documentation
kBucketSize: 20,
enabled: true,
randomWalk: {
enabled: true, // Allows to disable discovery (enabled by default)
interval: 300e3,
timeout: 10e3
}
}
}
})
```
#### Setup with Content and Peer Routing
```js
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const DelegatedPeerRouter = require('libp2p-delegated-peer-routing')
const DelegatedContentRouter = require('libp2p-delegated-content-routing')
const PeerInfo = require('peer-info')
// create a peerInfo
const peerInfo = await PeerInfo.create()
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO],
contentRouting: [
new DelegatedContentRouter(peerInfo.id)
],
peerRouting: [
new DelegatedPeerRouter()
],
},
peerInfo
})
```
#### Setup with Relay
```js
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
},
config: {
relay: { // Circuit Relay options (this config is part of libp2p core configurations)
enabled: true, // Allows you to dial and accept relayed connections. Does not make you a relay.
hop: {
enabled: true, // Allows you to be a relay for other peers
active: true // You will attempt to dial destination peers if you are not connected to them
}
}
}
})
```
#### Configuring Dialing
Dialing in libp2p can be configured to limit the rate of dialing, and how long dials are allowed to take. The below configuration example shows the default values for the dialer.
```js
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
},
dialer: {
maxParallelDials: 100, // How many multiaddrs we can dial in parallel
maxDialsPerPeer: 4, // How many multiaddrs we can dial per peer, in parallel
dialTimeout: 30e3 // 30 second dial timeout per peer
}
```
#### Configuring Connection Manager
The Connection Manager prunes Connections in libp2p whenever certain limits are exceeded. If Metrics are enabled, you can also configure the Connection Manager to monitor the bandwidth of libp2p and prune connections as needed. You can read more about what Connection Manager does at [./CONNECTION_MANAGER.md](./CONNECTION_MANAGER.md). The configuration values below show the defaults for Connection Manager. See [./CONNECTION_MANAGER.md](./CONNECTION_MANAGER.md#options) for a full description of the parameters.
```js
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
},
connectionManager: {
maxConnections: Infinity,
minConnections: 0,
pollInterval: 2000,
defaultPeerValue: 1,
// The below values will only be taken into account when Metrics are enabled
maxData: Infinity,
maxSentData: Infinity,
maxReceivedData: Infinity,
maxEventLoopDelay: Infinity,
movingAverageInterval: 60000
}
})
```
#### Configuring Metrics
Metrics are disabled in libp2p by default. You can enable and configure them as follows. Aside from enabled being `false` by default, the configuration options listed here are the current defaults.
```js
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
},
metrics: {
enabled: true,
computeThrottleMaxQueueSize: 1000, // How many messages a stat will queue before processing
computeThrottleTimeout: 2000, // Time in milliseconds a stat will wait, after the last item was added, before processing
movingAverageIntervals: [ // The moving averages that will be computed
60 * 1000, // 1 minute
5 * 60 * 1000, // 5 minutes
15 * 60 * 1000 // 15 minutes
],
maxOldPeersRetention: 50 // How many disconnected peers we will retain stats for
}
})
```
#### Customizing Transports
Some Transports can be passed additional options when they are created. For example, `libp2p-webrtc-star` accepts an optional, custom `wrtc` implementation. In addition to libp2p passing itself and an `Upgrader` to handle connection upgrading, libp2p will also pass the options, if they are provided, from `config.transport`.
```js
const Libp2p = require('libp2p')
const WebRTCStar = require('libp2p-webrtc-star')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const wrtc = require('wrtc')
const transportKey = WebRTCStar.prototype[Symbol.toStringTag]
const node = await Libp2p.create({
modules: {
transport: [WebRTCStar],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
},
config: {
transport: {
[transportKey]: {
wrtc // You can use `wrtc` when running in Node.js
}
}
}
})
```
## Configuration examples
As libp2p is designed to be a modular networking library, its usage will vary based on individual project needs. We've included links to some existing project configurations for your reference, in case you wish to replicate their configuration:
- [libp2p-ipfs-nodejs](https://github.com/ipfs/js-ipfs/tree/master/src/core/runtime/libp2p-nodejs.js) - libp2p configuration used by js-ipfs when running in Node.js
- [libp2p-ipfs-browser](https://github.com/ipfs/js-ipfs/tree/master/src/core/runtime/libp2p-browser.js) - libp2p configuration used by js-ipfs when running in a Browser (that supports WebRTC)
If you have developed a project using `js-libp2p`, please consider submitting your configuration to this list so that it can be found easily by other users.
The [examples](../examples) are also a good source of help for finding a configuration that suits your needs.

View File

@ -1,20 +0,0 @@
# Connection Manager
The Connection Manager works with the Registrar to keep connections across libp2p within acceptable ranges, which can be configured. By default Connection Manager will monitor:
- The total number of open connections
- The latency/delay of the event loop
If Metrics are enabled for libp2p, see [./CONFIGURATION.md#configuring-metrics](./CONFIGURATION.md#configuring-metrics) on how to configure metrics, the Connection Manager can be used to prune connections when certain limits are exceeded.
The following is a list of available options for setting limits for the Connection Manager to enforce.
## Options
- `maxConnections`: the maximum number of connections libp2p is willing to have before it starts disconnecting. Defaults to `Infinity`
- `minConnections`: the minimum number of connections below which libp2p not activate preemptive disconnections. Defaults to `0`.
- `maxData`: sets the maximum data — in bytes per second - (sent and received) this node is willing to endure before it starts disconnecting peers. Defaults to `Infinity`.
- `maxSentData`: sets the maximum sent data — in bytes per second - this node is willing to endure before it starts disconnecting peers. Defaults to `Infinity`.
- `maxReceivedData`: sets the maximum received data — in bytes per second - this node is willing to endure before it starts disconnecting peers. Defaults to `Infinity`.
- `maxEventLoopDelay`: sets the maximum event loop delay (measured in milliseconds) this node is willing to endure before it starts disconnecting peers. Defaults to `Infinity`.
- `pollInterval`: sets the poll interval (in milliseconds) for assessing the current state and determining if this peer needs to force a disconnect. Defaults to `2000` (2 seconds).
- `movingAverageInterval`: the interval used to calculate moving averages (in milliseconds). Defaults to `60000` (1 minute). This must be an available interval configured in `Metrics`
- `defaultPeerValue`: number between 0 and 1. Defaults to 1.

View File

@ -1,32 +0,0 @@
# js-libp2p Dialer
**Synopsis**
* Parallel dials to the same peer will yield the same connection/error when the first dial settles.
* All Dial Requests in js-libp2p must request a token(s) from the Dialer.
* The number of tokens requested should be between 1 and the MAX_PER_PEER_DIALS max set in the Dialer.
* If the number of available tokens is less than requested, the Dialer may return less than requested.
* The number of tokens a DialRequest obtains reflects the maximum number of parallel Multiaddr Dials it can make.
* If no tokens are available a DialRequest should immediately end and throw.
* As tokens are limited, DialRequests should be given a prioritized list of Multiaddrs to minimize the potential request time.
* Once a Multiaddr Dial has succeeded, all pending dials in that Dial Request should be aborted.
* If DIAL_TIMEOUT time has elapsed before any one Multiaddr Dial succeeds, all remaining dials in the DialRequest should be aborted.
* When a Multiaddr Dial is settled, if there are no more addresses to dial, its token should be released back to the dialer.
* Once the DialRequest is settled, any remaining tokens should be released to the dialer.
## Multiaddr Confidence
An effective dialing system should involve the inclusion of a Confidence system for Multiaddrs. This enables ranking of Multiaddrs so that a prioritized list can be passed to DialRequests to maximize usage of Dialer Tokens, and minimize connection times.
**Not Yet Implemented**: This system will be designed and implemented in a future update.
## Notes
* A DialRequest gets a set of tokens from the Dialer, up to the MAX_PER_PEER_DIALS max.
* A DialRequest SHOULD fail if no dial tokens are available. The DialRequest MAY proceed without tokens, but this should be reserved for High Priority actions and should be rare.
* A DialRequest MUST NOT request more tokens than it has addresses to dial. Example: If the MAX_PER_PEER_DIALS is 4 and a DialRequest has 1 address, it should only request 1 token.
* A DialRequest SHOULD execute parallel dials for each of its addresses up the total number of tokens it has.
* On a successful dial, the DialRequest MUST abort any other in progress dials, return the successful connection and release all tokens.
* A new DialRequest SHOULD be given a descending list of prioritized Multiaddrs, based on their confidence. Having higher confidence Multiaddrs first can help minimize the time a DialRequest is active.
* A failed dial to a Multiaddr SHOULD add that Multiaddr to a temporary denyList.
* A failed dial to a Multiaddr SHOULD lower the confidence of that Multiaddr.
* A successful dial to a Multiaddr SHOULD increase the confidence of that Multiaddr.

View File

@ -1,28 +0,0 @@
# Bandwidth Metrics
- Metrics gathering is optional, as there is a performance hit to using it.
- Metrics do NOT currently contain OS level stats, only libp2p application level Metrics. For example, TCP messages (ACK, FIN, etc) are not accounted for.
- See the [API](./API.md) for Metrics usage. Metrics in libp2p do not emit events, as such applications wishing to read Metrics will need to do so actively. This ensures that the system is not unnecessarily firing update notifications.
## Tracking
- When a transport hands off a connection for upgrading, Metrics are hooked up if enabled.
- When a stream is created, Metrics will be tracked on that stream and associated to that streams protocol.
- Tracked Metrics are associated to a specific peer, and count towards global bandwidth Metrics.
### Metrics Processing
- The main Metrics object consists of individual `Stats` objects
- The following categories are tracked:
- Global stats; every byte in and out
- Peer stats; every byte in and out, per peer
- Protocol stats; every byte in and out, per protocol
- When a message goes through Metrics:
- It is added to the global stat
- It is added to the stats for the remote peer
- It is added to the protocol stats if there is one
- When data is pushed onto a `Stat` it is added to a queue
- The queue is processed at the earliest of either (configurable):
- every 2 seconds after the last item was added to the queue
- or once 1000 items have been queued
- When the queue is processed:
- The data length is added to either the `in` or `out` stat
- The moving averages is calculated since the last queue processing (based on most recently processed item timestamp)

View File

@ -1,8 +1,8 @@
# `js-libp2p` Examples and Tutorials
In this folder, you can find a variety of examples to help you get started in using js-libp2p, in Node.js and in the Browser. Every example has a specific purpose and some incorporate a full tutorial that you can follow through, helping you expand your knowledge about libp2p and p2p networks in general.
In this folder, you can find a variety of examples to help you get started in using js-libp2p, in Node.js and in the Browser. Every example as a specific purpose and some of each incorporate a full tutorial that you can follow through, helping you expand your knowledge about libp2p and p2p networks in general.
Let us know if you find any issues, or if you want to contribute and add a new tutorial, feel free to submit a PR, thank you!
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!
## Understanding how libp2p works
@ -22,5 +22,3 @@ Let us know if you find any issues, or if you want to contribute and add a new t
- Running libp2p in the Electron (future)
- [The standard echo net example with libp2p](./echo)
- [A simple chat app with libp2p](./chat)
For go-libp2p examples, check out https://github.com/libp2p/go-libp2p-examples#examples-and-tutorials

View File

@ -4,44 +4,76 @@
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const Node = require('./libp2p-bundle')
const { stdinToStream, streamToConsole } = require('./stream')
const pull = require('pull-stream')
const async = require('async')
const Pushable = require('pull-pushable')
const p = Pushable()
let idListener
async function run() {
const [idDialer, idListener] = await Promise.all([
PeerId.createFromJSON(require('./peer-id-dialer')),
PeerId.createFromJSON(require('./peer-id-listener'))
])
// Create a new libp2p node on localhost with a randomly chosen port
const peerDialer = new PeerInfo(idDialer)
async.parallel([
(callback) => {
PeerId.createFromJSON(require('./peer-id-dialer'), (err, idDialer) => {
if (err) {
throw err
}
callback(null, idDialer)
})
},
(callback) => {
PeerId.createFromJSON(require('./peer-id-listener'), (err, idListener) => {
if (err) {
throw err
}
callback(null, idListener)
})
}
], (err, ids) => {
if (err) throw err
const peerDialer = new PeerInfo(ids[0])
peerDialer.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const nodeDialer = new Node({
peerInfo: peerDialer
})
// Create a PeerInfo with the listening peer's address
const peerListener = new PeerInfo(idListener)
const peerListener = new PeerInfo(ids[1])
idListener = ids[1]
peerListener.multiaddrs.add('/ip4/127.0.0.1/tcp/10333')
nodeDialer.start((err) => {
if (err) {
throw err
}
// Start the libp2p host
await nodeDialer.start()
console.log('Dialer ready, listening on:')
// Output this node's address
console.log('Dialer ready, listening on:')
peerListener.multiaddrs.forEach((ma) => {
console.log(ma.toString() + '/p2p/' + idListener.toB58String())
peerListener.multiaddrs.forEach((ma) => {
console.log(ma.toString() + '/p2p/' + idListener.toB58String())
})
nodeDialer.dialProtocol(peerListener, '/chat/1.0.0', (err, conn) => {
if (err) {
throw err
}
console.log('nodeA dialed to nodeB on protocol: /chat/1.0.0')
console.log('Type a message and see what happens')
// Write operation. Data sent as a buffer
pull(
p,
conn
)
// Sink, data converted from buffer to utf8 string
pull(
conn,
pull.map((data) => {
return data.toString('utf8').replace('\n', '')
}),
pull.drain(console.log)
)
process.stdin.setEncoding('utf8')
process.openStdin().on('data', (chunk) => {
var data = chunk.toString()
p.push(data)
})
})
})
// Dial to the remote peer (the "listener")
const { stream } = await nodeDialer.dialProtocol(peerListener, '/chat/1.0.0')
console.log('Dialer dialed to listener on protocol: /chat/1.0.0')
console.log('Type a message and see what happens')
// Send stdin to the stream
stdinToStream(stream)
// Read the stream and output to console
streamToConsole(stream)
}
run()
})

View File

@ -1,12 +1,41 @@
'use strict'
const TCP = require('libp2p-tcp')
const MulticastDNS = require('libp2p-mdns')
const WS = require('libp2p-websockets')
const Bootstrap = require('libp2p-bootstrap')
const spdy = require('libp2p-spdy')
const KadDHT = require('libp2p-kad-dht')
const mplex = require('libp2p-mplex')
const secio = require('libp2p-secio')
const defaultsDeep = require('@nodeutils/defaults-deep')
const libp2p = require('../../..')
function mapMuxers (list) {
return list.map((pref) => {
if (typeof pref !== 'string') {
return pref
}
switch (pref.trim().toLowerCase()) {
case 'spdy': return spdy
case 'mplex': return mplex
default:
throw new Error(pref + ' muxer not available')
}
})
}
function getMuxers (muxers) {
const muxerPrefs = process.env.LIBP2P_MUXER
if (muxerPrefs && !muxers) {
return mapMuxers(muxerPrefs.split(','))
} else if (muxers) {
return mapMuxers(muxers)
} else {
return [mplex, spdy]
}
}
class Node extends libp2p {
constructor (_options) {
const defaults = {
@ -15,8 +44,29 @@ class Node extends libp2p {
TCP,
WS
],
streamMuxer: [ mplex ],
connEncryption: [ secio ]
streamMuxer: getMuxers(_options.muxer),
connEncryption: [ secio ],
peerDiscovery: [
MulticastDNS,
Bootstrap
],
dht: KadDHT
},
config: {
peerDiscovery: {
mdns: {
interval: 10000,
enabled: false
},
bootstrap: {
interval: 10000,
enabled: false,
list: _options.bootstrapList
}
},
dht: {
kBucketSize: 20
}
}
}

View File

@ -4,38 +4,53 @@
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const Node = require('./libp2p-bundle.js')
const { stdinToStream, streamToConsole } = require('./stream')
const pull = require('pull-stream')
const Pushable = require('pull-pushable')
const p = Pushable()
async function run() {
// Create a new libp2p node with the given multi-address
const idListener = await PeerId.createFromJSON(require('./peer-id-listener'))
PeerId.createFromJSON(require('./peer-id-listener'), (err, idListener) => {
if (err) {
throw err
}
const peerListener = new PeerInfo(idListener)
peerListener.multiaddrs.add('/ip4/0.0.0.0/tcp/10333')
const nodeListener = new Node({
peerInfo: peerListener
})
// Log a message when a remote peer connects to us
nodeListener.on('peer:connect', (peerInfo) => {
console.log(peerInfo.id.toB58String())
nodeListener.start((err) => {
if (err) {
throw err
}
nodeListener.on('peer:connect', (peerInfo) => {
console.log(peerInfo.id.toB58String())
})
nodeListener.handle('/chat/1.0.0', (protocol, conn) => {
pull(
p,
conn
)
pull(
conn,
pull.map((data) => {
return data.toString('utf8').replace('\n', '')
}),
pull.drain(console.log)
)
process.stdin.setEncoding('utf8')
process.openStdin().on('data', (chunk) => {
var data = chunk.toString()
p.push(data)
})
})
console.log('Listener ready, listening on:')
peerListener.multiaddrs.forEach((ma) => {
console.log(ma.toString() + '/p2p/' + idListener.toB58String())
})
})
// Handle messages for the protocol
await nodeListener.handle('/chat/1.0.0', async ({ stream }) => {
// Send stdin to the stream
stdinToStream(stream)
// Read the stream and output to console
streamToConsole(stream)
})
// Start listening
await nodeListener.start()
// Output listen addresses to the console
console.log('Listener ready, listening on:')
peerListener.multiaddrs.forEach((ma) => {
console.log(ma.toString() + '/p2p/' + idListener.toB58String())
})
}
run()
})

View File

@ -1,40 +0,0 @@
'use strict'
/* eslint-disable no-console */
const pipe = require('it-pipe')
const lp = require('it-length-prefixed')
function stdinToStream(stream) {
// Read utf-8 from stdin
process.stdin.setEncoding('utf8')
pipe(
// Read from stdin (the source)
process.stdin,
// Encode with length prefix (so receiving side knows how much data is coming)
lp.encode(),
// Write to the stream (the sink)
stream.sink
)
}
function streamToConsole(stream) {
pipe(
// Read from the stream (the source)
stream.source,
// Decode length-prefixed data
lp.decode(),
// Sink function
async function (source) {
// For each chunk of data
for await (const msg of source) {
// Output the data as a utf8 string
console.log('> ' + msg.toString('utf8').replace('\n', ''))
}
}
)
}
module.exports = {
stdinToStream,
streamToConsole
}

View File

@ -44,7 +44,7 @@ class App extends Component {
isLoading: this.state.isLoading + 1
})
this.ipfs.cat(this.state.hash, (err, data) => {
this.ipfs.files.cat(this.state.hash, (err, data) => {
if (err) console.log('Error', err)
this.setState({

View File

@ -1,11 +1,14 @@
/* eslint-disable no-console */
'use strict'
const Libp2p = require('../../')
const libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
const Bootstrap = require('libp2p-bootstrap')
const waterfall = require('async/waterfall')
const defaultsDeep = require('@nodeutils/defaults-deep')
// Find this list at: https://github.com/ipfs/js-ipfs/blob/master/src/core/runtime/config-nodejs.json
const bootstrapers = [
@ -20,35 +23,51 @@ const bootstrapers = [
'/ip4/104.236.151.122/tcp/4001/p2p/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx'
]
;(async () => {
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [SECIO],
peerDiscovery: [Bootstrap]
},
config: {
peerDiscovery: {
bootstrap: {
interval: 60e3,
enabled: true,
list: bootstrapers
class MyBundle extends libp2p {
constructor (_options) {
const defaults = {
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ SECIO ],
peerDiscovery: [ Bootstrap ]
},
config: {
peerDiscovery: {
autoDial: true,
bootstrap: {
interval: 20e3,
enabled: true,
list: bootstrapers
}
}
}
}
})
node.peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
super(defaultsDeep(_options, defaults))
}
}
node.on('peer:connect', (peer) => {
console.log('Connection established to:', peer.id.toB58String()) // Emitted when a peer has been found
})
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 }
node.on('peer:discovery', (peer) => {
// No need to dial, autoDial is on
console.log('Discovered:', peer.id.toB58String())
})
await node.start()
})();
node.on('peer:connect', (peer) => {
console.log('Connection established to:', peer.id.toB58String())
})
})

View File

@ -1,45 +1,63 @@
/* eslint-disable no-console */
'use strict'
const Libp2p = require('../../')
const libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
const MulticastDNS = require('libp2p-mdns')
const waterfall = require('async/waterfall')
const parallel = require('async/parallel')
const defaultsDeep = require('@nodeutils/defaults-deep')
const createNode = async () => {
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [SECIO],
peerDiscovery: [MulticastDNS]
},
config: {
peerDiscovery: {
mdns: {
interval: 20e3,
enabled: true
class MyBundle extends libp2p {
constructor (_options) {
const defaults = {
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ SECIO ],
peerDiscovery: [ MulticastDNS ]
},
config: {
peerDiscovery: {
mdns: {
interval: 20e3,
enabled: true
}
}
}
}
})
node.peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
return node
super(defaultsDeep(_options, defaults))
}
}
;(async () => {
const [node1, node2] = await Promise.all([
createNode(),
createNode()
])
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.on('peer:discovery', (peer) => console.log('Discovered:', peer.id.toB58String()))
node2.on('peer:discovery', (peer) => console.log('Discovered:', peer.id.toB58String()))
await Promise.all([
node1.start(),
node2.start()
])
})();
})

View File

@ -10,32 +10,36 @@ These mechanisms save configuration and enable a node to operate without any exp
For this demo, we will connect to IPFS default bootstrapper nodes and so, we will need to support the same set of features those nodes have, that are: TCP, mplex and SECIO. You can see the complete example at [1.js](./1.js).
First, we create our libp2p node.
First, we create our libp2p bundle.
```JavaScript
const Libp2p = require('libp2p')
const Bootstrap = require('libp2p-bootstrap')
const node = Libp2p.create({
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ SECIO ],
peerDiscovery: [ Bootstrap ]
},
config: {
peerDiscovery: {
bootstrap: {
interval: 60e3,
enabled: true,
list: bootstrapers
const Bootstrap = require('libp2p-railing')
class MyBundle extends libp2p {
constructor (peerInfo) {
const defaults = {
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ SECIO ],
peerDiscovery: [ Bootstrap ]
},
config: {
peerDiscovery: {
bootstrap: {
interval: 2000,
enabled: true,
list: bootstrapers
}
}
}
}
super(defaultsDeep(_options, defaults))
}
})
}
```
In this configuration, we use a `bootstrappers` array listing peers to connect _on boot_. Here is the list used by js-ipfs and go-ipfs.
In this bundle, we use a `bootstrappers` array listing peers to connect _on boot_. Here is the list used by js-ipfs and go-ipfs.
```JavaScript
const bootstrapers = [
@ -54,37 +58,34 @@ const bootstrapers = [
Now, once we create and start the node, we can listen for events such as `peer:discovery` and `peer:connect`, these events tell us when we found a peer, independently of the discovery mechanism used and when we actually dialed to that peer.
```JavaScript
const node = await Libp2p.create({
peerInfo,
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ SECIO ],
peerDiscovery: [ Bootstrap ]
},
config: {
peerDiscovery: {
bootstrap: {
interval: 60e3,
enabled: true,
list: bootstrapers
}
}
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 }
// Emitted when a peer has been found
node.on('peer:discovery', (peer) => {
console.log('Discovered:', peer.id.toB58String())
// Note how we need to dial, even if just to warm up the Connection (by not
// picking any protocol) in order to get a full Connection. The Peer Discovery
// doesn't make any decisions for you.
node.dial(peer, () => {})
})
// Once the dial is complete, this event is emitted.
node.on('peer:connect', (peer) => {
console.log('Connection established to:', peer.id.toB58String())
})
})
node.peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
node.on('peer:connect', (peer) => {
console.log('Connection established to:', peer.id.toB58String()) // Emitted when a peer has been found
})
// Emitted when a peer has been found
node.on('peer:discovery', (peer) => {
console.log('Discovered:', peer.id.toB58String())
})
await node.start()
```
From running [1.js](./1.js), you should see the following:
@ -100,56 +101,76 @@ Discovered: QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64
Discovered: QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd
Discovered: QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3
Discovered: QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx
Connection established to: QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3
Connection established to: QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd
Connection established to: QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64
Connection established to: QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm
Connection established to: QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM
Connection established to: QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx
Connection established to: QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ
Connection established to: QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z
Connection established to: QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu
```
## 2. MulticastDNS to find other peers in the network
For this example, we need `libp2p-mdns`, go ahead and `npm install` it. You can find the complete solution at [2.js](./2.js).
Update your libp2p configuration to include MulticastDNS.
Update your libp2p bundle to include MulticastDNS.
```JavaScript
const Libp2p = require('libp2p')
const MulticastDNS = require('libp2p-mdns')
const createNode = () => {
return Libp2p.create({
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ SECIO ],
peerDiscovery: [ MulticastDNS ]
},
config: {
peerDiscovery: {
mdns: {
interval: 20e3,
enabled: true
class MyBundle extends libp2p {
constructor (peerInfo) {
const defaults = {
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ SECIO ],
peerDiscovery: [ MulticastDNS ]
},
config: {
peerDiscovery: {
mdns: {
// Run at 1s so we can observe more quickly, default is 10s
interval: 1000,
enabled: true
}
}
}
}
})
super(defaultsDeep(_options, defaults))
}
}
```
To observe it working, spawn two nodes.
```JavaScript
const [node1, node2] = await Promise.all([
createNode(),
createNode()
])
parallel([
(cb) => createNode(cb),
(cb) => createNode(cb)
], (err, nodes) => {
if (err) { throw err }
node1.on('peer:discovery', (peer) => console.log('Discovered:', peer.id.toB58String()))
node2.on('peer:discovery', (peer) => console.log('Discovered:', peer.id.toB58String()))
const node1 = nodes[0]
const node2 = nodes[1]
node1.on('peer:discovery', (peer) => console.log('Discovered:', peer.id.toB58String()))
node2.on('peer:discovery', (peer) => console.log('Discovered:', peer.id.toB58String()))
})
```
If you run this example, you will see the other peers being discovered.
If you run this example, you will see a continuous stream of each peer discovering each other.
```bash
> node 2.js
Discovered: QmSSbQpuKrxkoXHm1v4Pi35hPN5hUHMZoBoawEs2Nhvi8m
Discovered: QmRcXXhtG8vTqwVBRonKWtV4ovDoC1Fe56WYtcrw694eiJ
Discovered: QmSSbQpuKrxkoXHm1v4Pi35hPN5hUHMZoBoawEs2Nhvi8m
Discovered: QmRcXXhtG8vTqwVBRonKWtV4ovDoC1Fe56WYtcrw694eiJ
Discovered: QmSSbQpuKrxkoXHm1v4Pi35hPN5hUHMZoBoawEs2Nhvi8m
Discovered: QmRcXXhtG8vTqwVBRonKWtV4ovDoC1Fe56WYtcrw694eiJ
```
## 3. Where to find other Peer Discovery Mechanisms

View File

@ -8,54 +8,51 @@
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const Node = require('./libp2p-bundle')
const pipe = require('it-pipe')
const pull = require('pull-stream')
const async = require('async')
async function run() {
const [dialerId, listenerId] = await Promise.all([
PeerId.createFromJSON(require('./id-d')),
PeerId.createFromJSON(require('./id-l'))
])
async.parallel([
(cb) => PeerId.createFromJSON(require('./id-d'), cb),
(cb) => PeerId.createFromJSON(require('./id-l'), cb)
], (err, ids) => {
if (err) { throw err }
// Dialer
const dialerId = ids[0]
const dialerPeerInfo = new PeerInfo(dialerId)
dialerPeerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const dialerNode = new Node({
peerInfo: dialerPeerInfo
})
// Peer to Dial (the listener)
const listenerPeerInfo = new PeerInfo(listenerId)
// Peer to Dial
const listenerPeerInfo = new PeerInfo(ids[1])
const listenerId = ids[1]
const listenerMultiaddr = '/ip4/127.0.0.1/tcp/10333/p2p/' +
listenerId.toB58String()
listenerPeerInfo.multiaddrs.add(listenerMultiaddr)
// Start the dialer libp2p node
await dialerNode.start()
dialerNode.start((err) => {
if (err) { throw err }
console.log('Dialer ready, listening on:')
dialerPeerInfo.multiaddrs.forEach((ma) => console.log(ma.toString() +
'/p2p/' + dialerId.toB58String()))
console.log('Dialer ready, listening on:')
dialerPeerInfo.multiaddrs.forEach((ma) => console.log(ma.toString() +
'/p2p/' + dialerId.toB58String()))
// Dial the listener node
console.log('Dialing to peer:', listenerMultiaddr.toString())
const { stream } = await dialerNode.dialProtocol(listenerPeerInfo, '/echo/1.0.0')
console.log('Dialing to peer:', listenerMultiaddr.toString())
dialerNode.dialProtocol(listenerPeerInfo, '/echo/1.0.0', (err, conn) => {
if (err) { throw err }
console.log('nodeA dialed to nodeB on protocol: /echo/1.0.0')
console.log('nodeA dialed to nodeB on protocol: /echo/1.0.0')
pipe(
// Source data
['hey'],
// Write to the stream, and pass its output to the next function
stream,
// Sink function
async function (source) {
// For each chunk of data
for await (const data of source) {
// Output the data
console.log('received echo:', data.toString())
}
}
)
}
run()
pull(
pull.values(['hey']),
conn,
pull.collect((err, data) => {
if (err) { throw err }
console.log('received echo:', data.toString())
})
)
})
})
})

View File

@ -1,12 +1,41 @@
'use strict'
const TCP = require('libp2p-tcp')
const MulticastDNS = require('libp2p-mdns')
const WS = require('libp2p-websockets')
const Bootstrap = require('libp2p-bootstrap')
const spdy = require('libp2p-spdy')
const KadDHT = require('libp2p-kad-dht')
const mplex = require('libp2p-mplex')
const secio = require('libp2p-secio')
const defaultsDeep = require('@nodeutils/defaults-deep')
const libp2p = require('../../..')
function mapMuxers (list) {
return list.map((pref) => {
if (typeof pref !== 'string') {
return pref
}
switch (pref.trim().toLowerCase()) {
case 'spdy': return spdy
case 'mplex': return mplex
default:
throw new Error(pref + ' muxer not available')
}
})
}
function getMuxers (muxers) {
const muxerPrefs = process.env.LIBP2P_MUXER
if (muxerPrefs && !muxers) {
return mapMuxers(muxerPrefs.split(','))
} else if (muxers) {
return mapMuxers(muxers)
} else {
return [mplex, spdy]
}
}
class Node extends libp2p {
constructor (_options) {
const defaults = {
@ -15,8 +44,29 @@ class Node extends libp2p {
TCP,
WS
],
streamMuxer: [ mplex ],
connEncryption: [ secio ]
streamMuxer: getMuxers(_options.muxer),
connEncryption: [ secio ],
peerDiscovery: [
MulticastDNS,
Bootstrap
],
dht: KadDHT
},
config: {
peerDiscovery: {
mdns: {
interval: 10000,
enabled: false
},
bootstrap: {
interval: 10000,
enabled: false,
list: _options.bootstrapList
}
},
dht: {
kBucketSize: 20
}
}
}

View File

@ -8,34 +8,39 @@
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const Node = require('./libp2p-bundle')
const pipe = require('it-pipe')
const pull = require('pull-stream')
const series = require('async/series')
async function run() {
const listenerId = await PeerId.createFromJSON(require('./id-l'))
let listenerId
let listenerNode
// Listener libp2p node
const listenerPeerInfo = new PeerInfo(listenerId)
listenerPeerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/10333')
const listenerNode = new Node({
peerInfo: listenerPeerInfo
})
series([
(cb) => {
PeerId.createFromJSON(require('./id-l'), (err, id) => {
if (err) { return cb(err) }
listenerId = id
cb()
})
},
(cb) => {
const listenerPeerInfo = new PeerInfo(listenerId)
listenerPeerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/10333')
listenerNode = new Node({
peerInfo: listenerPeerInfo
})
// Log a message when we receive a connection
listenerNode.on('peer:connect', (peerInfo) => {
console.log('received dial to me from:', peerInfo.id.toB58String())
})
listenerNode.on('peer:connect', (peerInfo) => {
console.log('received dial to me from:', peerInfo.id.toB58String())
})
// Handle incoming connections for the protocol by piping from the stream
// back to itself (an echo)
await listenerNode.handle('/echo/1.0.0', ({ stream }) => pipe(stream.source, stream.sink))
// Start listening
await listenerNode.start()
listenerNode.handle('/echo/1.0.0', (protocol, conn) => pull(conn, conn))
listenerNode.start(cb)
}
], (err) => {
if (err) { throw err }
console.log('Listener ready, listening on:')
listenerNode.peerInfo.multiaddrs.forEach((ma) => {
console.log(ma.toString() + '/p2p/' + listenerId.toB58String())
})
}
run()
})

View File

@ -1,52 +1,63 @@
'use strict'
const Libp2p = require('../../')
const libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
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')
const defaultsDeep = require('@nodeutils/defaults-deep')
const pipe = require('it-pipe')
const createNode = async () => {
const peerInfo = await PeerInfo.create()
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const node = await Libp2p.create({
peerInfo,
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [SECIO]
class MyBundle extends libp2p {
constructor (_options) {
const defaults = {
modules: {
transport: [ TCP ],
streamMuxer: [ SPDY ],
connEncryption: [ SECIO ]
}
}
})
await node.start()
return node
super(defaultsDeep(_options, defaults))
}
}
;(async () => {
const [node1, node2] = await Promise.all([
createNode(),
createNode()
])
function createNode (callback) {
let node
node2.handle('/a-protocol', ({ stream }) => {
pipe(
stream,
async function (source) {
for await (const msg of source) {
console.log(msg.toString())
}
}
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()
)
})
const { stream } = await node1.dialProtocol(node2.peerInfo, '/a-protocol')
await pipe(
['This information is sent out encrypted to the other peer'],
stream
)
})();
node1.dialProtocol(node2.peerInfo, '/a-protocol', (err, conn) => {
if (err) { throw err }
pull(pull.values(['This information is sent out encrypted to the other peer']), conn)
})
})

View File

@ -4,7 +4,7 @@ libp2p can leverage the encrypted communications from the transports it uses (i.
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/p2p/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.
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
@ -12,21 +12,24 @@ We will build this example on top of example for [Protocol and Stream Multiplexi
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 configuration, all you have to do is:
To add it to your libp2p bundle, all you have to do is:
```JavaScript
const Libp2p = require('libp2p')
const SECIO = require('libp2p-secio')
const createNode = () => {
return Libp2p.create({
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
// Attach secio as the crypto channel to use
connEncryption: [ SECIO ]
class MyBundle extends libp2p {
constructor (peerInfo) {
const defaults = {
modules: {
transport: [ TCP ],
streamMuxer: [ SPDY ],
// Attach secio as the crypto channel to use
connEncryption: [ SECIO ]
}
}
})
super(defaultsDeep(_options, defaults))
}
}
```

View File

@ -1,55 +1,77 @@
/* eslint-disable no-console */
'use strict'
const Libp2p = require('../../')
const libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
const KadDHT = require('libp2p-kad-dht')
const defaultsDeep = require('@nodeutils/defaults-deep')
const waterfall = require('async/waterfall')
const parallel = require('async/parallel')
const delay = require('delay')
const createNode = async () => {
const peerInfo = await PeerInfo.create()
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const node = await Libp2p.create({
peerInfo,
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [SECIO],
dht: KadDHT
},
config: {
dht: {
enabled: true
class MyBundle extends libp2p {
constructor (_options) {
const defaults = {
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ SECIO ],
// we add the DHT module that will enable Peer and Content Routing
dht: KadDHT
},
config: {
dht: {
enabled: true,
kBucketSize: 20
}
}
}
})
await node.start()
return node
super(defaultsDeep(_options, defaults))
}
}
;(async () => {
const [node1, node2, node3] = await Promise.all([
createNode(),
createNode(),
createNode()
])
function createNode (callback) {
let node
await Promise.all([
node1.dial(node2.peerInfo),
node2.dial(node3.peerInfo)
])
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))
}
// The DHT routing tables need a moment to populate
await delay(100)
parallel([
(cb) => createNode(cb),
(cb) => createNode(cb),
(cb) => createNode(cb)
], (err, nodes) => {
if (err) { throw err }
const peer = await node1.peerRouting.findPeer(node3.peerInfo.id)
const node1 = nodes[0]
const node2 = nodes[1]
const node3 = nodes[2]
console.log('Found it, multiaddrs are:')
peer.multiaddrs.forEach((ma) => console.log(ma.toString()))
})();
parallel([
(cb) => node1.dial(node2.peerInfo, cb),
(cb) => node2.dial(node3.peerInfo, cb),
// Set up of the cons might take time
(cb) => setTimeout(cb, 300)
], (err) => {
if (err) { throw err }
node1.peerRouting.findPeer(node3.peerInfo.id, (err, peer) => {
if (err) { throw err }
console.log('Found it, multiaddrs are:')
peer.multiaddrs.forEach((ma) => console.log(ma.toString()))
})
})
})

View File

@ -1,61 +1,85 @@
/* eslint-disable no-console */
'use strict'
const Libp2p = require('../../')
const libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
const CID = require('cids')
const KadDHT = require('libp2p-kad-dht')
const defaultsDeep = require('@nodeutils/defaults-deep')
const waterfall = require('async/waterfall')
const parallel = require('async/parallel')
const all = require('it-all')
const delay = require('delay')
const createNode = async () => {
const peerInfo = await PeerInfo.create()
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const node = await Libp2p.create({
peerInfo,
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [SECIO],
dht: KadDHT
},
config: {
dht: {
enabled: true
class MyBundle extends libp2p {
constructor (_options) {
const defaults = {
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ SECIO ],
// we add the DHT module that will enable Peer and Content Routing
dht: KadDHT
},
config: {
dht: {
enabled: true,
kBucketSize: 20
}
}
}
})
await node.start()
return node
super(defaultsDeep(_options, defaults))
}
}
;(async () => {
const [node1, node2, node3] = await Promise.all([
createNode(),
createNode(),
createNode()
])
function createNode (callback) {
let node
await Promise.all([
node1.dial(node2.peerInfo),
node2.dial(node3.peerInfo)
])
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))
}
const cid = new CID('QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL')
await node1.contentRouting.provide(cid)
parallel([
(cb) => createNode(cb),
(cb) => createNode(cb),
(cb) => createNode(cb)
], (err, nodes) => {
if (err) { throw err }
console.log('Node %s is providing %s', node1.peerInfo.id.toB58String(), cid.toBaseEncodedString())
const node1 = nodes[0]
const node2 = nodes[1]
const node3 = nodes[2]
// wait for propagation
await delay(300)
parallel([
(cb) => node1.dial(node2.peerInfo, cb),
(cb) => node2.dial(node3.peerInfo, cb),
// Set up of the cons might take time
(cb) => setTimeout(cb, 300)
], (err) => {
if (err) { throw err }
const providers = await all(node3.contentRouting.findProviders(cid, { timeout: 3000 }))
const cid = new CID('QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL')
console.log('Found provider:', providers[0].id.toB58String())
})();
node1.contentRouting.provide(cid, (err) => {
if (err) { throw err }
console.log('Node %s is providing %s', node1.peerInfo.id.toB58String(), cid.toBaseEncodedString())
node3.contentRouting.findProviders(cid, 5000, (err, providers) => {
if (err) { throw err }
console.log('Found provider:', providers[0].id.toB58String())
})
})
})
})

View File

@ -10,27 +10,31 @@ Content Routing is the category of modules that offer a way to find where conten
This example builds on top of the [Protocol and Stream Muxing](../protocol-and-stream-muxing). We need to install `libp2p-kad-dht`, go ahead and `npm install libp2p-kad-dht`. If you want to see the final version, open [1.js](./1.js).
First, let's update our config to support Peer Routing and Content Routing.
First, let's update our bundle to support Peer Routing and Content Routing.
```JavaScript
const Libp2p = require('libp2p')
const KadDHT = require('libp2p-kad-dht')
const node = await Libp2p.create({
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ SECIO ],
// we add the DHT module that will enable Peer and Content Routing
dht: KadDHT
},
config: {
dht: {
// dht must be enabled
enabled: true
class MyBundle extends libp2p {
constructor (_options) {
const defaults = {
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ SECIO ],
// we add the DHT module that will enable Peer and Content Routing
dht: KadDHT
},
config: {
dht: {
// dht must be enabled
enabled: true,
kBucketSize: 20
}
}
}
super(defaultsDeep(_options, defaults))
}
})
}
```
Once that is done, we can use the createNode function we developed in the previous example to create 3 nodes. Connect node 1 to node 2 and node 2 to node 3. We will use node 2 as a way to find the whereabouts of node 3
@ -40,18 +44,22 @@ const node1 = nodes[0]
const node2 = nodes[1]
const node3 = nodes[2]
await Promise.all([
node1.dial(node2.peerInfo),
node2.dial(node3.peerInfo)
])
parallel([
(cb) => node1.dial(node2.peerInfo, cb),
(cb) => node2.dial(node3.peerInfo, cb),
// Set up of the cons might take time
(cb) => setTimeout(cb, 100)
], (err) => {
if (err) { throw err }
// Set up of the cons might take time
await delay(100)
//
node1.peerRouting.findPeer(node3.peerInfo.id, (err, peer) => {
if (err) { throw err }
const peer = await node1.peerRouting.findPeer(node3.peerInfo.id)
console.log('Found it, multiaddrs are:')
peer.multiaddrs.forEach((ma) => console.log(ma.toString()))
console.log('Found it, multiaddrs are:')
peer.multiaddrs.forEach((ma) => console.log(ma.toString()))
})
})
```
You should see the output being something like:
@ -74,12 +82,17 @@ You can find this example completed in [2.js](./2.js), however as you will see i
Instead of calling `peerRouting.findPeer`, we will use `contentRouting.provide` and `contentRouting.findProviders`.
```JavaScript
await node1.contentRouting.provide(cid)
console.log('Node %s is providing %s', node1.peerInfo.id.toB58String(), cid.toBaseEncodedString())
node1.contentRouting.provide(cid, (err) => {
if (err) { throw err }
const provs = await all(node3.contentRouting.findProviders(cid, { timeout: 5000 }))
console.log('Node %s is providing %s', node1.peerInfo.id.toB58String(), cid.toBaseEncodedString())
console.log('Found provider:', providers[0].id.toB58String())
node3.contentRouting.findProviders(cid, 5000, (err, providers) => {
if (err) { throw err }
console.log('Found provider:', providers[0].id.toB58String())
})
})
```
The output of your program should look like:

View File

@ -1,5 +1,5 @@
# Private Networking
This example shows how to set up a private network of libp2p nodes.
# Private Networking with IPFS
This example shows how to set up a private network of IPFS nodes.
## Setup
Install dependencies:

145
examples/pnet-ipfs/index.js Normal file
View File

@ -0,0 +1,145 @@
/* eslint no-console: ["off"] */
'use strict'
const IPFS = require('ipfs')
const assert = require('assert').strict
const writeKey = require('libp2p-pnet').generate
const path = require('path')
const fs = require('fs')
const privateLibp2pBundle = require('./libp2p-bundle')
const { mkdirp } = require('./utils')
// Create two separate repo paths so we can run two nodes and check their output
const repo1 = path.resolve('./tmp', 'repo1', '.ipfs')
const repo2 = path.resolve('./tmp', 'repo2', '.ipfs')
mkdirp(repo1)
mkdirp(repo2)
// Create a buffer and write the swarm key to it
const swarmKey = Buffer.alloc(95)
writeKey(swarmKey)
// This key is for the `TASK` mentioned in the writeFileSync calls below
const otherSwarmKey = Buffer.alloc(95)
writeKey(otherSwarmKey)
// Add the swarm key to both repos
const swarmKey1Path = path.resolve(repo1, 'swarm.key')
const swarmKey2Path = path.resolve(repo2, 'swarm.key')
fs.writeFileSync(swarmKey1Path, swarmKey)
// TASK: switch the commented out line below so we're using a different key, to see the nodes fail to connect
fs.writeFileSync(swarmKey2Path, swarmKey)
// fs.writeFileSync(swarmKey2Path, otherSwarmKey)
// Create the first ipfs node
const node1 = new IPFS({
repo: repo1,
libp2p: privateLibp2pBundle(swarmKey1Path),
config: {
Addresses: {
// Set the swarm address so we dont get port collision on the nodes
Swarm: ['/ip4/0.0.0.0/tcp/9101']
}
}
})
// Create the second ipfs node
const node2 = new IPFS({
repo: repo2,
libp2p: privateLibp2pBundle(swarmKey2Path),
config: {
Addresses: {
// Set the swarm address so we dont get port collision on the nodes
Swarm: ['/ip4/0.0.0.0/tcp/9102']
}
}
})
console.log('auto starting the nodes...')
// `nodesStarted` keeps track of how many of our nodes have started
let nodesStarted = 0
/**
* Calls `connectAndTalk` when both nodes have started
* @returns {void}
*/
const didStartHandler = () => {
if (++nodesStarted === 2) {
// If both nodes are up, start talking
connectAndTalk()
}
}
/**
* Exits the process when all started nodes have stopped
* @returns {void}
*/
const didStopHandler = () => {
if (--nodesStarted < 1) {
console.log('all nodes stopped, exiting.')
process.exit(0)
}
}
/**
* Stops the running nodes
* @param {Error} err An optional error to log to the console
* @returns {void}
*/
const doStop = (err) => {
if (err) {
console.error(err)
}
console.log('Shutting down...')
node1.stop()
node2.stop()
}
/**
* Connects the IPFS nodes and transfers data between them
* @returns {void}
*/
const connectAndTalk = async () => {
console.log('connecting the nodes...')
const node2Id = await node2.id()
const dataToAdd = Buffer.from('Hello, private friend!')
// Connect the nodes
// This will error when different private keys are used
try {
await node1.swarm.connect(node2Id.addresses[0])
} catch (err) {
return doStop(err)
}
console.log('the nodes are connected, let\'s add some data')
// Add some data to node 1
let addedCID
try {
addedCID = await node1.files.add(dataToAdd)
} catch (err) {
return doStop(err)
}
console.log(`added ${addedCID[0].path} to the node1`)
// Retrieve the data from node 2
let cattedData
try {
cattedData = await node2.files.cat(addedCID[0].path)
} catch (err) {
return doStop(err)
}
assert.deepEqual(cattedData.toString(), dataToAdd.toString(), 'Should have equal data')
console.log(`successfully retrieved "${dataToAdd.toString()}" from node2`)
doStop()
}
// Wait for the nodes to boot
node1.once('start', didStartHandler)
node2.once('start', didStartHandler)
// Listen for the nodes stopping so we can cleanup
node1.once('stop', didStopHandler)
node2.once('stop', didStopHandler)

View File

@ -0,0 +1,60 @@
'use strict'
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const fs = require('fs')
const Protector = require('libp2p-pnet')
/**
* Options for the libp2p bundle
* @typedef {Object} libp2pBundle~options
* @property {PeerInfo} peerInfo - The PeerInfo of the IPFS node
* @property {PeerBook} peerBook - The PeerBook of the IPFS node
* @property {Object} config - The config of the IPFS node
* @property {Object} options - The options given to the IPFS node
*/
/**
* privateLibp2pBundle returns a libp2p bundle function that will use the swarm
* key at the given `swarmKeyPath` to create the Protector
*
* @param {string} swarmKeyPath The path to our swarm key
* @returns {libp2pBundle} Returns a libp2pBundle function for use in IPFS creation
*/
const privateLibp2pBundle = (swarmKeyPath) => {
/**
* This is the bundle we will use to create our fully customized libp2p bundle.
*
* @param {libp2pBundle~options} opts The options to use when generating the libp2p node
* @returns {Libp2p} Our new libp2p node
*/
const libp2pBundle = (opts) => {
// Set convenience variables to clearly showcase some of the useful things that are available
const peerInfo = opts.peerInfo
const peerBook = opts.peerBook
// Build and return our libp2p node
return new Libp2p({
peerInfo,
peerBook,
modules: {
transport: [TCP], // We're only using the TCP transport for this example
streamMuxer: [MPLEX], // We're only using mplex muxing
// Let's make sure to use identifying crypto in our pnet since the protector doesn't
// care about node identity, and only the presence of private keys
connEncryption: [SECIO],
// Leave peer discovery empty, we don't want to find peers. We could omit the property, but it's
// being left in for explicit readability.
// We should explicitly dial pnet peers, or use a custom discovery service for finding nodes in our pnet
peerDiscovery: [],
connProtector: new Protector(fs.readFileSync(swarmKeyPath))
}
})
}
return libp2pBundle
}
module.exports = privateLibp2pBundle

View File

@ -11,9 +11,11 @@
"author": "",
"license": "ISC",
"dependencies": {
"libp2p": "../..",
"libp2p-mplex": "^0.9.3",
"libp2p-secio": "^0.12.1",
"libp2p-tcp": "^0.14.2"
"ipfs": "~0.32.3",
"libp2p": "~0.23.1",
"libp2p-mplex": "~0.8.2",
"libp2p-pnet": "../../",
"libp2p-secio": "~0.10.0",
"libp2p-tcp": "~0.13.0"
}
}

View File

@ -1,50 +0,0 @@
/* eslint no-console: ["off"] */
'use strict'
const { generate } = require('libp2p/src/pnet')
const privateLibp2pNode = require('./libp2p-node')
const pipe = require('it-pipe')
// Create a buffer and write the swarm key to it
const swarmKey = Buffer.alloc(95)
generate(swarmKey)
// This key is for testing a different key not working
const otherSwarmKey = Buffer.alloc(95)
generate(otherSwarmKey)
;(async () => {
const node1 = await privateLibp2pNode(swarmKey)
// TASK: switch the commented out line below so we're using a different key, to see the nodes fail to connect
const node2 = await privateLibp2pNode(swarmKey)
// const node2 = await privateLibp2pNode(otherSwarmKey)
await Promise.all([
node1.start(),
node2.start()
])
console.log('nodes started...')
await node1.dial(node2.peerInfo)
node2.handle('/private', ({ stream }) => {
pipe(
stream,
async function (source) {
for await (const msg of source) {
console.log(msg.toString())
}
}
)
})
const { stream } = await node1.dialProtocol(node2.peerInfo, '/private')
await pipe(
['This message is sent on a private network'],
stream
)
})();

View File

@ -1,36 +0,0 @@
'use strict'
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const Protector = require('libp2p/src/pnet')
/**
* privateLibp2pNode returns a libp2p node function that will use the swarm
* key at the given `swarmKeyPath` to create the Protector
*
* @param {Buffer} swarmKey
* @returns {Promise<libp2p>} Returns a libp2pNode function for use in IPFS creation
*/
const privateLibp2pNode = async (swarmKeyPath) => {
const node = await Libp2p.create({
modules: {
transport: [TCP], // We're only using the TCP transport for this example
streamMuxer: [MPLEX], // We're only using mplex muxing
// Let's make sure to use identifying crypto in our pnet since the protector doesn't
// care about node identity, and only the presence of private keys
connEncryption: [SECIO],
// Leave peer discovery empty, we don't want to find peers. We could omit the property, but it's
// being left in for explicit readability.
// We should explicitly dial pnet peers, or use a custom discovery service for finding nodes in our pnet
peerDiscovery: [],
connProtector: new Protector(swarmKeyPath)
}
})
node.peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
return node
}
module.exports = privateLibp2pNode

View File

@ -1,79 +1,102 @@
'use strict'
const Libp2p = require('../../')
const libp2p = require('../../')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
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')
const defaultsDeep = require('@nodeutils/defaults-deep')
const pipe = require('it-pipe')
const createNode = async () => {
const peerInfo = await PeerInfo.create()
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const node = await Libp2p.create({
peerInfo,
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
class MyBundle extends libp2p {
constructor (_options) {
const defaults = {
modules: {
transport: [ TCP ]
}
}
})
await node.start()
return node
super(defaultsDeep(_options, defaults))
}
}
;(async () => {
const [node1, node2] = await Promise.all([
createNode(),
createNode()
])
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', ({ stream }) => {
pipe(
stream,
async function (source) {
for await (const msg of source) {
console.log(msg.toString())
}
}
node2.handle('/your-protocol', (protocol, conn) => {
pull(
conn,
pull.map((v) => v.toString()),
pull.log()
)
})
// multiple protocols
// semver matching
/*
node2.handle(['/another-protocol/1.0.0', '/another-protocol/2.0.0'], ({ protocol, stream }) => {
if (protocol === '/another-protocol/2.0.0') {
// handle backwards compatibility
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)
}
pipe(
stream,
async function (source) {
for await (const msg of source) {
console.log(msg.toString())
}
}
)
})
*/
const { stream } = await node1.dialProtocol(node2.peerInfo, ['/your-protocol'])
await pipe(
['my own protocol, wow!'],
stream
)
node1.dialProtocol(node2.peerInfo, '/your-protocol', (err, conn) => {
if (err) { throw err }
pull(pull.values(['my own protocol, wow!']), conn)
})
/*
const { stream } = node1.dialProtocol(node2.peerInfo, ['/another-protocol/1.0.0'])
await pipe(
['my own protocol, wow!'],
stream
)
node1.dialProtocol(node2.peerInfo, '/another-protocol/1.0.0', (err, conn) => {
if (err) { throw err }
pull(pull.values(['semver me please']), conn)
})
*/
})();
/*
node1.dialProtocol(node2.peerInfo, '/custom-match-func/some-query', (err, conn) => {
if (err) { throw err }
pull(pull.values(['do I fall into your criteria?']), conn)
})
*/
})

View File

@ -1,63 +1,83 @@
'use strict'
const Libp2p = require('../../')
const libp2p = require('../../')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
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')
const defaultsDeep = require('@nodeutils/defaults-deep')
const pipe = require('it-pipe')
const createNode = async () => {
const peerInfo = await PeerInfo.create()
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const node = await Libp2p.create({
peerInfo,
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
class MyBundle extends libp2p {
constructor (_options) {
const defaults = {
modules: {
transport: [ TCP ],
streamMuxer: [ SPDY ]
}
}
})
await node.start()
return node
super(defaultsDeep(_options, defaults))
}
}
;(async () => {
const [node1, node2] = await Promise.all([
createNode(),
createNode()
])
function createNode (callback) {
let node
node2.handle(['/a', '/b'], ({ protocol, stream }) => {
pipe(
stream,
async function (source) {
for await (const msg of source) {
console.log(`from: ${protocol}, msg: ${msg.toString()}`)
}
}
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()
)
})
const { stream: stream1 } = await node1.dialProtocol(node2.peerInfo, ['/a'])
await pipe(
['protocol (a)'],
stream1
)
node2.handle('/b', (protocol, conn) => {
pull(
conn,
pull.map((v) => v.toString()),
pull.log()
)
})
const { stream: stream2 } = await node1.dialProtocol(node2.peerInfo, ['/b'])
await pipe(
['protocol (b)'],
stream2
)
const { stream: stream3 } = await node1.dialProtocol(node2.peerInfo, ['/b'])
await pipe(
['another stream on protocol (b)'],
stream3
)
})();
series([
(cb) => node1.dialProtocol(node2.peerInfo, '/a', (err, conn) => {
if (err) { throw err }
pull(pull.values(['protocol (a)']), conn)
cb()
}),
(cb) => node1.dialProtocol(node2.peerInfo, '/b', (err, conn) => {
if (err) { throw err }
pull(pull.values(['protocol (b)']), conn)
cb()
}),
(cb) => node1.dialProtocol(node2.peerInfo, '/b', (err, conn) => {
if (err) { throw err }
pull(pull.values(['another conn on protocol (b)']), conn)
cb()
})
])
})

View File

@ -1,69 +1,88 @@
/* eslint-disable no-console */
'use strict'
const Libp2p = require('../../')
const libp2p = require('../../')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
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')
const defaultsDeep = require('@nodeutils/defaults-deep')
const pipe = require('it-pipe')
const createNode = async () => {
const peerInfo = await PeerInfo.create()
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const node = await Libp2p.create({
peerInfo,
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
class MyBundle extends libp2p {
constructor (_options) {
const defaults = {
modules: {
transport: [ TCP ],
streamMuxer: [ SPDY ]
}
}
})
await node.start()
return node
super(defaultsDeep(_options, defaults))
}
}
;(async () => {
const [node1, node2] = await Promise.all([
createNode(),
createNode()
])
node1.handle('/node-1', ({ stream }) => {
pipe(
stream,
async function (source) {
for await (const msg of source) {
console.log(msg.toString())
}
}
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', ({ stream }) => {
pipe(
stream,
async function (source) {
for await (const msg of source) {
console.log(msg.toString())
}
}
node2.handle('/node-2', (protocol, conn) => {
pull(
conn,
pull.map((v) => v.toString()),
pull.log()
)
})
const { stream: stream1 } = await node1.dialProtocol(node2.peerInfo, ['/node-2'])
await pipe(
['from 1 to 2'],
stream1
)
const { stream: stream2 } = await node2.dialProtocol(node1.peerInfo, ['/node-1'])
await pipe(
['from 2 to 1'],
stream2
)
})();
series([
(cb) => node1.dialProtocol(node2.peerInfo, '/node-2', (err, conn) => {
if (err) { throw err }
pull(pull.values(['from 1 to 2']), conn)
cb()
}),
(cb) => node2.dialProtocol(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()))
})
})

View File

@ -6,30 +6,23 @@ The feature of agreeing on a protocol over an established connection is what we
# 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`, `it-pipe`, `it-buffer` and `streaming-iterables`. This example reuses the base left by the [Transports](../transports) example. You can see the complete solution at [1.js](./1.js).
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 pipe = require('it-pipe')
const { map } = require('streaming-iterables')
const { toBuffer } = require('it-buffer')
// ...
const node1 = nodes[0]
const node2 = nodes[1]
// Here we are telling libp2p that if someone dials this node to talk with the `/your-protocol`
// multicodec, the protocol identifier, please call this handler and give it the stream
// 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', ({ stream }) => {
pipe(
stream,
source => (async function () {
for await (const msg of source) {
console.log(msg.toString())
}
})()
node2.handle('/your-protocol', (protocol, conn) => {
pull(
conn,
pull.map((v) => v.toString()),
pull.log()
)
})
```
@ -37,54 +30,53 @@ node2.handle('/your-protocol', ({ stream }) => {
After the protocol is _handled_, now we can dial to it.
```JavaScript
const { stream } = await node1.dialProtocol(node2.peerInfo, ['/your-protocol'])
await pipe(
['my own protocol, wow!'],
stream
)
node1.dialProtocol(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', ({ stream }) => {
pipe(
stream,
async function (source) {
for await (const msg of source) {
console.log(msg.toString())
}
}
node2.handle('/another-protocol/1.0.1', (protocol, conn) => {
pull(
conn,
pull.map((v) => v.toString()),
pull.log()
)
})
// ...
const { stream } = await node1.dialProtocol(node2.peerInfo, ['/another-protocol/1.0.0'])
await pipe(
['my own protocol, wow!'],
stream
)
node1.dialProtocol(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 provide multiple protocols for the same handler. If you have a backwards incompatible change, but it only requires minor changes to the code, you may prefer to do protocol checking instead of having multiple handlers
There is still one last feature, you can create your custom match functions.
```JavaScript
node2.handle(['/another-protocol/1.0.0', '/another-protocol/2.0.0'], ({ protocol, stream }) => {
if (protocol === '/another-protocol/2.0.0') {
// handle backwards compatibility
}
pipe(
stream,
async function (source) {
for await (const msg of source) {
console.log(msg.toString())
}
}
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.dialProtocol(node2.peerInfo, '/custom-match-func/some-query', (err, conn) => {
if (err) { throw err }
pull(pull.values(['do I fall into your criteria?']), conn)
})
```
@ -92,68 +84,77 @@ Try all of this out by executing [1.js](./1.js).
# 2. Reuse existing connection
The examples 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.
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 an 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.
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 [libp2p-mplex](https://github.com/libp2p/js-libp2p-mplex) and pluging it in is as easy as adding a transport. Let's revisit our libp2p configuration.
Currently, we have two available [libp2p-spdy](https://github.com/libp2p/js-libp2p-spdy) and [libp2p-mplex](https://github.com/libp2p/js-libp2p-mplex) and pluging them in is as easy as adding another transport. Let's revisit our libp2p bundle.
```JavaScript
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SPDY = require('libp2p-spdy')
//...
const createNode = () => {
return Libp2p.create({
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ]
class MyBundle extends libp2p {
constructor (_options) {
const defaults = {
modules: {
transport: [ 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
streamMuxer: [ SPDY ]
}
}
})
super(defaultsDeep(_options, defaults))
}
}
```
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', '/b'], ({ protocol, stream }) => {
pipe(
stream,
async function (source) {
for await (const msg of source) {
console.log(`from: ${protocol}, msg: ${msg.toString()}`)
}
}
node2.handle('/a', (protocol, conn) => {
pull(
conn,
pull.map((v) => v.toString()),
pull.log()
)
})
const { stream } = await node1.dialProtocol(node2.peerInfo, ['/a'])
await pipe(
['protocol (a)'],
stream
)
node2.handle('/b', (protocol, conn) => {
pull(
conn,
pull.map((v) => v.toString()),
pull.log()
)
})
const { stream: stream2 } = await node1.dialProtocol(node2.peerInfo, ['/b'])
await pipe(
['protocol (b)'],
stream2
)
const { stream: stream3 } = await node1.dialProtocol(node2.peerInfo, ['/b'])
await pipe(
['another stream on protocol (b)'],
stream3
)
series([
(cb) => node1.dialProtocol(node2.peerInfo, '/a', (err, conn) => {
if (err) { throw err }
pull(pull.values(['protocol (a)']), conn)
cb()
}),
(cb) => node1.dialProtocol(node2.peerInfo, '/b', (err, conn) => {
if (err) { throw err }
pull(pull.values(['protocol (b)']), conn)
cb()
}),
(cb) => node1.dialProtocol(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
from: /a, msg: protocol (a)
from: /b, msg: protocol (b)
from: /b, msg: another stream on protocol (b)
protocol (a)
protocol (b)
another protocol (b)
```
# 3. Bidirectional connections
@ -167,5 +168,8 @@ You can see this working on example [3.js](./3.js). The result should look like
```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/p2p/QmZwMKTo6wG4Te9A6M2eJnWDpR8uhsGed4YRegnV5DcKiv
node 2 to node 1: /ip4/127.0.0.1/tcp/50630/p2p/QmRgormJQeDyXhDKma11eUtksoh8vWmeBoxghVt4meauW9
from 2 to 1
```

View File

@ -1,51 +1,103 @@
/* eslint-disable no-console */
'use strict'
const Libp2p = require('../../')
const libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
const MulticastDNS = require('libp2p-mdns')
const Gossipsub = require('libp2p-gossipsub')
const defaultsDeep = require('@nodeutils/defaults-deep')
const waterfall = require('async/waterfall')
const parallel = require('async/parallel')
const series = require('async/series')
const createNode = async () => {
const peerInfo = await PeerInfo.create()
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const node = await Libp2p.create({
peerInfo,
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [SECIO],
pubsub: Gossipsub
class MyBundle extends libp2p {
constructor (_options) {
const defaults = {
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ SECIO ],
peerDiscovery: [ MulticastDNS ],
pubsub: Gossipsub
},
config: {
peerDiscovery: {
mdns: {
interval: 2000,
enabled: true
}
},
pubsub: {
enabled: true,
emitSelf: true
}
}
}
})
await node.start()
return node
super(defaultsDeep(_options, defaults))
}
}
;(async () => {
const topic = 'news'
function createNode (callback) {
let node
const [node1, node2] = await Promise.all([
createNode(),
createNode(),
])
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))
}
await node1.dial(node2.peerInfo)
parallel([
(cb) => createNode(cb),
(cb) => createNode(cb)
], (err, nodes) => {
if (err) { throw err }
await node1.pubsub.subscribe(topic, (msg) => {
console.log(`node1 received: ${msg.data.toString()}`)
const node1 = nodes[0]
const node2 = nodes[1]
node1.once('peer:connect', (peer) => {
console.log('connected to %s', peer.id.toB58String())
series([
// node1 subscribes to "news"
(cb) => node1.pubsub.subscribe(
'news',
(msg) => console.log(`node1 received: ${msg.data.toString()}`),
cb
),
(cb) => setTimeout(cb, 500),
// node2 subscribes to "news"
(cb) => node2.pubsub.subscribe(
'news',
(msg) => console.log(`node2 received: ${msg.data.toString()}`),
cb
),
(cb) => setTimeout(cb, 500),
// node2 publishes "news" every second
(cb) => {
setInterval(() => {
node2.pubsub.publish(
'news',
Buffer.from('Bird bird bird, bird is the word!'),
(err) => {
if (err) { throw err }
}
)
}, 1000)
cb()
},
], (err) => {
if (err) { throw err }
})
})
await node2.pubsub.subscribe(topic, (msg) => {
console.log(`node2 received: ${msg.data.toString()}`)
})
// node2 publishes "news" every second
setInterval(() => {
node2.pubsub.publish(topic, Buffer.from('Bird bird bird, bird is the word!'))
}, 1000)
})();
})

View File

@ -14,47 +14,44 @@ For this example, we will use MulticastDNS for automatic Peer Discovery. This ex
Using PubSub is super simple, you only need to provide the implementation of your choice and you are ready to go. No need for extra configuration.
First, let's update our libp2p configuration with a pubsub implementation.
```JavaScript
const Libp2p = require('libp2p')
const Gossipsub = require('libp2p-gossipsub')
node1.once('peer:connect', (peer) => {
console.log('connected to %s', peer.id.toB58String())
const node = await Libp2p.create({
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ SECIO ],
// we add the Pubsub module we want
pubsub: Gossipsub
}
series([
// node1 subscribes to "news"
(cb) => node1.pubsub.subscribe(
'news',
(msg) => console.log(`node1 received: ${msg.data.toString()}`),
cb
),
(cb) => setTimeout(cb, 500),
// node2 subscribes to "news"
(cb) => node2.pubsub.subscribe(
'news',
(msg) => console.log(`node2 received: ${msg.data.toString()}`),
cb
),
(cb) => setTimeout(cb, 500),
// node2 publishes "news" every second
(cb) => {
setInterval(() => {
node2.pubsub.publish(
'news',
Buffer.from('Bird bird bird, bird is the word!'),
(err) => {
if (err) { throw err }
}
)
}, 1000)
cb()
},
], (err) => {
if (err) { throw err }
})
})
```
Once that is done, we only need to create a few libp2p nodes, connect them and everything is ready to start using pubsub.
```JavaScript
const topic = 'news'
const node1 = nodes[0]
const node2 = nodes[1]
await node1.dial(node2.peerInfo)
await node1.pubsub.subscribe(topic, (msg) => {
console.log(`node1 received: ${msg.data.toString()}`)
})
await node2.pubsub.subscribe(topic, (msg) => {
console.log(`node2 received: ${msg.data.toString()}`)
})
// node2 publishes "news" every second
setInterval(() => {
node2.pubsub.publish(topic, Buffer.from('Bird bird bird, bird is the word!'))
}, 1000)
```
The output of the program should look like:
```
@ -71,6 +68,12 @@ You can change the pubsub `emitSelf` option if you don't want the publishing nod
```JavaScript
const defaults = {
config: {
peerDiscovery: {
mdns: {
interval: 2000,
enabled: true
}
},
pubsub: {
enabled: true,
emitSelf: false

View File

@ -1,33 +1,39 @@
/* eslint-disable no-console */
'use strict'
const Libp2p = require('../..')
const libp2p = require('../../')
const TCP = require('libp2p-tcp')
const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
const waterfall = require('async/waterfall')
const defaultsDeep = require('@nodeutils/defaults-deep')
const createNode = async (peerInfo) => {
// 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')
const node = await Libp2p.create({
peerInfo,
modules: {
transport: [TCP],
connEncryption: [SECIO]
class MyBundle extends libp2p {
constructor (_options) {
const defaults = {
modules: {
transport: [
TCP
]
}
}
})
await node.start()
return node
super(defaultsDeep(_options, defaults))
}
}
;(async () => {
const peerInfo = await PeerInfo.create()
const node = await createNode(peerInfo)
let node
waterfall([
(cb) => PeerInfo.create(cb),
(peerInfo, cb) => {
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
node = new MyBundle({ peerInfo: peerInfo })
node.start(cb)
}
], (err) => {
if (err) { throw err }
console.log('node has started (true/false):', node.isStarted())
console.log('listening on:')
node.peerInfo.multiaddrs.forEach((ma) => console.log(ma.toString()))
})();
})

View File

@ -1,31 +1,39 @@
/* eslint-disable no-console */
'use strict'
const Libp2p = require('../..')
const libp2p = require('../../')
const TCP = require('libp2p-tcp')
const SECIO = require('libp2p-secio')
const MPLEX = require('libp2p-mplex')
const PeerInfo = require('peer-info')
const waterfall = require('async/waterfall')
const defaultsDeep = require('@nodeutils/defaults-deep')
const parallel = require('async/parallel')
const pull = require('pull-stream')
const pipe = require('it-pipe')
const concat = require('it-concat')
const createNode = async (peerInfo) => {
// 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')
const node = await Libp2p.create({
peerInfo,
modules: {
transport: [TCP],
connEncryption: [SECIO],
streamMuxer: [MPLEX]
class MyBundle extends libp2p {
constructor (_options) {
const defaults = {
modules: {
transport: [
TCP
]
}
}
})
await node.start()
return node
super(defaultsDeep(_options, defaults))
}
}
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: peerInfo })
node.start(cb)
}
], (err) => callback(err, node))
}
function printAddrs (node, number) {
@ -33,31 +41,29 @@ function printAddrs (node, number) {
node.peerInfo.multiaddrs.forEach((ma) => console.log(ma.toString()))
}
;(async () => {
const [peerInfo1, peerInfo2] = await Promise.all([
PeerInfo.create(),
PeerInfo.create()
])
const [node1, node2] = await Promise.all([
createNode(peerInfo1),
createNode(peerInfo2)
])
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', async ({ stream }) => {
const result = await pipe(
stream,
concat
node2.handle('/print', (protocol, conn) => {
pull(
conn,
pull.map((v) => v.toString()),
pull.log()
)
console.log(result.toString())
})
const { stream } = await node1.dialProtocol(node2.peerInfo, '/print')
node1.dialProtocol(node2.peerInfo, '/print', (err, conn) => {
if (err) { throw err }
await pipe(
['Hello', ' ', 'p2p', ' ', 'world', '!'],
stream
)
})();
pull(pull.values(['Hello', ' ', 'p2p', ' ', 'world', '!']), conn)
})
})

View File

@ -1,62 +1,70 @@
/* eslint-disable no-console */
'use strict'
const Libp2p = require('../..')
const libp2p = require('../../')
const TCP = require('libp2p-tcp')
const WebSockets = require('libp2p-websockets')
const SECIO = require('libp2p-secio')
const MPLEX = require('libp2p-mplex')
const PeerInfo = require('peer-info')
const waterfall = require('async/waterfall')
const defaultsDeep = require('@nodeutils/defaults-deep')
const parallel = require('async/parallel')
const pull = require('pull-stream')
const pipe = require('it-pipe')
const createNode = async (peerInfo, transports, multiaddrs = []) => {
if (!Array.isArray(multiaddrs)) {
multiaddrs = [multiaddrs]
}
multiaddrs.forEach((addr) => peerInfo.multiaddrs.add(addr))
const node = await Libp2p.create({
peerInfo,
modules: {
transport: transports,
connEncryption: [SECIO],
streamMuxer: [MPLEX]
class MyBundle extends libp2p {
constructor (_options) {
const defaults = {
modules: {
transport: [
TCP,
WebSockets
]
}
}
})
await node.start()
return node
super(defaultsDeep(_options, defaults))
}
}
function printAddrs(node, number) {
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: 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 ({ stream }) {
pipe(
stream,
async function (source) {
for await (const msg of source) {
console.log(msg.toString())
}
}
function print (protocol, conn) {
pull(
conn,
pull.map((v) => v.toString()),
pull.log()
)
}
;(async () => {
const [peerInfo1, peerInfo2, peerInfo3] = await Promise.all([
PeerInfo.create(),
PeerInfo.create(),
PeerInfo.create()
])
const [node1, node2, node3] = await Promise.all([
createNode(peerInfo1, [TCP], '/ip4/0.0.0.0/tcp/0'),
createNode(peerInfo2, [TCP, WebSockets], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']),
createNode(peerInfo3, [WebSockets], '/ip4/127.0.0.1/tcp/20000/ws')
])
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')
@ -66,24 +74,21 @@ function print ({ stream }) {
node2.handle('/print', print)
node3.handle('/print', print)
// node 1 (TCP) dials to node 2 (TCP+WebSockets)
const { stream } = await node1.dialProtocol(node2.peerInfo, '/print')
await pipe(
['node 1 dialed to node 2 successfully'],
stream
)
node1.dialProtocol(node2.peerInfo, '/print', (err, conn) => {
if (err) { throw err }
// node 2 (TCP+WebSockets) dials to node 2 (WebSockets)
const { stream: stream2 } = await node2.dialProtocol(node3.peerInfo, '/print')
await pipe(
['node 2 dialed to node 3 successfully'],
stream2
)
pull(pull.values(['node 1 dialed to node 2 successfully']), conn)
})
// node 3 (listening WebSockets) can dial node 1 (TCP)
try {
await node3.dialProtocol(node1.peerInfo, '/print')
} catch (err) {
console.log('node 3 failed to dial to node 1 with:', err.message)
}
})();
node2.dialProtocol(node3.peerInfo, '/print', (err, conn) => {
if (err) { throw err }
pull(pull.values(['node 2 dialed to node 3 successfully']), conn)
})
node3.dialProtocol(node1.peerInfo, '/print', (err, conn) => {
if (err) {
console.log('node 3 failed to dial to node 1 with:', err.message)
}
})
})

View File

@ -1,65 +1,85 @@
# [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/js-interfaces/tree/master/src/transport) specification. Some examples of possible transports are: TCP, UTP, WebRTC, QUIC, HTTP, Pigeon and so on.
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/js-interfaces/tree/master/src/transport) specification. A way to recognize a candidate transport is through the badge:
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 node with TCP
## 1. Creating a libp2p Bundle with TCP
When using libp2p, you need properly configure it, that is, pick your set of modules and create your network stack with the properties you need. In this example, we will create a libp2p node TCP. You can find the complete solution on the file [1.js](./1.js).
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 dependencies total, so go ahead and install all of them with:
You will need 5 deps total, so go ahead and install all of them with:
```bash
> npm install libp2p libp2p-tcp libp2p-secio peer-info
> npm install libp2p libp2p-tcp peer-info async @nodeutils/defaults-deep
```
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 libp2p node! Insert:
First thing is to create our own bundle! Insert:
```JavaScript
'use strict'
const Libp2p = require('libp2p')
const libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
const waterfall = require('async/waterfall')
const defaultsDeep = require('@nodeutils/defaults-deep')
const createNode = async (peerInfo) => {
// 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')
const node = await Libp2p.create({
peerInfo,
modules: {
transport: [ TCP ],
connEncryption: [ SECIO ]
// This MyBundle class is your libp2p bundle packed with TCP
class MyBundle extends libp2p {
constructor (_options) {
const defaults = {
// modules is a JS object that will describe the components
// we want for our libp2p bundle
modules: {
transport: [
TCP
]
}
}
})
await node.start()
return node
super(defaultsDeep(_options, defaults))
}
}
```
Now that we have a function to create our own libp2p node, let's create a node with it.
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
const peerInfo = await PeerInfo.create()
const node = await createNode(peerInfo)
let node
// At this point the node has started
console.log('node has started (true/false):', node.isStarted())
// 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()))
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: 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.isStarted())
// 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 something like:
@ -76,64 +96,78 @@ That `QmW2cKTakTYqbQkUzBTEGXgWYFj1YEPeUndE1YWs6CBzDQ` is the PeerId that was cre
## 2. Dialing from one node to another node
Now that we have our `createNode` function, let's create two nodes and make them dial to each other! You can find the complete solution at [2.js](./2.js).
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 it-pipe it-buffer
> npm install pull-stream
```
And we also need to import the module on our .js file:
```js
const pipe = require('it-pipe')
const { toBuffer } = require('it-buffer')
const pull = require('pull-stream')
```
We are going to reuse the `createNode` function from step 1, but this time to make things simpler, we will create another function to print the addrs to avoid duplicating code.
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: 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. We already added `async` as a dependency, but still need to import `async/parallel`:
```js
const parallel = require('async/parallel')
```
Then,
```js
;(async () => {
const [peerInfo1, peerInfo2] = await Promise.all([
PeerInfo.create(),
PeerInfo.create()
])
const [node1, node2] = await Promise.all([
createNode(),
createNode()
])
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', ({ stream }) => {
pipe(
stream,
async function (source) {
for await (const msg of source) {
console.log(msg.toString())
}
}
node2.handle('/print', (protocol, conn) => {
pull(
conn,
pull.map((v) => v.toString()),
pull.log()
)
})
const { stream } = await node1.dialProtocol(node2.peerInfo, '/print')
node1.dialProtocol(node2.peerInfo, '/print', (err, conn) => {
if (err) { throw err }
await pipe(
['Hello', ' ', 'p2p', ' ', 'world', '!'],
stream
)
})();
pull(pull.values(['Hello', ' ', 'p2p', ' ', 'world', '!']), conn)
})
})
```
The result should be look like:
@ -161,28 +195,46 @@ In this example, we will need to also install `libp2p-websockets`, go ahead and
> 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 `createNode` function to contemplate WebSockets as well. Moreover, let's upgrade our function to enable us to pick the addrs in which a node will start a listener:
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')
// ...
const createNode = async (peerInfo, transports, multiaddrs = []) => {
if (!Array.isArray(multiaddrs)) {
multiaddrs = [multiaddrs]
class MyBundle extends libp2p {
constructor (_options) {
const defaults = {
modules: {
transport: [
TCP,
WebSockets
]
}
}
super(defaultsDeep(_options, defaults))
}
}
```
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]
}
multiaddrs.forEach((addr) => peerInfo.multiaddrs.add(addr))
let node
const node = await Libp2p.create({
peerInfo,
modules: {
transport: transports,
connEncryption: [ SECIO ]
waterfall([
(cb) => PeerInfo.create(cb),
(peerInfo, cb) => {
addrs.forEach((addr) => peerInfo.multiaddrs.add(addr))
node = new MyBundle({ peerInfo: peerInfo })
node.start(cb)
}
})
await node.start()
return node
], (err) => callback(err, node))
}
```
@ -191,61 +243,58 @@ As a rule, a libp2p node will only be capable of using a transport if: a) it has
Let's update our flow to create nodes and see how they behave when dialing to each other:
```JavaScript
const WebSockets = require('libp2p-websockets')
const TCP = require('libp2p-tcp')
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 [peerInfo1, peerInfo2, peerInfo3] = await Promise.all([
PeerInfo.create(),
PeerInfo.create(),
PeerInfo.create()
])
const [node1, node2, node3] = await Promise.all([
createNode(peerInfo1, [TCP], '/ip4/0.0.0.0/tcp/0'),
createNode(peerInfo2, [TCP, WebSockets], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']),
createNode(peerInfo3, [WebSockets], '/ip4/127.0.0.1/tcp/20000/ws')
])
const node1 = nodes[0]
const node2 = nodes[1]
const node3 = nodes[2]
printAddrs(node1, '1')
printAddrs(node2, '2')
printAddrs(node3, '3')
printAddrs(node1, '1')
printAddrs(node2, '2')
printAddrs(node3, '3')
node1.handle('/print', print)
node2.handle('/print', print)
node3.handle('/print', print)
node1.handle('/print', print)
node2.handle('/print', print)
node3.handle('/print', print)
// node 1 (TCP) dials to node 2 (TCP+WebSockets)
const { stream } = await node1.dialProtocol(node2.peerInfo, '/print')
await pipe(
['node 1 dialed to node 2 successfully'],
stream
)
// node 1 (TCP) dials to node 2 (TCP+WebSockets)
node1.dialProtocol(node2.peerInfo, '/print', (err, conn) => {
if (err) { throw err }
// node 2 (TCP+WebSockets) dials to node 2 (WebSockets)
const { stream: stream2 } = await node2.dialProtocol(node3.peerInfo, '/print')
await pipe(
['node 2 dialed to node 3 successfully'],
stream2
)
pull(pull.values(['node 1 dialed to node 2 successfully']), conn)
})
// node 3 (WebSockets) attempts to dial to node 1 (TCP)
try {
await node3.dialProtocol(node1.peerInfo, '/print')
} catch (err) {
console.log('node 3 failed to dial to node 1 with:', err.message)
}
// node 2 (TCP+WebSockets) dials to node 2 (WebSockets)
node2.dialProtocol(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.dialProtocol(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 ({ stream }) {
pipe(
stream,
async function (source) {
for await (const msg of source) {
console.log(msg.toString())
}
}
function print (protocol, conn) {
pull(
conn,
pull.map((v) => v.toString()),
pull.log()
)
}
```
@ -263,19 +312,18 @@ node 2 is listening on:
/ip4/192.168.2.156/tcp/62619/p2p/QmWAQtWdzWXibgfyc7WRHhhv6MdqVKzXvyfSTnN2aAvixX
node 3 is listening on:
/ip4/127.0.0.1/tcp/20000/ws/p2p/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
node 3 failed to dial to node 1 with:
Error: No transport available for address /ip4/127.0.0.1/tcp/51482
```
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 several transports available and plenty to come, you can find these at [interface-transport implementations](https://github.com/libp2p/js-interfaces/tree/master/src/transport#modules-that-implement-the-interface) list.
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 as far as libp2p is concerned, if it follows the interface defined at the [spec](https://github.com/libp2p/js-interfaces/tree/master/src/transport#api) it will be able to use it.
Adding more transports is done through the same way as you added TCP and WebSockets. Some transports might offer extra functionalities, but as far as libp2p is concerned, if 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.

View File

@ -8,14 +8,15 @@
"Lead Maintainer"
],
"rows": [
"libp2p",
"Libp2p",
["libp2p/interface-libp2p", "interface-libp2p"],
["libp2p/js-libp2p", "libp2p"],
["libp2p/js-libp2p-daemon", "libp2p-daemon"],
["libp2p/js-libp2p-daemon-client", "libp2p-daemon-client"],
["libp2p/js-interfaces", "libp2p-interfaces"],
["libp2p/interop", "interop-libp2p"],
"transports",
"Connection",
["libp2p/interface-connection", "interface-connection"],
"Transport",
["libp2p/interface-transport", "interface-transport"],
["libp2p/js-libp2p-tcp", "libp2p-tcp"],
["libp2p/js-libp2p-utp", "libp2p-utp"],
["libp2p/js-libp2p-webrtc-direct", "libp2p-webrtc-direct"],
@ -23,14 +24,16 @@
["libp2p/js-libp2p-websockets", "libp2p-websockets"],
["libp2p/js-libp2p-websocket-star", "libp2p-websocket-star"],
"secure channels",
"Crypto Channels",
["libp2p/js-libp2p-secio", "libp2p-secio"],
"stream multiplexers",
"Stream Muxers",
["libp2p/interface-stream-muxer", "interface-stream-muxer"],
["libp2p/js-libp2p-mplex", "libp2p-mplex"],
["libp2p/js-libp2p-spdy", "libp2p-spdy"],
"peer discovery",
"Discovery",
["libp2p/interface-peer-discovery", "interface-peer-discovery"],
["libp2p/js-libp2p-bootstrap", "libp2p-bootstrap"],
["libp2p/js-libp2p-kad-dht", "libp2p-kad-dht"],
["libp2p/js-libp2p-mdns", "libp2p-mdns"],
@ -38,29 +41,26 @@
["libp2p/js-libp2p-webrtc-star", "libp2p-webrtc-star"],
["libp2p/js-libp2p-websocket-star", "libp2p-websocket-star"],
"content routing",
"Content Routing",
["libp2p/interface-content-routing", "interface-content-routing"],
["libp2p/js-libp2p-delegated-content-routing", "libp2p-delegated-content-routing"],
["libp2p/js-libp2p-kad-dht", "libp2p-kad-dht"],
"peer routing",
"Peer Routing",
["libp2p/interface-peer-routing", "interface-peer-routing"],
["libp2p/js-libp2p-delegated-peer-routing", "libp2p-delegated-peer-routing"],
["libp2p/js-libp2p-kad-dht", "libp2p-kad-dht"],
"utilities",
"Utilities",
["libp2p/js-libp2p-crypto", "libp2p-crypto"],
["libp2p/js-libp2p-crypto-secp256k1", "libp2p-crypto-secp256k1"],
"data types",
"Data Types",
["libp2p/js-peer-book", "peer-book"],
["libp2p/js-peer-id", "peer-id"],
["libp2p/js-peer-info", "peer-info"],
"pubsub",
["libp2p/js-libp2p-pubsub", "libp2p-pubsub"],
["libp2p/js-libp2p-floodsub", "libp2p-floodsub"],
["ChainSafe/gossipsub-js", "libp2p-gossipsub"],
"extensions",
["libp2p/js-libp2p-nat-mgnr", "libp2p-nat-mgnr"],
["libp2p/js-libp2p-utils", "libp2p-utils"]
"Extensions",
["libp2p/js-libp2p-floodsub", "libp2p-floodsub"]
]
}

View File

@ -1,6 +1,6 @@
{
"name": "libp2p",
"version": "0.27.0-pre.2",
"version": "0.26.2",
"description": "JavaScript implementation of libp2p, a modular peer to peer network stack",
"leadMaintainer": "Jacob Heun <jacobheun@gmail.com>",
"main": "src/index.js",
@ -11,8 +11,8 @@
"scripts": {
"lint": "aegir lint",
"build": "aegir build",
"test": "npm run test:node && npm run test:browser",
"test:node": "aegir test -t node -f \"./test/**/*.{node,spec}.js\"",
"test": "aegir test -t node -t browser",
"test:node": "aegir test -t node",
"test:browser": "aegir test -t browser",
"release": "aegir release -t node -t browser",
"release-minor": "aegir release --type minor -t node -t browser",
@ -42,15 +42,13 @@
},
"dependencies": {
"abort-controller": "^3.0.0",
"aggregate-error": "^3.0.1",
"any-signal": "^1.1.0",
"async": "^2.6.2",
"bignumber.js": "^9.0.0",
"class-is": "^1.1.0",
"debug": "^4.1.1",
"err-code": "^1.1.2",
"fsm-event": "^2.1.0",
"hashlru": "^2.3.0",
"it-all": "^1.0.1",
"it-buffer": "^0.1.1",
"it-handshake": "^1.0.1",
"it-length-prefixed": "^3.0.0",
"it-pipe": "^1.1.0",
@ -59,49 +57,61 @@
"libp2p-crypto": "^0.17.1",
"libp2p-interfaces": "^0.1.5",
"mafmt": "^7.0.0",
"merge-options": "^2.0.0",
"merge-options": "^1.0.1",
"moving-average": "^1.0.0",
"multiaddr": "^7.2.1",
"multiaddr": "^7.1.0",
"multistream-select": "^0.15.0",
"mutable-proxy": "^1.0.0",
"p-any": "^2.1.0",
"p-fifo": "^1.0.0",
"once": "^1.4.0",
"p-map": "^3.0.0",
"p-queue": "^6.1.1",
"p-settle": "^3.1.0",
"peer-id": "^0.13.4",
"peer-id": "^0.13.3",
"peer-info": "^0.17.0",
"promisify-es6": "^1.0.3",
"protons": "^1.0.1",
"pull-cat": "^1.1.11",
"pull-handshake": "^1.1.4",
"pull-stream": "^3.6.9",
"retimer": "^2.0.0",
"timeout-abort-controller": "^1.0.0",
"xsalsa20": "^1.0.2"
},
"devDependencies": {
"@nodeutils/defaults-deep": "^1.1.0",
"abortable-iterator": "^2.1.0",
"aegir": "^20.4.1",
"aegir": "^20.0.0",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"chai-checkmark": "^1.0.1",
"cids": "^0.7.1",
"delay": "^4.3.0",
"dirty-chai": "^2.0.1",
"it-concat": "^1.0.0",
"electron-webrtc": "^0.3.0",
"glob": "^7.1.4",
"interface-datastore": "^0.6.0",
"it-pair": "^1.0.0",
"it-pushable": "^1.4.0",
"libp2p-bootstrap": "^0.10.3",
"libp2p-delegated-content-routing": "^0.4.1",
"libp2p-delegated-peer-routing": "^0.4.0",
"libp2p-floodsub": "^0.20.0",
"libp2p-gossipsub": "^0.2.0",
"libp2p-kad-dht": "^0.18.2",
"libp2p-mdns": "^0.13.0",
"libp2p-bootstrap": "^0.9.7",
"libp2p-delegated-content-routing": "^0.2.2",
"libp2p-delegated-peer-routing": "^0.2.2",
"libp2p-floodsub": "^0.19.0",
"libp2p-gossipsub": "ChainSafe/gossipsub-js#beta/async",
"libp2p-kad-dht": "^0.15.3",
"libp2p-mdns": "^0.12.3",
"libp2p-mplex": "^0.9.1",
"libp2p-secio": "^0.12.1",
"libp2p-pnet": "~0.1.0",
"libp2p-secio": "^0.11.1",
"libp2p-spdy": "^0.13.2",
"libp2p-tcp": "^0.14.1",
"libp2p-webrtc-star": "^0.17.0",
"libp2p-websockets": "^0.13.1",
"lodash.times": "^4.3.2",
"nock": "^10.0.6",
"p-defer": "^3.0.0",
"p-times": "^2.1.0",
"p-wait-for": "^3.1.0",
"portfinder": "^1.0.20",
"pull-goodbye": "0.0.2",
"pull-length-prefixed": "^1.3.3",
"pull-mplex": "^0.1.2",
"pull-pair": "^1.1.0",
"pull-protocol-buffers": "~0.1.2",
"pull-serializer": "^0.3.2",
"sinon": "^7.2.7",
"streaming-iterables": "^4.1.0",
"wrtc": "^0.4.1"
@ -116,7 +126,6 @@
"Chris Dostert <chrisdostert@users.noreply.github.com>",
"Daijiro Wachi <daijiro.wachi@gmail.com>",
"David Dias <daviddias.p@gmail.com>",
"Didrik Nordström <didrik.nordstrom@gmail.com>",
"Diogo Silva <fsdiogo@gmail.com>",
"Dmitriy Ryajov <dryajov@gmail.com>",
"Elven <mon.samuel@qq.com>",
@ -153,12 +162,10 @@
"Yusef Napora <yusef@napora.org>",
"Zane Starr <zcstarr@gmail.com>",
"a1300 <a1300@users.noreply.github.com>",
"dirkmc <dirkmdev@gmail.com>",
"ebinks <elizabethjbinks@gmail.com>",
"greenkeeperio-bot <support@greenkeeper.io>",
"isan_rivkin <isanrivkin@gmail.com>",
"mayerwin <mayerwin@users.noreply.github.com>",
"phillmac <phillmac@users.noreply.github.com>",
"swedneck <40505480+swedneck@users.noreply.github.com>",
"ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>"
]

View File

@ -32,6 +32,8 @@ Prior to `libp2p-circuit` there was a rift in the IPFS network, were IPFS nodes
- [Example](#example)
- [Create dialer/listener](#create-dialerlistener)
- [Create `relay`](#create-relay)
- [This module uses `pull-streams`](#this-module-uses-pull-streams)
- [Converting `pull-streams` to Node.js Streams](#converting-pull-streams-to-nodejs-streams)
- [API](#api)
- [Implementation rational](#implementation-rational)
@ -46,7 +48,7 @@ const Circuit = require('libp2p-circuit')
const multiaddr = require('multiaddr')
const pull = require('pull-stream')
const mh1 = multiaddr('/p2p-circuit/p2p/QmHash') // dial /p2p/QmHash over any circuit
const mh1 = multiaddr('/p2p-circuit/ipfs/QmHash') // dial /ipfs/QmHash over any circuit
const circuit = new Circuit(swarmInstance, options) // pass swarm instance and options
@ -89,13 +91,37 @@ const relay = new Relay(options)
relay.mount(swarmInstance) // start relaying traffic
```
### This module uses `pull-streams`
We expose a streaming interface based on `pull-streams`, rather then on the Node.js core streams implementation (aka Node.js streams). `pull-streams` offers us a better mechanism for error handling and flow control guarantees. If you would like to know more about why we did this, see the discussion at this [issue](https://github.com/ipfs/js-ipfs/issues/362).
You can learn more about pull-streams at:
- [The history of Node.js streams, nodebp April 2014](https://www.youtube.com/watch?v=g5ewQEuXjsQ)
- [The history of streams, 2016](http://dominictarr.com/post/145135293917/history-of-streams)
- [pull-streams, the simple streaming primitive](http://dominictarr.com/post/149248845122/pull-streams-pull-streams-are-a-very-simple)
- [pull-streams documentation](https://pull-stream.github.io/)
#### Converting `pull-streams` to Node.js Streams
If you are a Node.js streams user, you can convert a pull-stream to a Node.js stream using the module [`pull-stream-to-stream`](https://github.com/dominictarr/pull-stream-to-stream), giving you an instance of a Node.js stream that is linked to the pull-stream. For example:
```js
const pullToStream = require('pull-stream-to-stream')
const nodeStreamInstance = pullToStream(pullStreamInstance)
// nodeStreamInstance is an instance of a Node.js Stream
```
To learn more about this utility, visit https://pull-stream.github.io/#pull-stream-to-stream.
## API
[![](https://raw.githubusercontent.com/libp2p/interface-transport/master/img/badge.png)](https://github.com/libp2p/interface-transport)
`libp2p-circuit` accepts Circuit addresses for both IPFS and non IPFS encapsulated addresses, i.e:
`/p2p-circuit/ip4/127.0.0.1/tcp/4001/p2p/QmHash`
`/p2p-circuit/ip4/127.0.0.1/tcp/4001/ipfs/QmHash`
Both for dialing and listening.

126
src/circuit/circuit.js Normal file
View File

@ -0,0 +1,126 @@
'use strict'
const mafmt = require('mafmt')
const multiaddr = require('multiaddr')
const CircuitDialer = require('./circuit/dialer')
const utilsFactory = require('./circuit/utils')
const debug = require('debug')
const log = debug('libp2p:circuit:transportdialer')
log.err = debug('libp2p:circuit:error:transportdialer')
const createListener = require('./listener')
class Circuit {
static get tag () {
return 'Circuit'
}
/**
* Creates an instance of Dialer.
*
* @param {Swarm} swarm - the swarm
* @param {any} options - config options
*
* @memberOf Dialer
*/
constructor (swarm, options) {
this.options = options || {}
this.swarm = swarm
this.dialer = null
this.utils = utilsFactory(swarm)
this.peerInfo = this.swarm._peerInfo
this.relays = this.filter(this.peerInfo.multiaddrs.toArray())
// if no explicit relays, add a default relay addr
if (this.relays.length === 0) {
this.peerInfo
.multiaddrs
.add(`/p2p-circuit/ipfs/${this.peerInfo.id.toB58String()}`)
}
this.dialer = new CircuitDialer(swarm, options)
this.swarm.on('peer-mux-established', (peerInfo) => {
this.dialer.canHop(peerInfo)
})
this.swarm.on('peer-mux-closed', (peerInfo) => {
this.dialer.relayPeers.delete(peerInfo.id.toB58String())
})
}
/**
* Dial the relays in the Addresses.Swarm config
*
* @param {Array} relays
* @return {void}
*/
_dialSwarmRelays () {
// if we have relay addresses in swarm config, then dial those relays
this.relays.forEach((relay) => {
const relaySegments = relay
.toString()
.split('/p2p-circuit')
.filter(segment => segment.length)
relaySegments.forEach((relaySegment) => {
const ma = this.utils.peerInfoFromMa(multiaddr(relaySegment))
this.dialer._dialRelay(ma)
})
})
}
/**
* Dial a peer over a relay
*
* @param {multiaddr} ma - the multiaddr of the peer to dial
* @param {Object} options - dial options
* @param {Function} cb - a callback called once dialed
* @returns {Connection} - the connection
*
* @memberOf Dialer
*/
dial (ma, options, cb) {
return this.dialer.dial(ma, options, cb)
}
/**
* Create a listener
*
* @param {any} options
* @param {Function} handler
* @return {listener}
*/
createListener (options, handler) {
if (typeof options === 'function') {
handler = options
options = this.options || {}
}
const listener = createListener(this.swarm, options, handler)
listener.on('listen', this._dialSwarmRelays.bind(this))
return listener
}
/**
* Filter check for all multiaddresses
* that this transport can dial on
*
* @param {any} multiaddrs
* @returns {Array<multiaddr>}
*
* @memberOf Dialer
*/
filter (multiaddrs) {
if (!Array.isArray(multiaddrs)) {
multiaddrs = [multiaddrs]
}
return multiaddrs.filter((ma) => {
return mafmt.Circuit.matches(ma)
})
}
}
module.exports = Circuit

View File

@ -0,0 +1,275 @@
'use strict'
const once = require('once')
const PeerId = require('peer-id')
const waterfall = require('async/waterfall')
const setImmediate = require('async/setImmediate')
const multiaddr = require('multiaddr')
const { Connection } = require('libp2p-interfaces/src/connection')
const utilsFactory = require('./utils')
const StreamHandler = require('./stream-handler')
const debug = require('debug')
const log = debug('libp2p:circuit:dialer')
log.err = debug('libp2p:circuit:error:dialer')
const multicodec = require('../multicodec')
const proto = require('../protocol')
class Dialer {
/**
* Creates an instance of Dialer.
* @param {Swarm} swarm - the swarm
* @param {any} options - config options
*
* @memberOf Dialer
*/
constructor (swarm, options) {
this.swarm = swarm
this.relayPeers = new Map()
this.relayConns = new Map()
this.options = options
this.utils = utilsFactory(swarm)
}
/**
* Helper that returns a relay connection
*
* @param {*} relay
* @param {*} callback
* @returns {Function} - callback
*/
_dialRelayHelper (relay, callback) {
if (this.relayConns.has(relay.id.toB58String())) {
return callback(null, this.relayConns.get(relay.id.toB58String()))
}
return this._dialRelay(relay, callback)
}
/**
* Dial a peer over a relay
*
* @param {multiaddr} ma - the multiaddr of the peer to dial
* @param {Function} cb - a callback called once dialed
* @returns {Connection} - the connection
*
*/
dial (ma, cb) {
cb = cb || (() => { })
const strMa = ma.toString()
if (!strMa.includes('/p2p-circuit')) {
log.err('invalid circuit address')
return cb(new Error('invalid circuit address'))
}
const addr = strMa.split('p2p-circuit') // extract relay address if any
const relay = addr[0] === '/' ? null : multiaddr(addr[0])
const peer = multiaddr(addr[1] || addr[0])
const dstConn = new Connection()
setImmediate(
this._dialPeer.bind(this),
peer,
relay,
(err, conn) => {
if (err) {
log.err(err)
return cb(err)
}
dstConn.setInnerConn(conn)
cb(null, dstConn)
})
return dstConn
}
/**
* Does the peer support the HOP protocol
*
* @param {PeerInfo} peer
* @param {Function} callback
* @returns {void}
*/
canHop (peer, callback) {
callback = once(callback || (() => { }))
this._dialRelayHelper(peer, (err, conn) => {
if (err) {
return callback(err)
}
const sh = new StreamHandler(conn)
waterfall([
(cb) => sh.write(proto.CircuitRelay.encode({
type: proto.CircuitRelay.Type.CAN_HOP
}), cb),
(cb) => sh.read(cb)
], (err, msg) => {
if (err) {
return callback(err)
}
const response = proto.CircuitRelay.decode(msg)
if (response.code !== proto.CircuitRelay.Status.SUCCESS) {
const err = new Error(`HOP not supported, skipping - ${this.utils.getB58String(peer)}`)
log(err)
return callback(err)
}
log('HOP supported adding as relay - %s', this.utils.getB58String(peer))
this.relayPeers.set(this.utils.getB58String(peer), peer)
sh.close()
callback()
})
})
}
/**
* Dial the destination peer over a relay
*
* @param {multiaddr} dstMa
* @param {Connection|PeerInfo} relay
* @param {Function} cb
* @return {Function|void}
* @private
*/
_dialPeer (dstMa, relay, cb) {
if (typeof relay === 'function') {
cb = relay
relay = null
}
if (!cb) {
cb = () => {}
}
dstMa = multiaddr(dstMa)
// if no relay provided, dial on all available relays until one succeeds
if (!relay) {
const relays = Array.from(this.relayPeers.values())
const next = (nextRelay) => {
if (!nextRelay) {
const err = 'no relay peers were found or all relays failed to dial'
log.err(err)
return cb(err)
}
return this._negotiateRelay(
nextRelay,
dstMa,
(err, conn) => {
if (err) {
log.err(err)
return next(relays.shift())
}
cb(null, conn)
})
}
next(relays.shift())
} else {
return this._negotiateRelay(
relay,
dstMa,
(err, conn) => {
if (err) {
log.err('An error has occurred negotiating the relay connection', err)
return cb(err)
}
return cb(null, conn)
})
}
}
/**
* Negotiate the relay connection
*
* @param {Multiaddr|PeerInfo|Connection} relay - the Connection or PeerInfo of the relay
* @param {multiaddr} dstMa - the multiaddr of the peer to relay the connection for
* @param {Function} callback - a callback which gets the negotiated relay connection
* @returns {void}
* @private
*
* @memberOf Dialer
*/
_negotiateRelay (relay, dstMa, callback) {
dstMa = multiaddr(dstMa)
relay = this.utils.peerInfoFromMa(relay)
const srcMas = this.swarm._peerInfo.multiaddrs.toArray()
this._dialRelayHelper(relay, (err, conn) => {
if (err) {
log.err(err)
return callback(err)
}
const sh = new StreamHandler(conn)
waterfall([
(cb) => {
log('negotiating relay for peer %s', dstMa.getPeerId())
let dstPeerId
try {
dstPeerId = PeerId.createFromB58String(dstMa.getPeerId()).id
} catch (err) {
return cb(err)
}
sh.write(
proto.CircuitRelay.encode({
type: proto.CircuitRelay.Type.HOP,
srcPeer: {
id: this.swarm._peerInfo.id.id,
addrs: srcMas.map((addr) => addr.buffer)
},
dstPeer: {
id: dstPeerId,
addrs: [dstMa.buffer]
}
}), cb)
},
(cb) => sh.read(cb)
], (err, msg) => {
if (err) {
return callback(err)
}
const message = proto.CircuitRelay.decode(msg)
if (message.type !== proto.CircuitRelay.Type.STATUS) {
return callback(new Error('Got invalid message type - ' +
`expected ${proto.CircuitRelay.Type.STATUS} got ${message.type}`))
}
if (message.code !== proto.CircuitRelay.Status.SUCCESS) {
return callback(new Error(`Got ${message.code} error code trying to dial over relay`))
}
callback(null, new Connection(sh.rest()))
})
})
}
/**
* Dial a relay peer by its PeerInfo
*
* @param {PeerInfo} peer - the PeerInfo of the relay peer
* @param {Function} cb - a callback with the connection to the relay peer
* @returns {void}
* @private
*/
_dialRelay (peer, cb) {
cb = once(cb || (() => { }))
this.swarm.dial(
peer,
multicodec.relay,
once((err, conn) => {
if (err) {
log.err(err)
return cb(err)
}
cb(null, conn)
}))
}
}
module.exports = Dialer

View File

@ -1,136 +1,283 @@
'use strict'
const pull = require('pull-stream/pull')
const debug = require('debug')
const PeerInfo = require('peer-info')
const PeerId = require('peer-id')
const { validateAddrs } = require('./utils')
const EE = require('events').EventEmitter
const once = require('once')
const utilsFactory = require('./utils')
const StreamHandler = require('./stream-handler')
const { CircuitRelay: CircuitPB } = require('../protocol')
const pipe = require('it-pipe')
const errCode = require('err-code')
const { codes: Errors } = require('../../errors')
const { stop } = require('./stop')
const proto = require('../protocol').CircuitRelay
const multiaddr = require('multiaddr')
const series = require('async/series')
const waterfall = require('async/waterfall')
const setImmediate = require('async/setImmediate')
const multicodec = require('./../multicodec')
const log = debug('libp2p:circuit:hop')
log.error = debug('libp2p:circuit:hop:error')
const log = debug('libp2p:circuit:relay')
log.err = debug('libp2p:circuit:error:relay')
module.exports.handleHop = async function handleHop ({
connection,
request,
streamHandler,
circuit
}) {
// Ensure hop is enabled
if (!circuit._options.hop.enabled) {
log('HOP request received but we are not acting as a relay')
return streamHandler.end({
type: CircuitPB.Type.STATUS,
code: CircuitPB.Status.HOP_CANT_SPEAK_RELAY
class Hop extends EE {
/**
* Construct a Circuit object
*
* This class will handle incoming circuit connections and
* either start a relay or hand the relayed connection to
* the swarm
*
* @param {Swarm} swarm
* @param {Object} options
*/
constructor (swarm, options) {
super()
this.swarm = swarm
this.peerInfo = this.swarm._peerInfo
this.utils = utilsFactory(swarm)
this.config = options || { active: false, enabled: false }
this.active = this.config.active
}
/**
* Handle the relay message
*
* @param {CircuitRelay} message
* @param {StreamHandler} sh
* @returns {*}
*/
handle (message, sh) {
if (!this.config.enabled) {
this.utils.writeResponse(
sh,
proto.Status.HOP_CANT_SPEAK_RELAY)
return sh.close()
}
// check if message is `CAN_HOP`
if (message.type === proto.Type.CAN_HOP) {
this.utils.writeResponse(
sh,
proto.Status.SUCCESS)
return sh.close()
}
// This is a relay request - validate and create a circuit
let srcPeerId = null
let dstPeerId = null
try {
srcPeerId = PeerId.createFromBytes(message.srcPeer.id).toB58String()
dstPeerId = PeerId.createFromBytes(message.dstPeer.id).toB58String()
} catch (err) {
log.err(err)
if (!srcPeerId) {
this.utils.writeResponse(
sh,
proto.Status.HOP_SRC_MULTIADDR_INVALID)
return sh.close()
}
if (!dstPeerId) {
this.utils.writeResponse(
sh,
proto.Status.HOP_DST_MULTIADDR_INVALID)
return sh.close()
}
}
if (srcPeerId === dstPeerId) {
this.utils.writeResponse(
sh,
proto.Status.HOP_CANT_RELAY_TO_SELF)
return sh.close()
}
if (!message.dstPeer.addrs.length) {
// TODO: use encapsulate here
const addr = multiaddr(`/p2p-circuit/ipfs/${dstPeerId}`).buffer
message.dstPeer.addrs.push(addr)
}
log('trying to establish a circuit: %s <-> %s', srcPeerId, dstPeerId)
const noPeer = () => {
// log.err(err)
this.utils.writeResponse(
sh,
proto.Status.HOP_NO_CONN_TO_DST)
return sh.close()
}
const isConnected = (cb) => {
let dstPeer
try {
dstPeer = this.swarm._peerBook.get(dstPeerId)
if (!dstPeer.isConnected() && !this.active) {
const err = new Error(`No Connection to peer ${dstPeerId}`)
noPeer(err)
return cb(err)
}
} catch (err) {
if (!this.active) {
noPeer(err)
return cb(err)
}
}
cb()
}
series([
(cb) => this.utils.validateAddrs(message, sh, proto.Type.HOP, cb),
(cb) => isConnected(cb),
(cb) => this._circuit(sh, message, cb)
], (err) => {
if (err) {
log.err(err)
sh.close()
return setImmediate(() => this.emit('circuit:error', err))
}
setImmediate(() => this.emit('circuit:success'))
})
}
// Validate the HOP request has the required input
try {
validateAddrs(request, streamHandler)
} catch (err) {
return log.error('invalid hop request via peer %s', connection.remotePeer.toB58String(), err)
}
/**
* Connect to STOP
*
* @param {PeerInfo} peer
* @param {StreamHandler} srcSh
* @param {function} callback
* @returns {void}
*/
_connectToStop (peer, srcSh, callback) {
this._dialPeer(peer, (err, dstConn) => {
if (err) {
this.utils.writeResponse(
srcSh,
proto.Status.HOP_CANT_DIAL_DST)
log.err(err)
return callback(err)
}
// Get the connection to the destination (stop) peer
const destinationPeer = new PeerId(request.dstPeer.id)
const destinationConnection = circuit._registrar.getConnection(new PeerInfo(destinationPeer))
if (!destinationConnection && !circuit._options.hop.active) {
log('HOP request received but we are not connected to the destination peer')
return streamHandler.end({
type: CircuitPB.Type.STATUS,
code: CircuitPB.Status.HOP_NO_CONN_TO_DST
return this.utils.writeResponse(
srcSh,
proto.Status.SUCCESS,
(err) => {
if (err) {
log.err(err)
return callback(err)
}
return callback(null, dstConn)
})
})
}
// TODO: Handle being an active relay
// Handle the incoming HOP request by performing a STOP request
const stopRequest = {
type: CircuitPB.Type.STOP,
dstPeer: request.dstPeer,
srcPeer: request.srcPeer
}
let destinationStream
try {
destinationStream = await stop({
connection: destinationConnection,
request: stopRequest,
circuit
/**
* Negotiate STOP
*
* @param {StreamHandler} dstSh
* @param {StreamHandler} srcSh
* @param {CircuitRelay} message
* @param {function} callback
* @returns {void}
*/
_negotiateStop (dstSh, srcSh, message, callback) {
const stopMsg = Object.assign({}, message, {
type: proto.Type.STOP // change the message type
})
} catch (err) {
return log.error(err)
dstSh.write(proto.encode(stopMsg),
(err) => {
if (err) {
this.utils.writeResponse(
srcSh,
proto.Status.HOP_CANT_OPEN_DST_STREAM)
log.err(err)
return callback(err)
}
// read response from STOP
dstSh.read((err, msg) => {
if (err) {
log.err(err)
return callback(err)
}
const message = proto.decode(msg)
if (message.code !== proto.Status.SUCCESS) {
return callback(new Error('Unable to create circuit!'))
}
return callback(null, msg)
})
})
}
log('hop request from %s is valid', connection.remotePeer.toB58String())
streamHandler.write({
type: CircuitPB.Type.STATUS,
code: CircuitPB.Status.SUCCESS
})
const sourceStream = streamHandler.rest()
/**
* Attempt to make a circuit from A <-> R <-> B where R is this relay
*
* @param {StreamHandler} srcSh - the source stream handler
* @param {CircuitRelay} message - the message with the src and dst entries
* @param {Function} callback - callback to signal success or failure
* @returns {void}
* @private
*/
_circuit (srcSh, message, callback) {
let dstSh = null
waterfall([
(cb) => this._connectToStop(message.dstPeer, srcSh, cb),
(_dstConn, cb) => {
dstSh = new StreamHandler(_dstConn)
this._negotiateStop(dstSh, srcSh, message, cb)
}
], (err) => {
if (err) {
// close/end the source stream if there was an error
if (srcSh) {
srcSh.close()
}
// Short circuit the two streams to create the relayed connection
return pipe(
sourceStream,
destinationStream,
sourceStream
)
}
if (dstSh) {
dstSh.close()
}
return callback(err)
}
/**
* Performs a HOP request to a relay peer, to request a connection to another
* peer. A new, virtual, connection will be created between the two via the relay.
*
* @param {object} options
* @param {Connection} options.connection Connection to the relay
* @param {*} options.request
* @param {Circuit} options.circuit
* @returns {Promise<Connection>}
*/
module.exports.hop = async function hop ({
connection,
request
}) {
// Create a new stream to the relay
const { stream } = await connection.newStream([multicodec.relay])
// Send the HOP request
const streamHandler = new StreamHandler({ stream })
streamHandler.write(request)
const src = srcSh.rest()
const dst = dstSh.rest()
const response = await streamHandler.read()
const srcIdStr = PeerId.createFromBytes(message.srcPeer.id).toB58String()
const dstIdStr = PeerId.createFromBytes(message.dstPeer.id).toB58String()
if (response.code === CircuitPB.Status.SUCCESS) {
log('hop request was successful')
return streamHandler.rest()
// circuit the src and dst streams
pull(
src,
dst,
src
)
log('circuit %s <-> %s established', srcIdStr, dstIdStr)
callback()
})
}
log('hop request failed with code %d, closing stream', response.code)
streamHandler.close()
throw errCode(new Error(`HOP request failed with code ${response.code}`), Errors.ERR_HOP_REQUEST_FAILED)
/**
* Dial the dest peer and create a circuit
*
* @param {Multiaddr} dstPeer
* @param {Function} callback
* @returns {void}
* @private
*/
_dialPeer (dstPeer, callback) {
const peerInfo = new PeerInfo(PeerId.createFromBytes(dstPeer.id))
dstPeer.addrs.forEach((a) => peerInfo.multiaddrs.add(a))
this.swarm.dial(peerInfo, multicodec.relay, once((err, conn) => {
if (err) {
log.err(err)
return callback(err)
}
callback(null, conn)
}))
}
}
/**
* Creates an unencoded CAN_HOP response based on the Circuits configuration
* @private
*/
module.exports.handleCanHop = function handleCanHop ({
connection,
streamHandler,
circuit
}) {
const canHop = circuit._options.hop.enabled
log('can hop (%s) request from %s', canHop, connection.remotePeer.toB58String())
streamHandler.end({
type: CircuitPB.Type.STATUS,
code: canHop ? CircuitPB.Status.SUCCESS : CircuitPB.Status.HOP_CANT_SPEAK_RELAY
})
}
module.exports = Hop

View File

@ -1,69 +1,56 @@
'use strict'
const { CircuitRelay: CircuitPB } = require('../protocol')
const multicodec = require('../multicodec')
const StreamHandler = require('./stream-handler')
const { validateAddrs } = require('./utils')
const setImmediate = require('async/setImmediate')
const EE = require('events').EventEmitter
const { Connection } = require('libp2p-interfaces/src/connection')
const utilsFactory = require('./utils')
const PeerInfo = require('peer-info')
const proto = require('../protocol').CircuitRelay
const series = require('async/series')
const debug = require('debug')
const log = debug('libp2p:circuit:stop')
log.error = debug('libp2p:circuit:stop:error')
log.err = debug('libp2p:circuit:error:stop')
/**
* Handles incoming STOP requests
*
* @private
* @param {*} options
* @param {Connection} options.connection
* @param {*} options.request The CircuitRelay protobuf request (unencoded)
* @param {StreamHandler} options.streamHandler
* @returns {Promise<*>} Resolves a duplex iterable
*/
module.exports.handleStop = function handleStop ({
connection,
request,
streamHandler
}) {
// Validate the STOP request has the required input
try {
validateAddrs(request, streamHandler)
} catch (err) {
return log.error('invalid stop request via peer %s', connection.remotePeer.toB58String(), err)
class Stop extends EE {
constructor (swarm) {
super()
this.swarm = swarm
this.utils = utilsFactory(swarm)
}
// The request is valid
log('stop request is valid')
streamHandler.write({
type: CircuitPB.Type.STATUS,
code: CircuitPB.Status.SUCCESS
})
return streamHandler.rest()
}
/**
* Handle the incoming STOP message
*
* @param {{}} msg - the parsed protobuf message
* @param {StreamHandler} sh - the stream handler wrapped connection
* @param {Function} callback - callback
* @returns {undefined}
*/
handle (msg, sh, callback) {
callback = callback || (() => {})
/**
* Creates a STOP request
* @private
* @param {*} options
* @param {Connection} options.connection
* @param {*} options.request The CircuitRelay protobuf request (unencoded)
* @returns {Promise<*>} Resolves a duplex iterable
*/
module.exports.stop = async function stop ({
connection,
request
}) {
const { stream } = await connection.newStream([multicodec.relay])
log('starting stop request to %s', connection.remotePeer.toB58String())
const streamHandler = new StreamHandler({ stream })
series([
(cb) => this.utils.validateAddrs(msg, sh, proto.Type.STOP, cb),
(cb) => this.utils.writeResponse(sh, proto.Status.Success, cb)
], (err) => {
if (err) {
// we don't return the error here,
// since multistream select don't expect one
callback()
return log(err)
}
streamHandler.write(request)
const response = await streamHandler.read()
if (response.code === CircuitPB.Status.SUCCESS) {
log('stop request to %s was successful', connection.remotePeer.toB58String())
return streamHandler.rest()
const peerInfo = new PeerInfo(this.utils.peerIdFromId(msg.srcPeer.id))
msg.srcPeer.addrs.forEach((addr) => peerInfo.multiaddrs.add(addr))
const newConn = new Connection(sh.rest())
newConn.setPeerInfo(peerInfo)
setImmediate(() => this.emit('connection', newConn))
callback(newConn)
})
}
log('stop request failed with code %d', response.code)
streamHandler.close()
}
module.exports = Stop

View File

@ -1,79 +1,139 @@
'use strict'
const lp = require('it-length-prefixed')
const handshake = require('it-handshake')
const { CircuitRelay: CircuitPB } = require('../protocol')
const values = require('pull-stream/sources/values')
const collect = require('pull-stream/sinks/collect')
const empty = require('pull-stream/sources/empty')
const pull = require('pull-stream/pull')
const lp = require('pull-length-prefixed')
const handshake = require('pull-handshake')
const debug = require('debug')
const log = debug('libp2p:circuit:stream-handler')
log.error = debug('libp2p:circuit:stream-handler:error')
log.err = debug('libp2p:circuit:error:stream-handler')
class StreamHandler {
/**
* Create a stream handler for connection
*
* @param {object} options
* @param {*} options.stream - A duplex iterable
* @param {Number} options.maxLength - max bytes length of message
* @param {Connection} conn - connection to read/write
* @param {Function|undefined} cb - handshake callback called on error
* @param {Number} timeout - handshake timeout
* @param {Number} maxLength - max bytes length of message
*/
constructor ({ stream, maxLength = 4096 }) {
this.stream = stream
constructor (conn, cb, timeout, maxLength) {
this.conn = conn
this.stream = null
this.shake = null
this.timeout = cb || 1000 * 60
this.maxLength = maxLength || 4096
this.shake = handshake(this.stream)
this.decoder = lp.decode.fromReader(this.shake.reader, { maxDataLength: maxLength })
if (typeof cb === 'function') {
this.timeout = timeout || 1000 * 60
}
this.stream = handshake({ timeout: this.timeout }, cb)
this.shake = this.stream.handshake
pull(this.stream, conn, this.stream)
}
isValid () {
return this.conn && this.shake && this.stream
}
/**
* Read and decode message
* @async
* @returns {void}
*
* @param {Function} cb
* @returns {void|Function}
*/
async read () {
const msg = await this.decoder.next()
if (msg.value) {
const value = CircuitPB.decode(msg.value.slice())
log('read message type', value.type)
return value
read (cb) {
if (!this.isValid()) {
return cb(new Error('handler is not in a valid state'))
}
log('read received no value, closing stream')
// End the stream, we didn't get data
this.close()
lp.decodeFromReader(
this.shake,
{ maxLength: this.maxLength },
(err, msg) => {
if (err) {
log.err(err)
// this.shake.abort(err)
return cb(err)
}
return cb(null, msg)
})
}
/**
* Encode and write array of buffers
*
* @param {*} msg An unencoded CircuitRelay protobuf message
* @param {Buffer[]} msg
* @param {Function} [cb]
* @returns {Function}
*/
write (msg) {
log('write message type %s', msg.type)
this.shake.write(lp.encode.single(CircuitPB.encode(msg)))
write (msg, cb) {
cb = cb || (() => {})
if (!this.isValid()) {
return cb(new Error('handler is not in a valid state'))
}
pull(
values([msg]),
lp.encode(),
collect((err, encoded) => {
if (err) {
log.err(err)
this.shake.abort(err)
return cb(err)
}
encoded.forEach((e) => this.shake.write(e))
cb()
})
)
}
/**
* Get the raw Connection
*
* @returns {null|Connection|*}
*/
getRawConn () {
return this.conn
}
/**
* Return the handshake rest stream and invalidate handler
*
* @return {*} A duplex iterable
* @return {*|{source, sink}}
*/
rest () {
this.shake.rest()
return this.shake.stream
}
const rest = this.shake.rest()
end (msg) {
this.write(msg)
this.close()
this.conn = null
this.stream = null
this.shake = null
return rest
}
/**
* Close the stream
*
* @returns {void}
* @returns {undefined}
*/
close () {
log('closing the stream')
this.rest().sink([])
if (!this.isValid()) {
return
}
// close stream
pull(
empty(),
this.rest()
)
}
}

View File

@ -1,51 +1,118 @@
'use strict'
const multiaddr = require('multiaddr')
const { CircuitRelay } = require('../protocol')
const PeerInfo = require('peer-info')
const PeerId = require('peer-id')
const proto = require('../protocol')
const { getPeerInfo } = require('../../get-peer-info')
/**
* Write a response
*
* @param {StreamHandler} streamHandler
* @param {CircuitRelay.Status} status
*/
function writeResponse (streamHandler, status) {
streamHandler.write({
type: CircuitRelay.Type.STATUS,
code: status
})
}
module.exports = function (swarm) {
/**
* Get b58 string from multiaddr or peerinfo
*
* @param {Multiaddr|PeerInfo} peer
* @return {*}
*/
function getB58String (peer) {
let b58Id = null
if (multiaddr.isMultiaddr(peer)) {
const relayMa = multiaddr(peer)
b58Id = relayMa.getPeerId()
} else if (PeerInfo.isPeerInfo(peer)) {
b58Id = peer.id.toB58String()
}
/**
* Validate incomming HOP/STOP message
*
* @param {*} msg A CircuitRelay unencoded protobuf message
* @param {StreamHandler} streamHandler
*/
function validateAddrs (msg, streamHandler) {
try {
msg.dstPeer.addrs.forEach((addr) => {
return multiaddr(addr)
})
} catch (err) {
writeResponse(streamHandler, msg.type === CircuitRelay.Type.HOP
? CircuitRelay.Status.HOP_DST_MULTIADDR_INVALID
: CircuitRelay.Status.STOP_DST_MULTIADDR_INVALID)
throw err
return b58Id
}
try {
msg.srcPeer.addrs.forEach((addr) => {
return multiaddr(addr)
})
} catch (err) {
writeResponse(streamHandler, msg.type === CircuitRelay.Type.HOP
? CircuitRelay.Status.HOP_SRC_MULTIADDR_INVALID
: CircuitRelay.Status.STOP_SRC_MULTIADDR_INVALID)
throw err
/**
* Helper to make a peer info from a multiaddrs
*
* @param {Multiaddr|PeerInfo|PeerId} peer
* @return {PeerInfo}
* @private
*/
function peerInfoFromMa (peer) {
return getPeerInfo(peer, swarm._peerBook)
}
/**
* Checks if peer has an existing connection
*
* @param {String} peerId
* @param {Swarm} swarm
* @return {Boolean}
*/
function isPeerConnected (peerId) {
return swarm.muxedConns[peerId] || swarm.conns[peerId]
}
/**
* Write a response
*
* @param {StreamHandler} streamHandler
* @param {CircuitRelay.Status} status
* @param {Function} cb
* @returns {*}
*/
function writeResponse (streamHandler, status, cb) {
cb = cb || (() => {})
streamHandler.write(proto.CircuitRelay.encode({
type: proto.CircuitRelay.Type.STATUS,
code: status
}))
return cb()
}
/**
* Validate incomming HOP/STOP message
*
* @param {CircuitRelay} msg
* @param {StreamHandler} streamHandler
* @param {CircuitRelay.Type} type
* @returns {*}
* @param {Function} cb
*/
function validateAddrs (msg, streamHandler, type, cb) {
try {
msg.dstPeer.addrs.forEach((addr) => {
return multiaddr(addr)
})
} catch (err) {
writeResponse(streamHandler, type === proto.CircuitRelay.Type.HOP
? proto.CircuitRelay.Status.HOP_DST_MULTIADDR_INVALID
: proto.CircuitRelay.Status.STOP_DST_MULTIADDR_INVALID)
return cb(err)
}
try {
msg.srcPeer.addrs.forEach((addr) => {
return multiaddr(addr)
})
} catch (err) {
writeResponse(streamHandler, type === proto.CircuitRelay.Type.HOP
? proto.CircuitRelay.Status.HOP_SRC_MULTIADDR_INVALID
: proto.CircuitRelay.Status.STOP_SRC_MULTIADDR_INVALID)
return cb(err)
}
return cb(null)
}
function peerIdFromId (id) {
if (typeof id === 'string') {
return PeerId.createFromB58String(id)
}
return PeerId.createFromBytes(id)
}
return {
getB58String,
peerInfoFromMa,
isPeerConnected,
validateAddrs,
writeResponse,
peerIdFromId
}
}
module.exports = {
validateAddrs
}

View File

@ -1,186 +1,3 @@
'use strict'
const mafmt = require('mafmt')
const multiaddr = require('multiaddr')
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const withIs = require('class-is')
const { CircuitRelay: CircuitPB } = require('./protocol')
const debug = require('debug')
const log = debug('libp2p:circuit')
log.error = debug('libp2p:circuit:error')
const { relay: multicodec } = require('./multicodec')
const createListener = require('./listener')
const { handleCanHop, handleHop, hop } = require('./circuit/hop')
const { handleStop } = require('./circuit/stop')
const StreamHandler = require('./circuit/stream-handler')
const toConnection = require('./stream-to-conn')
class Circuit {
/**
* Creates an instance of Circuit.
*
* @constructor
* @param {object} options
* @param {Libp2p} options.libp2p
* @param {Upgrader} options.upgrader
*/
constructor ({ libp2p, upgrader }) {
this._dialer = libp2p.dialer
this._registrar = libp2p.registrar
this._upgrader = upgrader
this._options = libp2p._config.relay
this.peerInfo = libp2p.peerInfo
this._registrar.handle(multicodec, this._onProtocol.bind(this))
}
async _onProtocol ({ connection, stream, protocol }) {
const streamHandler = new StreamHandler({ stream })
const request = await streamHandler.read()
const circuit = this
let virtualConnection
switch (request.type) {
case CircuitPB.Type.CAN_HOP: {
log('received CAN_HOP request from %s', connection.remotePeer.toB58String())
await handleCanHop({ circuit, connection, streamHandler })
break
}
case CircuitPB.Type.HOP: {
log('received HOP request from %s', connection.remotePeer.toB58String())
virtualConnection = await handleHop({
connection,
request,
streamHandler,
circuit
})
break
}
case CircuitPB.Type.STOP: {
log('received STOP request from %s', connection.remotePeer.toB58String())
virtualConnection = await handleStop({
connection,
request,
streamHandler,
circuit
})
break
}
default: {
log('Request of type %s not supported', request.type)
}
}
if (virtualConnection) {
const remoteAddr = multiaddr(request.dstPeer.addrs[0])
const localAddr = multiaddr(request.srcPeer.addrs[0])
const maConn = toConnection({
stream: virtualConnection,
remoteAddr,
localAddr
})
const type = CircuitPB.Type === CircuitPB.Type.HOP ? 'relay' : 'inbound'
log('new %s connection %s', type, maConn.remoteAddr)
const conn = await this._upgrader.upgradeInbound(maConn)
log('%s connection %s upgraded', type, maConn.remoteAddr)
this.handler && this.handler(conn)
}
}
/**
* Dial a peer over a relay
*
* @param {multiaddr} ma - the multiaddr of the peer to dial
* @param {Object} options - dial options
* @param {AbortSignal} [options.signal] - An optional abort signal
* @returns {Connection} - the connection
*/
async dial (ma, options) {
// Check the multiaddr to see if it contains a relay and a destination peer
const addrs = ma.toString().split('/p2p-circuit')
const relayAddr = multiaddr(addrs[0])
const destinationAddr = multiaddr(addrs[addrs.length - 1])
const relayPeer = PeerId.createFromCID(relayAddr.getPeerId())
const destinationPeer = PeerId.createFromCID(destinationAddr.getPeerId())
let disconnectOnFailure = false
let relayConnection = this._registrar.getConnection(new PeerInfo(relayPeer))
if (!relayConnection) {
relayConnection = await this._dialer.connectToPeer(relayAddr, options)
disconnectOnFailure = true
}
try {
const virtualConnection = await hop({
connection: relayConnection,
circuit: this,
request: {
type: CircuitPB.Type.HOP,
srcPeer: {
id: this.peerInfo.id.toBytes(),
addrs: this.peerInfo.multiaddrs.toArray().map(addr => addr.buffer)
},
dstPeer: {
id: destinationPeer.toBytes(),
addrs: [multiaddr(destinationAddr).buffer]
}
}
})
const localAddr = relayAddr.encapsulate(`/p2p-circuit/p2p/${this.peerInfo.id.toB58String()}`)
const maConn = toConnection({
stream: virtualConnection,
remoteAddr: ma,
localAddr
})
log('new outbound connection %s', maConn.remoteAddr)
return this._upgrader.upgradeOutbound(maConn)
} catch (err) {
log.error('Circuit relay dial failed', err)
disconnectOnFailure && await relayConnection.close()
throw err
}
}
/**
* Create a listener
*
* @param {any} options
* @param {Function} handler
* @return {listener}
*/
createListener (options, handler) {
if (typeof options === 'function') {
handler = options
options = {}
}
// Called on successful HOP and STOP requests
this.handler = handler
return createListener(this, options)
}
/**
* Filter check for all Multiaddrs that this transport can dial on
*
* @param {Array<Multiaddr>} multiaddrs
* @returns {Array<Multiaddr>}
*/
filter (multiaddrs) {
multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs]
return multiaddrs.filter((ma) => {
return mafmt.Circuit.matches(ma)
})
}
}
/**
* @type {Circuit}
*/
module.exports = withIs(Circuit, { className: 'Circuit', symbolName: '@libp2p/js-libp2p-circuit/circuit' })
module.exports = require('./circuit')

View File

@ -1,42 +1,94 @@
'use strict'
const EventEmitter = require('events')
const setImmediate = require('async/setImmediate')
const multicodec = require('./multicodec')
const EE = require('events').EventEmitter
const multiaddr = require('multiaddr')
const mafmt = require('mafmt')
const Stop = require('./circuit/stop')
const Hop = require('./circuit/hop')
const proto = require('./protocol')
const utilsFactory = require('./circuit/utils')
const StreamHandler = require('./circuit/stream-handler')
const debug = require('debug')
const log = debug('libp2p:circuit:listener')
log.err = debug('libp2p:circuit:error:listener')
/**
* @param {*} circuit
* @returns {Listener} a transport listener
*/
module.exports = (circuit) => {
const listener = new EventEmitter()
const listeningAddrs = new Map()
module.exports = (swarm, options, connHandler) => {
const listener = new EE()
const utils = utilsFactory(swarm)
listener.stopHandler = new Stop(swarm)
listener.stopHandler.on('connection', (conn) => listener.emit('connection', conn))
listener.hopHandler = new Hop(swarm, options.hop)
/**
* Add swarm handler and listen for incoming connections
*
* @param {Multiaddr} addr
* @param {Multiaddr} ma
* @param {Function} callback
* @return {void}
*/
listener.listen = async (addr) => {
const [addrString] = String(addr).split('/p2p-circuit').slice(-1)
listener.listen = (ma, callback) => {
callback = callback || (() => {})
const relayConn = await circuit._dialer.connectToPeer(multiaddr(addrString))
const relayedAddr = relayConn.remoteAddr.encapsulate('/p2p-circuit')
swarm.handle(multicodec.relay, (_, conn) => {
const sh = new StreamHandler(conn)
listeningAddrs.set(relayConn.remotePeer.toB58String(), relayedAddr)
listener.emit('listening')
sh.read((err, msg) => {
if (err) {
log.err(err)
return
}
let request = null
try {
request = proto.CircuitRelay.decode(msg)
} catch (err) {
return utils.writeResponse(
sh,
proto.CircuitRelay.Status.MALFORMED_MESSAGE)
}
switch (request.type) {
case proto.CircuitRelay.Type.CAN_HOP:
case proto.CircuitRelay.Type.HOP: {
return listener.hopHandler.handle(request, sh)
}
case proto.CircuitRelay.Type.STOP: {
return listener.stopHandler.handle(request, sh, connHandler)
}
default: {
utils.writeResponse(
sh,
proto.CircuitRelay.Status.INVALID_MSG_TYPE)
return sh.close()
}
}
})
})
setImmediate(() => listener.emit('listen'))
callback()
}
/**
* TODO: Remove the peers from our topology
* Remove swarm listener
*
* @param {Function} cb
* @return {void}
*/
listener.close = () => {}
listener.close = (cb) => {
swarm.unhandle(multicodec.relay)
setImmediate(() => listener.emit('close'))
cb()
}
/**
* Get fixed up multiaddrs
@ -52,14 +104,45 @@ module.exports = (circuit) => {
* the encapsulated transport address. This is useful when for example, a peer should only
* be dialed over TCP rather than any other transport
*
* @return {Multiaddr[]}
* @param {Function} callback
* @return {void}
*/
listener.getAddrs = () => {
const addrs = []
for (const addr of listeningAddrs.values()) {
addrs.push(addr)
listener.getAddrs = (callback) => {
let addrs = swarm._peerInfo.multiaddrs.toArray()
// get all the explicit relay addrs excluding self
const p2pAddrs = addrs.filter((addr) => {
return mafmt.Circuit.matches(addr) &&
!addr.toString().includes(swarm._peerInfo.id.toB58String())
})
// use the explicit relays instead of any relay
if (p2pAddrs.length) {
addrs = p2pAddrs
}
return addrs
const listenAddrs = []
addrs.forEach((addr) => {
const peerMa = `/p2p-circuit/ipfs/${swarm._peerInfo.id.toB58String()}`
if (addr.toString() === peerMa) {
listenAddrs.push(multiaddr(peerMa))
return
}
if (!mafmt.Circuit.matches(addr)) {
if (addr.getPeerId()) {
// by default we're reachable over any relay
listenAddrs.push(multiaddr('/p2p-circuit').encapsulate(addr))
} else {
const ma = `${addr}/ipfs/${swarm._peerInfo.id.toB58String()}`
listenAddrs.push(multiaddr('/p2p-circuit').encapsulate(ma))
}
} else {
listenAddrs.push(addr.encapsulate(`/ipfs/${swarm._peerInfo.id.toB58String()}`))
}
})
callback(null, listenAddrs)
}
return listener

View File

@ -1,49 +0,0 @@
'use strict'
const abortable = require('abortable-iterator')
const log = require('debug')('libp2p:circuit:stream')
// Convert a duplex iterable into a MultiaddrConnection
// https://github.com/libp2p/interface-transport#multiaddrconnection
module.exports = ({ stream, remoteAddr, localAddr }, options = {}) => {
const { sink, source } = stream
const maConn = {
async sink (source) {
if (options.signal) {
source = abortable(source, options.signal)
}
try {
await sink(source)
} catch (err) {
// If aborted we can safely ignore
if (err.type !== 'aborted') {
// If the source errored the socket will already have been destroyed by
// toIterable.duplex(). If the socket errored it will already be
// destroyed. There's nothing to do here except log the error & return.
log(err)
}
}
close()
},
source: options.signal ? abortable(source, options.signal) : source,
conn: stream,
localAddr,
remoteAddr,
timeline: { open: Date.now() },
close () {
sink([])
close()
}
}
function close () {
if (!maConn.timeline.close) {
maConn.timeline.close = Date.now()
}
}
return maConn
}

View File

@ -1,20 +1,11 @@
'use strict'
const mergeOptions = require('merge-options')
const Constants = require('./constants')
const DefaultConfig = {
connectionManager: {
minPeers: 25
},
dialer: {
maxParallelDials: Constants.MAX_PARALLEL_DIALS,
maxDialsPerPeer: Constants.MAX_PER_PEER_DIALS,
dialTimeout: Constants.DIAL_TIMEOUT
},
metrics: {
enabled: false
},
config: {
dht: {
enabled: false,
@ -41,8 +32,7 @@ const DefaultConfig = {
enabled: false,
active: false
}
},
transport: {}
}
}
}

View File

@ -0,0 +1,99 @@
# libp2p-connection-manager
> JavaScript connection manager for libp2p
**Note**: git history prior to merging into js-libp2p can be found in the original repository, https://github.com/libp2p/js-libp2p-connection-manager.
## Table of Contents
- [Install](#install)
- [npm](#npm)
- [Use in Node.js, a browser with browserify, webpack or any other bundler](##use-in-nodejs-or-in-the-browser-with-browserify-webpack-or-any-other-bundler)
- [Usage](#usage)
- [API](#api)
- [Contribute](#contribute)
- [License](#license)
## API
A connection manager manages the peers you're connected to. The application provides one or more limits that will trigger the disconnection of peers. These limits can be any of the following:
* number of connected peers
* maximum bandwidth (sent / received or both)
* maximum event loop delay
The connection manager will disconnect peers (starting from the less important peers) until all the measures are withing the stated limits.
A connection manager first disconnects the peers with the least value. By default all peers have the same importance (1), but the application can define otherwise. Once a peer disconnects the connection manager discards the peer importance. (If necessary, the application should redefine the peer state if the peer is again connected).
### Create a ConnectionManager
```js
const libp2p = // …
const options = {}
const connManager = new ConnManager(libp2p, options)
```
Options is an optional object with the following key-value pairs:
* **`maxPeers`**: number identifying the maximum number of peers the current peer is willing to be connected to before is starts disconnecting. Defaults to `Infinity`
* **`maxPeersPerProtocol`**: Object with key-value pairs, where a key is the protocol tag (case-insensitive) and the value is a number, representing the maximum number of peers allowing to connect for each protocol. Defaults to `{}`.
* **`minPeers`**: number identifying the number of peers below which this node will not activate preemptive disconnections. Defaults to `0`.
* **`maxData`**: sets the maximum data — in bytes per second - (sent and received) this node is willing to endure before it starts disconnecting peers. Defaults to `Infinity`.
* **`maxSentData`**: sets the maximum sent data — in bytes per second - this node is willing to endure before it starts disconnecting peers. Defaults to `Infinity`.
* **`maxReceivedData`**: sets the maximum received data — in bytes per second - this node is willing to endure before it starts disconnecting peers. Defaults to `Infinity`.
* **`maxEventLoopDelay`**: sets the maximum event loop delay (measured in miliseconds) this node is willing to endure before it starts disconnecting peers. Defaults to `Infinity`.
* **`pollInterval`**: sets the poll interval (in miliseconds) for assessing the current state and determining if this peer needs to force a disconnect. Defaults to `2000` (2 seconds).
* **`movingAverageInterval`**: the interval used to calculate moving averages (in miliseconds). Defaults to `60000` (1 minute).
* **`defaultPeerValue`**: number between 0 and 1. Defaults to 1.
### `connManager.start()`
Starts the connection manager.
### `connManager.stop()`
Stops the connection manager.
### `connManager.setPeerValue(peerId, value)`
Sets the peer value for a given peer id. This is used to sort peers (in reverse order of value) to determine which to disconnect from first.
Arguments:
* peerId: B58-encoded string or [`peer-id`](https://github.com/libp2p/js-peer-id) instance.
* value: a number between 0 and 1, which represents a scale of how valuable this given peer id is to the application.
### `connManager.peers()`
Returns the peers this connection manager is connected to.
Returns an array of [PeerInfo](https://github.com/libp2p/js-peer-info).
### `connManager.emit('limit:exceeded', limitName, measured)`
Emitted when a limit is exceeded. Limit names can be:
* `maxPeers`
* `minPeers`
* `maxData`
* `maxSentData`
* `maxReceivedData`
* `maxEventLoopDelay`
* a protocol tag string (lower-cased)
### `connManager.emit('disconnect:preemptive', peerId)`
Emitted when a peer is about to be preemptively disconnected.
### `connManager.emit('disconnected', peerId)`
Emitted when a peer is disconnected (preemptively or note). If this peer reconnects, you will need to reset it's value, since the connection manager does not remember it.
### `connManager.emit('connected', peerId: String)`
Emitted when a peer connects. This is a good event to set the peer value, so you can get some control over who gets banned once a maximum number of peers is reached.

View File

@ -1,14 +1,12 @@
'use strict'
const assert = require('assert')
const mergeOptions = require('merge-options')
const EventEmitter = require('events')
const LatencyMonitor = require('latency-monitor').default
const debug = require('debug')('libp2p:connection-manager')
const retimer = require('retimer')
const defaultOptions = {
maxConnections: Infinity,
minConnections: 0,
maxPeers: Infinity,
minPeers: 0,
maxData: Infinity,
maxSentData: Infinity,
maxReceivedData: Infinity,
@ -18,171 +16,170 @@ const defaultOptions = {
defaultPeerValue: 1
}
class ConnectionManager {
/**
* @constructor
* @param {Libp2p} libp2p
* @param {object} options
* @param {Number} options.maxConnections The maximum number of connections allowed. Default=Infinity
* @param {Number} options.minConnections The minimum number of connections to avoid pruning. Default=0
* @param {Number} options.maxData The max data (in and out), per average interval to allow. Default=Infinity
* @param {Number} options.maxSentData The max outgoing data, per average interval to allow. Default=Infinity
* @param {Number} options.maxReceivedData The max incoming data, per average interval to allow.. Default=Infinity
* @param {Number} options.maxEventLoopDelay The upper limit the event loop can take to run. Default=Infinity
* @param {Number} options.pollInterval How often, in milliseconds, metrics and latency should be checked. Default=2000
* @param {Number} options.movingAverageInterval How often, in milliseconds, to compute averages. Default=60000
* @param {Number} options.defaultPeerValue The value of the peer. Default=1
*/
class ConnectionManager extends EventEmitter {
constructor (libp2p, options) {
super()
this._libp2p = libp2p
this._registrar = libp2p.registrar
this._peerId = libp2p.peerInfo.id.toString()
this._options = mergeOptions.call({ ignoreUndefined: true }, defaultOptions, options)
assert(
this._options.maxConnections > this._options.minConnections,
'Connection Manager maxConnections must be greater than minConnections'
)
this._options = Object.assign({}, defaultOptions, options)
this._options.maxPeersPerProtocol = fixMaxPeersPerProtocol(this._options.maxPeersPerProtocol)
debug('options: %j', this._options)
this._metrics = libp2p.metrics
this._peerValues = new Map()
this._connections = new Map()
this._timer = null
this._checkMetrics = this._checkMetrics.bind(this)
}
/**
* Starts the Connection Manager. If Metrics are not enabled on libp2p
* only event loop and connection limits will be monitored.
*/
start () {
if (this._metrics) {
this._timer = this._timer || retimer(this._checkMetrics, this._options.pollInterval)
this._stats = libp2p.stats
if (options && !this._stats) {
throw new Error('No libp2p.stats')
}
this._peerValues = new Map()
this._peers = new Map()
this._peerProtocols = new Map()
this._peerCountPerProtocol = new Map()
this._onStatsUpdate = this._onStatsUpdate.bind(this)
this._onPeerConnect = this._onPeerConnect.bind(this)
this._onPeerDisconnect = this._onPeerDisconnect.bind(this)
if (this._libp2p.isStarted()) {
this._onceStarted()
} else {
this._libp2p.once('start', this._onceStarted.bind(this))
}
}
start () {
this._stats.on('update', this._onStatsUpdate)
this._libp2p.on('connection:start', this._onPeerConnect)
this._libp2p.on('connection:end', this._onPeerDisconnect)
// latency monitor
this._latencyMonitor = new LatencyMonitor({
latencyCheckIntervalMs: this._options.pollInterval,
dataEmitIntervalMs: this._options.pollInterval
})
this._onLatencyMeasure = this._onLatencyMeasure.bind(this)
this._latencyMonitor.on('data', this._onLatencyMeasure)
debug('started')
}
/**
* Stops the Connection Manager
*/
stop () {
this._timer && this._timer.clear()
this._latencyMonitor && this._latencyMonitor.removeListener('data', this._onLatencyMeasure)
debug('stopped')
this._stats.removeListener('update', this._onStatsUpdate)
this._libp2p.removeListener('connection:start', this._onPeerConnect)
this._libp2p.removeListener('connection:end', this._onPeerDisconnect)
this._latencyMonitor.removeListener('data', this._onLatencyMeasure)
}
/**
* Sets the value of the given peer. Peers with lower values
* will be disconnected first.
* @param {PeerId} peerId
* @param {number} value A number between 0 and 1
*/
setPeerValue (peerId, value) {
if (value < 0 || value > 1) {
throw new Error('value should be a number between 0 and 1')
}
if (peerId.toString) {
peerId = peerId.toString()
if (peerId.toB58String) {
peerId = peerId.toB58String()
}
this._peerValues.set(peerId, value)
}
/**
* Checks the libp2p metrics to determine if any values have exceeded
* the configured maximums.
* @private
*/
_checkMetrics () {
const movingAverages = this._metrics.global.movingAverages
const received = movingAverages.dataReceived[this._options.movingAverageInterval].movingAverage()
_onceStarted () {
this._peerId = this._libp2p.peerInfo.id.toB58String()
}
_onStatsUpdate () {
const movingAvgs = this._stats.global.movingAverages
const received = movingAvgs.dataReceived[this._options.movingAverageInterval].movingAverage()
this._checkLimit('maxReceivedData', received)
const sent = movingAverages.dataSent[this._options.movingAverageInterval].movingAverage()
const sent = movingAvgs.dataSent[this._options.movingAverageInterval].movingAverage()
this._checkLimit('maxSentData', sent)
const total = received + sent
this._checkLimit('maxData', total)
debug('metrics update', total)
this._timer.reschedule(this._options.pollInterval)
debug('stats update', total)
}
/**
* Tracks the incoming connection and check the connection limit
* @param {Connection} connection
*/
onConnect (connection) {
const peerId = connection.remotePeer.toString()
this._connections.set(connection.id, connection)
if (!this._peerValues.has(peerId)) {
this._peerValues.set(peerId, this._options.defaultPeerValue)
_onPeerConnect (peerInfo) {
const peerId = peerInfo.id.toB58String()
debug('%s: connected to %s', this._peerId, peerId)
this._peerValues.set(peerId, this._options.defaultPeerValue)
this._peers.set(peerId, peerInfo)
this.emit('connected', peerId)
this._checkLimit('maxPeers', this._peers.size)
protocolsFromPeerInfo(peerInfo).forEach((protocolTag) => {
const protocol = this._peerCountPerProtocol[protocolTag]
if (!protocol) {
this._peerCountPerProtocol[protocolTag] = 0
}
this._peerCountPerProtocol[protocolTag]++
let peerProtocols = this._peerProtocols[peerId]
if (!peerProtocols) {
peerProtocols = this._peerProtocols[peerId] = new Set()
}
peerProtocols.add(protocolTag)
this._checkProtocolMaxPeersLimit(protocolTag, this._peerCountPerProtocol[protocolTag])
})
}
_onPeerDisconnect (peerInfo) {
const peerId = peerInfo.id.toB58String()
debug('%s: disconnected from %s', this._peerId, peerId)
this._peerValues.delete(peerId)
this._peers.delete(peerId)
const peerProtocols = this._peerProtocols[peerId]
if (peerProtocols) {
Array.from(peerProtocols).forEach((protocolTag) => {
const peerCountForProtocol = this._peerCountPerProtocol[protocolTag]
if (peerCountForProtocol) {
this._peerCountPerProtocol[protocolTag]--
}
})
}
this._checkLimit('maxConnections', this._connections.size)
this.emit('disconnected', peerId)
}
/**
* Removes the connection from tracking
* @param {Connection} connection
*/
onDisconnect (connection) {
this._connections.delete(connection.id)
this._peerValues.delete(connection.remotePeer.toString())
}
/**
* If the event loop is slow, maybe close a connection
* @private
* @param {*} summary The LatencyMonitor summary
*/
_onLatencyMeasure (summary) {
this._checkLimit('maxEventLoopDelay', summary.avgMs)
}
/**
* If the `value` of `name` has exceeded its limit, maybe close a connection
* @private
* @param {string} name The name of the field to check limits for
* @param {number} value The current value of the field
*/
_checkLimit (name, value) {
const limit = this._options[name]
debug('checking limit of %s. current value: %d of %d', name, value, limit)
if (value > limit) {
debug('%s: limit exceeded: %s, %d', this._peerId, name, value)
this.emit('limit:exceeded', name, value)
this._maybeDisconnectOne()
}
}
_checkProtocolMaxPeersLimit (protocolTag, value) {
debug('checking protocol limit. current value of %s is %d', protocolTag, value)
const limit = this._options.maxPeersPerProtocol[protocolTag]
if (value > limit) {
debug('%s: protocol max peers limit exceeded: %s, %d', this._peerId, protocolTag, value)
this.emit('limit:exceeded', protocolTag, value)
this._maybeDisconnectOne()
}
}
/**
* If we have more connections than our maximum, close a connection
* to the lowest valued peer.
* @private
*/
_maybeDisconnectOne () {
if (this._options.minConnections < this._connections.size) {
if (this._options.minPeers < this._peerValues.size) {
const peerValues = Array.from(this._peerValues).sort(byPeerValue)
debug('%s: sorted peer values: %j', this._peerId, peerValues)
const disconnectPeer = peerValues[0]
if (disconnectPeer) {
const peerId = disconnectPeer[0]
debug('%s: lowest value peer is %s', this._peerId, peerId)
debug('%s: closing a connection to %j', this._peerId, peerId)
for (const connection of this._connections.values()) {
if (connection.remotePeer.toString() === peerId) {
connection.close()
break
}
}
debug('%s: forcing disconnection from %j', this._peerId, peerId)
this._disconnectPeer(peerId)
}
}
}
_disconnectPeer (peerId) {
debug('preemptively disconnecting peer', peerId)
this.emit('%s: disconnect:preemptive', this._peerId, peerId)
const peer = this._peers.get(peerId)
this._libp2p.hangUp(peer, (err) => {
if (err) {
this.emit('error', err)
}
})
}
}
module.exports = ConnectionManager
@ -190,3 +187,32 @@ module.exports = ConnectionManager
function byPeerValue (peerValueEntryA, peerValueEntryB) {
return peerValueEntryA[1] - peerValueEntryB[1]
}
function fixMaxPeersPerProtocol (maxPeersPerProtocol) {
if (!maxPeersPerProtocol) {
maxPeersPerProtocol = {}
}
Object.keys(maxPeersPerProtocol).forEach((transportTag) => {
const max = maxPeersPerProtocol[transportTag]
delete maxPeersPerProtocol[transportTag]
maxPeersPerProtocol[transportTag.toLowerCase()] = max
})
return maxPeersPerProtocol
}
function protocolsFromPeerInfo (peerInfo) {
const protocolTags = new Set()
peerInfo.multiaddrs.forEach((multiaddr) => {
multiaddr.protos().map(protocolToProtocolTag).forEach((protocolTag) => {
protocolTags.add(protocolTag)
})
})
return Array.from(protocolTags)
}
function protocolToProtocolTag (protocol) {
return protocol.name.toLowerCase()
}

View File

@ -1,17 +1,12 @@
'use strict'
module.exports = {
DENY_TTL: 5 * 60 * 1e3, // How long before an errored peer can be dialed again
DENY_ATTEMPTS: 5, // Num of unsuccessful dials before a peer is permanently denied
DIAL_TIMEOUT: 30e3, // How long in ms a dial attempt is allowed to take
MAX_COLD_CALLS: 50, // How many dials w/o protocols that can be queued
MAX_PARALLEL_DIALS: 100, // Maximum allowed concurrent dials
MAX_PER_PEER_DIALS: 4, // Allowed parallel dials per DialRequest
METRICS: {
computeThrottleMaxQueueSize: 1000,
computeThrottleTimeout: 2000,
movingAverageIntervals: [
60 * 1000, // 1 minute
5 * 60 * 1000, // 5 minutes
15 * 60 * 1000 // 15 minutes
],
maxOldPeersRetention: 50
}
QUARTER_HOUR: 15 * 60e3,
PRIORITY_HIGH: 10,
PRIORITY_LOW: 20
}

View File

@ -1,18 +1,16 @@
'use strict'
const tryEach = require('async/tryEach')
const parallel = require('async/parallel')
const errCode = require('err-code')
const { messages, codes } = require('./errors')
const all = require('it-all')
const pAny = require('p-any')
const promisify = require('promisify-es6')
module.exports = (node) => {
const routers = node._modules.contentRouting || []
const dht = node._dht
// If we have the dht, make it first
if (dht) {
routers.unshift(dht)
if (node._dht) {
routers.unshift(node._dht)
}
return {
@ -21,93 +19,66 @@ module.exports = (node) => {
* Once a content router succeeds, iteration will stop.
*
* @param {CID} key The CID key of the content to find
* @param {object} [options]
* @param {number} [options.timeout] How long the query should run
* @param {number} [options.maxNumProviders] - maximum number of providers to find
* @returns {AsyncIterable<PeerInfo>}
* @param {object} options
* @param {number} options.maxTimeout How long the query should run
* @param {number} options.maxNumProviders - maximum number of providers to find
* @param {function(Error, Result<Array>)} callback
* @returns {void}
*/
async * findProviders (key, options) {
findProviders: promisify((key, options, callback) => {
if (typeof options === 'function') {
callback = options
options = {}
} else if (typeof options === 'number') { // This can be deprecated in a future release
options = {
maxTimeout: options
}
}
if (!routers.length) {
throw errCode(new Error('No content routers available'), 'NO_ROUTERS_AVAILABLE')
return callback(errCode(new Error('No content routers available'), 'NO_ROUTERS_AVAILABLE'))
}
const result = await pAny(
routers.map(async (router) => {
const provs = await all(router.findProviders(key, options))
if (!provs || !provs.length) {
throw errCode(new Error('not found'), 'NOT_FOUND')
const tasks = routers.map((router) => {
return (cb) => router.findProviders(key, options, (err, results) => {
if (err) {
return cb(err)
}
return provs
})
)
for (const pInfo of result) {
yield pInfo
}
},
// If we don't have any results, we need to provide an error to keep trying
if (!results || Object.keys(results).length === 0) {
return cb(errCode(new Error('not found'), 'NOT_FOUND'), null)
}
cb(null, results)
})
})
tryEach(tasks, (err, results) => {
if (err && err.code !== 'NOT_FOUND') {
return callback(err)
}
results = results || []
callback(null, results)
})
}),
/**
* Iterates over all content routers in parallel to notify it is
* a provider of the given key.
*
* @param {CID} key The CID key of the content to find
* @returns {Promise<void>}
* @param {function(Error)} callback
* @returns {void}
*/
async provide (key) { // eslint-disable-line require-await
provide: promisify((key, callback) => {
if (!routers.length) {
throw errCode(new Error('No content routers available'), 'NO_ROUTERS_AVAILABLE')
return callback(errCode(new Error('No content routers available'), 'NO_ROUTERS_AVAILABLE'))
}
return Promise.all(routers.map((router) => router.provide(key)))
},
/**
* Store the given key/value pair in the DHT.
* @param {Buffer} key
* @param {Buffer} value
* @param {Object} [options] - put options
* @param {number} [options.minPeers] - minimum number of peers required to successfully put
* @returns {Promise<void>}
*/
async put (key, value, options) { // eslint-disable-line require-await
if (!node.isStarted() || !dht.isStarted) {
throw errCode(new Error(messages.NOT_STARTED_YET), codes.DHT_NOT_STARTED)
}
return dht.put(key, value, options)
},
/**
* Get the value to the given key.
* Times out after 1 minute by default.
* @param {Buffer} key
* @param {Object} [options] - get options
* @param {number} [options.timeout] - optional timeout (default: 60000)
* @returns {Promise<{from: PeerId, val: Buffer}>}
*/
async get (key, options) { // eslint-disable-line require-await
if (!node.isStarted() || !dht.isStarted) {
throw errCode(new Error(messages.NOT_STARTED_YET), codes.DHT_NOT_STARTED)
}
return dht.get(key, options)
},
/**
* Get the `n` values to the given key without sorting.
* @param {Buffer} key
* @param {number} nVals
* @param {Object} [options] - get options
* @param {number} [options.timeout] - optional timeout (default: 60000)
* @returns {Promise<Array<{from: PeerId, val: Buffer}>>}
*/
async getMany (key, nVals, options) { // eslint-disable-line require-await
if (!node.isStarted() || !dht.isStarted) {
throw errCode(new Error(messages.NOT_STARTED_YET), codes.DHT_NOT_STARTED)
}
return dht.getMany(key, nVals, options)
}
parallel(routers.map((router) => {
return (cb) => router.provide(key, cb)
}), callback)
})
}
}

43
src/dht.js Normal file
View File

@ -0,0 +1,43 @@
'use strict'
const nextTick = require('async/nextTick')
const errCode = require('err-code')
const promisify = require('promisify-es6')
const { messages, codes } = require('./errors')
module.exports = (node) => {
return {
put: promisify((key, value, callback) => {
if (!node._dht) {
return nextTick(callback, errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED))
}
node._dht.put(key, value, callback)
}),
get: promisify((key, options, callback) => {
if (typeof options === 'function') {
callback = options
options = {}
}
if (!node._dht) {
return nextTick(callback, errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED))
}
node._dht.get(key, options, callback)
}),
getMany: promisify((key, nVals, options, callback) => {
if (typeof options === 'function') {
callback = options
options = {}
}
if (!node._dht) {
return nextTick(callback, errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED))
}
node._dht.getMany(key, nVals, options, callback)
})
}
}

127
src/dialer.js Normal file
View File

@ -0,0 +1,127 @@
'use strict'
const nextTick = require('async/nextTick')
const multiaddr = require('multiaddr')
const errCode = require('err-code')
const { default: PQueue } = require('p-queue')
const AbortController = require('abort-controller')
const debug = require('debug')
const log = debug('libp2p:dialer')
log.error = debug('libp2p:dialer:error')
const { codes } = require('./errors')
const {
MAX_PARALLEL_DIALS,
DIAL_TIMEOUT
} = require('./constants')
class Dialer {
/**
* @constructor
* @param {object} options
* @param {TransportManager} options.transportManager
* @param {number} options.concurrency Number of max concurrent dials. Defaults to `MAX_PARALLEL_DIALS`
* @param {number} options.timeout How long a dial attempt is allowed to take. Defaults to `DIAL_TIMEOUT`
*/
constructor ({
transportManager,
concurrency = MAX_PARALLEL_DIALS,
timeout = DIAL_TIMEOUT
}) {
this.transportManager = transportManager
this.concurrency = concurrency
this.timeout = timeout
this.queue = new PQueue({ concurrency, timeout, throwOnTimeout: true })
/**
* @property {IdentifyService}
*/
this._identifyService = null
}
set identifyService (service) {
this._identifyService = service
}
/**
* @type {IdentifyService}
*/
get identifyService () {
return this._identifyService
}
/**
* Connects to a given `Multiaddr`. `addr` should include the id of the peer being
* dialed, it will be used for encryption verification.
*
* @async
* @param {Multiaddr} addr The address to dial
* @param {object} [options]
* @param {AbortSignal} [options.signal] An AbortController signal
* @returns {Promise<Connection>}
*/
async connectToMultiaddr (addr, options = {}) {
addr = multiaddr(addr)
let conn
let controller
if (!options.signal) {
controller = new AbortController()
options.signal = controller.signal
}
try {
conn = await this.queue.add(() => this.transportManager.dial(addr, options))
} catch (err) {
if (err.name === 'TimeoutError') {
controller.abort()
err.code = codes.ERR_TIMEOUT
}
log.error('Error dialing address %s,', addr, err)
throw err
}
// Perform a delayed Identify handshake
if (this.identifyService) {
nextTick(async () => {
try {
await this.identifyService.identify(conn, conn.remotePeer)
// TODO: Update the PeerStore with the information from identify
} catch (err) {
log.error(err)
}
})
}
return conn
}
/**
* Connects to a given `PeerInfo` by dialing all of its known addresses.
* The dial to the first address that is successfully able to upgrade a connection
* will be used.
*
* @async
* @param {PeerInfo} peerInfo The remote peer to dial
* @param {object} [options]
* @param {AbortSignal} [options.signal] An AbortController signal
* @returns {Promise<Connection>}
*/
async connectToPeer (peerInfo, options = {}) {
const addrs = peerInfo.multiaddrs.toArray()
for (const addr of addrs) {
try {
return await this.connectToMultiaddr(addr, options)
} catch (_) {
// The error is already logged, just move to the next addr
continue
}
}
const err = errCode(new Error('Could not dial peer, all addresses failed'), codes.ERR_CONNECTION_FAILED)
log.error(err)
throw err
}
}
module.exports = Dialer

View File

@ -1,80 +0,0 @@
'use strict'
const AbortController = require('abort-controller')
const anySignal = require('any-signal')
const debug = require('debug')
const errCode = require('err-code')
const log = debug('libp2p:dialer:request')
log.error = debug('libp2p:dialer:request:error')
const FIFO = require('p-fifo')
const pAny = require('p-any')
class DialRequest {
/**
* Manages running the `dialAction` on multiple provided `addrs` in parallel
* up to a maximum determined by the number of tokens returned
* from `dialer.getTokens`. Once a DialRequest is created, it can be
* started using `DialRequest.run(options)`. Once a single dial has succeeded,
* all other dials in the request will be cancelled.
* @param {object} options
* @param {Multiaddr[]} options.addrs
* @param {function(Multiaddr):Promise<Connection>} options.dialAction
* @param {Dialer} options.dialer
*/
constructor ({
addrs,
dialAction,
dialer
}) {
this.addrs = addrs
this.dialer = dialer
this.dialAction = dialAction
}
/**
* @async
* @param {object} options
* @param {AbortSignal} options.signal An AbortController signal
* @returns {Connection}
*/
async run (options) {
const tokens = this.dialer.getTokens(this.addrs.length)
// If no tokens are available, throw
if (tokens.length < 1) {
throw errCode(new Error('No dial tokens available'), 'ERR_NO_DIAL_TOKENS')
}
const tokenHolder = new FIFO()
tokens.forEach(token => tokenHolder.push(token))
const dialAbortControllers = this.addrs.map(() => new AbortController())
let completedDials = 0
try {
return await pAny(this.addrs.map(async (addr, i) => {
const token = await tokenHolder.shift() // get token
let conn
try {
const signal = dialAbortControllers[i].signal
conn = await this.dialAction(addr, { ...options, signal: anySignal([signal, options.signal]) })
// Remove the successful AbortController so it is not aborted
dialAbortControllers.splice(i, 1)
} finally {
completedDials++
// If we have more or equal dials remaining than tokens, recycle the token, otherwise release it
if (this.addrs.length - completedDials >= tokens.length) {
tokenHolder.push(token)
} else {
this.dialer.releaseToken(tokens.splice(tokens.indexOf(token), 1)[0])
}
}
return conn
}))
} finally {
dialAbortControllers.map(c => c.abort()) // success/failure happened, abort everything else
tokens.forEach(token => this.dialer.releaseToken(token)) // release tokens back to the dialer
}
}
}
module.exports.DialRequest = DialRequest

View File

@ -1,214 +0,0 @@
'use strict'
const multiaddr = require('multiaddr')
const errCode = require('err-code')
const TimeoutController = require('timeout-abort-controller')
const anySignal = require('any-signal')
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const debug = require('debug')
const log = debug('libp2p:dialer')
log.error = debug('libp2p:dialer:error')
const { DialRequest } = require('./dial-request')
const { codes } = require('../errors')
const {
DIAL_TIMEOUT,
MAX_PARALLEL_DIALS,
MAX_PER_PEER_DIALS
} = require('../constants')
class Dialer {
/**
* @constructor
* @param {object} options
* @param {TransportManager} options.transportManager
* @param {Peerstore} peerStore
* @param {number} options.concurrency Number of max concurrent dials. Defaults to `MAX_PARALLEL_DIALS`
* @param {number} options.timeout How long a dial attempt is allowed to take. Defaults to `DIAL_TIMEOUT`
*/
constructor ({
transportManager,
peerStore,
concurrency = MAX_PARALLEL_DIALS,
timeout = DIAL_TIMEOUT,
perPeerLimit = MAX_PER_PEER_DIALS
}) {
this.transportManager = transportManager
this.peerStore = peerStore
this.concurrency = concurrency
this.timeout = timeout
this.perPeerLimit = perPeerLimit
this.tokens = [...new Array(concurrency)].map((_, index) => index)
this._pendingDials = new Map()
}
/**
* Clears any pending dials
*/
destroy () {
for (const dial of this._pendingDials.values()) {
try {
dial.controller.abort()
} catch (err) {
log.error(err)
}
}
this._pendingDials.clear()
}
/**
* Connects to a given `PeerId` or `Multiaddr` by dialing all of its known addresses.
* The dial to the first address that is successfully able to upgrade a connection
* will be used.
*
* @param {PeerInfo|Multiaddr} peer The peer to dial
* @param {object} [options]
* @param {AbortSignal} [options.signal] An AbortController signal
* @returns {Promise<Connection>}
*/
async connectToPeer (peer, options = {}) {
const dialTarget = this._createDialTarget(peer)
if (dialTarget.addrs.length === 0) {
throw errCode(new Error('The dial request has no addresses'), 'ERR_NO_DIAL_MULTIADDRS')
}
const pendingDial = this._pendingDials.get(dialTarget.id) || this._createPendingDial(dialTarget, options)
try {
const connection = await pendingDial.promise
log('dial succeeded to %s', dialTarget.id)
return connection
} catch (err) {
// Error is a timeout
if (pendingDial.controller.signal.aborted) {
err.code = codes.ERR_TIMEOUT
}
log.error(err)
throw err
} finally {
pendingDial.destroy()
}
}
/**
* @typedef DialTarget
* @property {string} id
* @property {Multiaddr[]} addrs
*/
/**
* Creates a DialTarget. The DialTarget is used to create and track
* the DialRequest to a given peer.
* @private
* @param {PeerInfo|Multiaddr} peer A PeerId or Multiaddr
* @returns {DialTarget}
*/
_createDialTarget (peer) {
const dialable = Dialer.getDialable(peer)
if (multiaddr.isMultiaddr(dialable)) {
return {
id: dialable.toString(),
addrs: [dialable]
}
}
const addrs = this.peerStore.multiaddrsForPeer(dialable)
return {
id: dialable.id.toString(),
addrs
}
}
/**
* @typedef PendingDial
* @property {DialRequest} dialRequest
* @property {TimeoutController} controller
* @property {Promise} promise
* @property {function():void} destroy
*/
/**
* Creates a PendingDial that wraps the underlying DialRequest
* @private
* @param {DialTarget} dialTarget
* @param {object} [options]
* @param {AbortSignal} [options.signal] An AbortController signal
* @returns {PendingDial}
*/
_createPendingDial (dialTarget, options) {
const dialAction = (addr, options) => {
if (options.signal.aborted) throw errCode(new Error('already aborted'), 'ERR_ALREADY_ABORTED')
return this.transportManager.dial(addr, options)
}
const dialRequest = new DialRequest({
addrs: dialTarget.addrs,
dialAction,
dialer: this
})
// Combine the timeout signal and options.signal, if provided
const timeoutController = new TimeoutController(this.timeout)
const signals = [timeoutController.signal]
options.signal && signals.push(options.signal)
const signal = anySignal(signals)
const pendingDial = {
dialRequest,
controller: timeoutController,
promise: dialRequest.run({ ...options, signal }),
destroy: () => {
timeoutController.clear()
this._pendingDials.delete(dialTarget.id)
}
}
this._pendingDials.set(dialTarget.id, pendingDial)
return pendingDial
}
getTokens (num) {
const total = Math.min(num, this.perPeerLimit, this.tokens.length)
const tokens = this.tokens.splice(0, total)
log('%d tokens request, returning %d, %d remaining', num, total, this.tokens.length)
return tokens
}
releaseToken (token) {
// Guard against duplicate releases
if (this.tokens.indexOf(token) > -1) return
log('token %d released', token)
this.tokens.push(token)
}
/**
* Converts the given `peer` into a `PeerInfo` or `Multiaddr`.
* @static
* @param {PeerInfo|PeerId|Multiaddr|string} peer
* @returns {PeerInfo|Multiaddr}
*/
static getDialable (peer) {
if (PeerInfo.isPeerInfo(peer)) return peer
if (typeof peer === 'string') {
peer = multiaddr(peer)
}
let addr
if (multiaddr.isMultiaddr(peer)) {
addr = peer
try {
peer = PeerId.createFromCID(peer.getPeerId())
} catch (err) {
// Couldn't get the PeerId, just use the address
return peer
}
}
if (PeerId.isPeerId(peer)) {
peer = new PeerInfo(peer)
}
addr && peer.multiaddrs.add(addr)
return peer
}
}
module.exports = Dialer

View File

@ -8,7 +8,6 @@ exports.messages = {
exports.codes = {
DHT_DISABLED: 'ERR_DHT_DISABLED',
PUBSUB_NOT_STARTED: 'ERR_PUBSUB_NOT_STARTED',
DHT_NOT_STARTED: 'ERR_DHT_NOT_STARTED',
ERR_CONNECTION_ENDED: 'ERR_CONNECTION_ENDED',
ERR_CONNECTION_FAILED: 'ERR_CONNECTION_FAILED',
ERR_NODE_NOT_STARTED: 'ERR_NODE_NOT_STARTED',
@ -16,7 +15,6 @@ exports.codes = {
ERR_DISCOVERED_SELF: 'ERR_DISCOVERED_SELF',
ERR_DUPLICATE_TRANSPORT: 'ERR_DUPLICATE_TRANSPORT',
ERR_ENCRYPTION_FAILED: 'ERR_ENCRYPTION_FAILED',
ERR_HOP_REQUEST_FAILED: 'ERR_HOP_REQUEST_FAILED',
ERR_INVALID_KEY: 'ERR_INVALID_KEY',
ERR_INVALID_MESSAGE: 'ERR_INVALID_MESSAGE',
ERR_INVALID_PEER: 'ERR_INVALID_PEER',

View File

@ -56,7 +56,10 @@ function getPeerInfoRemote (peer, libp2p) {
try {
peerInfo = getPeerInfo(peer, libp2p.peerStore)
} catch (err) {
throw errCode(err, 'ERR_INVALID_PEER_TYPE')
return Promise.reject(errCode(
new Error(`${peer} is not a valid peer type`),
'ERR_INVALID_PEER_TYPE'
))
}
// If we don't have an address for the peer, attempt to find it
@ -64,7 +67,7 @@ function getPeerInfoRemote (peer, libp2p) {
return libp2p.peerRouting.findPeer(peerInfo.id)
}
return peerInfo
return Promise.resolve(peerInfo)
}
module.exports = {

View File

@ -9,7 +9,7 @@ const { collect, take } = require('streaming-iterables')
const PeerInfo = require('peer-info')
const PeerId = require('peer-id')
const multiaddr = require('multiaddr')
const { toBuffer } = require('it-buffer')
const { toBuffer } = require('../util')
const Message = require('./message')
@ -148,9 +148,10 @@ class IdentifyService {
*
* @async
* @param {Connection} connection
* @param {PeerID} expectedPeer The PeerId the identify response should match
* @returns {Promise<void>}
*/
async identify (connection) {
async identify (connection, expectedPeer) {
const { stream } = await connection.newStream(MULTICODEC_IDENTIFY)
const [data] = await pipe(
stream,
@ -180,7 +181,7 @@ class IdentifyService {
const id = await PeerId.createFromPubKey(publicKey)
const peerInfo = new PeerInfo(id)
if (connection.remotePeer.toString() !== id.toString()) {
if (expectedPeer && expectedPeer.toB58String() !== id.toB58String()) {
throw errCode(new Error('identified peer does not match the expected peer'), codes.ERR_INVALID_PEER)
}
@ -191,7 +192,7 @@ class IdentifyService {
IdentifyService.updatePeerAddresses(peerInfo, listenAddrs)
IdentifyService.updatePeerProtocols(peerInfo, protocols)
this.registrar.peerStore.replace(peerInfo)
this.registrar.peerStore.update(peerInfo)
// TODO: Track our observed address so that we can score it
log('received observed address of %s', observedAddr)
}
@ -282,7 +283,7 @@ class IdentifyService {
IdentifyService.updatePeerProtocols(peerInfo, message.protocols)
// Update the peer in the PeerStore
this.registrar.peerStore.replace(peerInfo)
this.registrar.peerStore.update(peerInfo)
}
}

View File

@ -1,38 +1,53 @@
'use strict'
const FSM = require('fsm-event')
const { EventEmitter } = require('events')
const debug = require('debug')
const log = debug('libp2p')
log.error = debug('libp2p:error')
const errCode = require('err-code')
const promisify = require('promisify-es6')
const each = require('async/each')
const PeerInfo = require('peer-info')
const multiaddr = require('multiaddr')
const Switch = require('./switch')
const Ping = require('./ping')
const { emitFirst } = require('./util')
const peerRouting = require('./peer-routing')
const contentRouting = require('./content-routing')
const dht = require('./dht')
const pubsub = require('./pubsub')
const { getPeerInfo } = require('./get-peer-info')
const { getPeerInfo, getPeerInfoRemote } = require('./get-peer-info')
const { validate: validateConfig } = require('./config')
const { codes } = require('./errors')
const ConnectionManager = require('./connection-manager')
const Circuit = require('./circuit')
const Dialer = require('./dialer')
const Metrics = require('./metrics')
const TransportManager = require('./transport-manager')
const Upgrader = require('./upgrader')
const PeerStore = require('./peer-store')
const Registrar = require('./registrar')
const ping = require('./ping')
const {
IdentifyService,
multicodecs: IDENTIFY_PROTOCOLS
} = require('./identify')
const notStarted = (action, state) => {
return errCode(
new Error(`libp2p cannot ${action} when not started; state is ${state}`),
codes.ERR_NODE_NOT_STARTED
)
}
/**
* @fires Libp2p#error Emitted when an error occurs
* @fires Libp2p#peer:connect Emitted when a peer is connected to this node
* @fires Libp2p#peer:disconnect Emitted when a peer disconnects from this node
* @fires Libp2p#peer:discovery Emitted when a peer is discovered
* @fires Libp2p#start Emitted when the node and its services has started
* @fires Libp2p#stop Emitted when the node and its services has stopped
*/
class Libp2p extends EventEmitter {
constructor (_options) {
@ -48,38 +63,28 @@ class Libp2p extends EventEmitter {
this._modules = this._options.modules
this._config = this._options.config
this._transport = [] // Transport instances/references
this._discovery = new Map() // Discovery service instances/references
this._discovery = [] // Discovery service instances/references
if (this._options.metrics.enabled) {
this.metrics = new Metrics(this._options.metrics)
}
this.peerStore = new PeerStore()
// create the switch, and listen for errors
this._switch = new Switch(this.peerInfo, this.peerStore, this._options.switch)
// Setup the Upgrader
this.upgrader = new Upgrader({
localPeer: this.peerInfo.id,
metrics: this.metrics,
onConnection: (connection) => {
const peerInfo = this.peerStore.put(new PeerInfo(connection.remotePeer), { silent: true })
this.registrar.onConnect(peerInfo, connection)
this.connectionManager.onConnect(connection)
this.emit('peer:connect', peerInfo)
const peerInfo = getPeerInfo(connection.remotePeer)
// Run identify for every connection
if (this.identifyService) {
this.identifyService.identify(connection, connection.remotePeer)
.catch(log.error)
}
this.peerStore.put(peerInfo)
this.registrar.onConnect(peerInfo, connection)
this.emit('peer:connect', peerInfo)
},
onConnectionEnd: (connection) => {
const peerInfo = Dialer.getDialable(connection.remotePeer)
this.registrar.onDisconnect(peerInfo, connection)
this.connectionManager.onDisconnect(connection)
const peerInfo = getPeerInfo(connection.remotePeer)
// If there are no connections to the peer, disconnect
if (!this.registrar.getConnection(peerInfo)) {
this.emit('peer:disconnect', peerInfo)
this.metrics && this.metrics.onPeerDisconnected(peerInfo.id)
}
this.registrar.onDisconnect(peerInfo, connection)
this.emit('peer:disconnect', peerInfo)
}
})
@ -88,14 +93,14 @@ class Libp2p extends EventEmitter {
this.handle = this.handle.bind(this)
this.registrar.handle = this.handle
// Create the Connection Manager
this.connectionManager = new ConnectionManager(this, this._options.connectionManager)
// Setup the transport manager
this.transportManager = new TransportManager({
libp2p: this,
upgrader: this.upgrader
})
this._modules.transport.forEach((Transport) => {
this.transportManager.add(Transport.prototype[Symbol.toStringTag], Transport)
})
// Attach crypto channels
if (this._modules.connEncryption) {
@ -106,23 +111,9 @@ class Libp2p extends EventEmitter {
}
this.dialer = new Dialer({
transportManager: this.transportManager,
peerStore: this.peerStore,
concurrency: this._options.dialer.maxParallelDials,
perPeerLimit: this._options.dialer.maxDialsPerPeer,
timeout: this._options.dialer.dialTimeout
transportManager: this.transportManager
})
this._modules.transport.forEach((Transport) => {
const key = Transport.prototype[Symbol.toStringTag]
const transportOptions = this._config.transport[key]
this.transportManager.add(key, Transport, transportOptions)
})
if (this._config.relay.enabled) {
this.transportManager.add(Circuit.prototype[Symbol.toStringTag], Circuit)
}
// Attach stream multiplexers
if (this._modules.streamMuxer) {
const muxers = this._modules.streamMuxer
@ -131,12 +122,12 @@ class Libp2p extends EventEmitter {
})
// Add the identify service since we can multiplex
this.identifyService = new IdentifyService({
this.dialer.identifyService = new IdentifyService({
registrar: this.registrar,
peerInfo: this.peerInfo,
protocols: this.upgrader.protocols
})
this.handle(Object.values(IDENTIFY_PROTOCOLS), this.identifyService.handleMessage)
this.handle(Object.values(IDENTIFY_PROTOCOLS), this.dialer.identifyService.handleMessage)
}
// Attach private network protector
@ -147,13 +138,10 @@ class Libp2p extends EventEmitter {
}
// dht provided components (peerRouting, contentRouting, dht)
if (this._modules.dht) {
if (this._config.dht.enabled) {
const DHT = this._modules.dht
this._dht = new DHT({
dialer: this.dialer,
peerInfo: this.peerInfo,
peerStore: this.peerStore,
registrar: this.registrar,
this._dht = new DHT(this._switch, {
datastore: this.datastore,
...this._config.dht
})
@ -168,11 +156,65 @@ class Libp2p extends EventEmitter {
// peer and content routing will automatically get modules from _modules and _dht
this.peerRouting = peerRouting(this)
this.contentRouting = contentRouting(this)
this.dht = dht(this)
// Mount default protocols
ping.mount(this)
Ping.mount(this._switch)
this._onDiscoveryPeer = this._onDiscoveryPeer.bind(this)
this.state = new FSM('STOPPED', {
STOPPED: {
start: 'STARTING',
stop: 'STOPPED',
done: 'STOPPED'
},
STARTING: {
done: 'STARTED',
abort: 'STOPPED',
stop: 'STOPPING'
},
STARTED: {
stop: 'STOPPING',
start: 'STARTED'
},
STOPPING: {
stop: 'STOPPING',
done: 'STOPPED'
}
})
this.state.on('STARTING', () => {
log('libp2p is starting')
this._onStarting()
})
this.state.on('STOPPING', () => {
log('libp2p is stopping')
})
this.state.on('STARTED', () => {
log('libp2p has started')
this.emit('start')
})
this.state.on('STOPPED', () => {
log('libp2p has stopped')
this.emit('stop')
})
this.state.on('error', (err) => {
log.error(err)
this.emit('error', err)
})
// Once we start, emit and dial any peers we may have already discovered
this.state.on('STARTED', () => {
for (const peerInfo of this.peerStore.peers) {
this.emit('peer:discovery', peerInfo)
this._maybeConnect(peerInfo)
}
})
this._peerDiscovered = this._peerDiscovered.bind(this)
// promisify all instance methods
;['start', 'hangUp', 'ping'].forEach(method => {
this[method] = promisify(this[method], { context: this })
})
}
/**
@ -191,22 +233,14 @@ class Libp2p extends EventEmitter {
}
/**
* Starts the libp2p node and all its subsystems
* Starts the libp2p node and all sub services
*
* @returns {Promise<void>}
* @param {function(Error)} callback
* @returns {void}
*/
async start () {
log('libp2p is starting')
try {
await this._onStarting()
await this._onDidStart()
log('libp2p has started')
} catch (err) {
this.emit('error', err)
log.error('An error occurred starting libp2p', err)
await this.stop()
throw err
}
start (callback = () => {}) {
emitFirst(this, ['error', 'start'], callback)
this.state('start')
}
/**
@ -215,42 +249,23 @@ class Libp2p extends EventEmitter {
* @returns {void}
*/
async stop () {
log('libp2p is stopping')
this.state('stop')
try {
this.connectionManager.stop()
await Promise.all([
this.pubsub && this.pubsub.stop(),
this._dht && this._dht.stop(),
this.metrics && this.metrics.stop()
])
this.pubsub && await this.pubsub.stop()
await this.transportManager.close()
await this.registrar.close()
ping.unmount(this)
this.dialer.destroy()
await this._switch.stop()
} catch (err) {
if (err) {
log.error(err)
this.emit('error', err)
}
}
this._isStarted = false
log('libp2p has stopped')
this.state('done')
}
isStarted () {
return this._isStarted
}
/**
* Gets a Map of the current connections. The keys are the stringified
* `PeerId` of the peer. The value is an array of Connections to that peer.
* @returns {Map<string, Connection[]>}
*/
get connections () {
return this.registrar.connections
return this.state ? this.state._state === 'STARTED' : false
}
/**
@ -279,49 +294,60 @@ class Libp2p extends EventEmitter {
* @returns {Promise<Connection|*>}
*/
async dialProtocol (peer, protocols, options) {
const dialable = Dialer.getDialable(peer)
let connection
if (PeerInfo.isPeerInfo(dialable)) {
this.peerStore.put(dialable, { silent: true })
connection = this.registrar.getConnection(dialable)
if (multiaddr.isMultiaddr(peer)) {
connection = await this.dialer.connectToMultiaddr(peer, options)
} else {
peer = await getPeerInfoRemote(peer, this)
connection = await this.dialer.connectToPeer(peer, options)
}
if (!connection) {
connection = await this.dialer.connectToPeer(dialable, options)
}
const peerInfo = getPeerInfo(connection.remotePeer)
// If a protocol was provided, create a new stream
if (protocols) {
return connection.newStream(protocols)
const stream = await connection.newStream(protocols)
peerInfo.protocols.add(stream.protocol)
this.peerStore.put(peerInfo)
return stream
}
this.peerStore.put(peerInfo)
return connection
}
/**
* Disconnects all connections to the given `peer`
* Disconnects from the given peer
*
* @param {PeerInfo|PeerId|multiaddr|string} peer the peer to close connections to
* @returns {Promise<void>}
* @param {PeerInfo|PeerId|Multiaddr|string} peer The peer to ping
* @param {function(Error)} callback
* @returns {void}
*/
hangUp (peer) {
const peerInfo = getPeerInfo(peer, this.peerStore)
return Promise.all(
this.registrar.connections.get(peerInfo.id.toString()).map(connection => {
return connection.close()
})
)
hangUp (peer, callback) {
getPeerInfoRemote(peer, this)
.then(peerInfo => {
this._switch.hangUp(peerInfo, callback)
}, callback)
}
/**
* Pings the given peer
* Pings the provided peer
*
* @param {PeerInfo|PeerId|Multiaddr|string} peer The peer to ping
* @returns {Promise<number>}
* @param {function(Error, Ping)} callback
* @returns {void}
*/
async ping (peer) {
const peerInfo = await getPeerInfo(peer, this.peerStore)
ping (peer, callback) {
if (!this.isStarted()) {
return callback(notStarted('ping', this.state._state))
}
return ping(this, peerInfo)
getPeerInfoRemote(peer, this)
.then(peerInfo => {
callback(null, new Ping(this._switch, peerInfo))
}, callback)
}
/**
@ -335,10 +361,7 @@ class Libp2p extends EventEmitter {
this.upgrader.protocols.set(protocol, handler)
})
// Only push if libp2p is running
if (this.isStarted() && this.identifyService) {
this.identifyService.pushToPeerStore(this.peerStore)
}
this.dialer.identifyService.pushToPeerStore(this.peerStore)
}
/**
@ -352,78 +375,57 @@ class Libp2p extends EventEmitter {
this.upgrader.protocols.delete(protocol)
})
// Only push if libp2p is running
if (this.isStarted() && this.identifyService) {
this.identifyService.pushToPeerStore(this.peerStore)
}
this.dialer.identifyService.pushToPeerStore(this.peerStore)
}
async _onStarting () {
// Listen on the addresses supplied in the peerInfo
if (!this._modules.transport) {
this.emit('error', new Error('no transports were present'))
return this.state('abort')
}
const multiaddrs = this.peerInfo.multiaddrs.toArray()
await this.transportManager.listen(multiaddrs)
// The addresses may change once the listener starts
// eg /ip4/0.0.0.0/tcp/0 => /ip4/192.168.1.0/tcp/58751
this.peerInfo.multiaddrs.clear()
for (const ma of this.transportManager.getAddrs()) {
this.peerInfo.multiaddrs.add(ma)
}
// Start parallel tasks
const tasks = [
this.transportManager.listen(multiaddrs)
]
if (this._config.pubsub.enabled) {
this.pubsub && this.pubsub.start()
}
// DHT subsystem
if (this._config.dht.enabled) {
this._dht && this._dht.start()
// TODO: this should be modified once random-walk is used as
// the other discovery modules
this._dht.on('peer', this._onDiscoveryPeer)
try {
await Promise.all(tasks)
} catch (err) {
log.error(err)
this.emit('error', err)
return this.state('stop')
}
// Start metrics if present
this.metrics && this.metrics.start()
// libp2p has started
this.state('done')
}
/**
* Called when libp2p has started and before it returns
* @private
*/
_onDidStart () {
this._isStarted = true
this.connectionManager.start()
this.peerStore.on('peer', peerInfo => {
this.emit('peer:discovery', peerInfo)
this._maybeConnect(peerInfo)
})
// Peer discovery
this._setupPeerDiscovery()
// Once we start, emit and dial any peers we may have already discovered
for (const peerInfo of this.peerStore.peers.values()) {
this.emit('peer:discovery', peerInfo)
this._maybeConnect(peerInfo)
}
}
/**
* Called whenever peer discovery services emit `peer` events.
* Known peers may be emitted.
* Handles discovered peers. Each discovered peer will be emitted via
* the `peer:discovery` event. If auto dial is enabled for libp2p
* and the current connection count is under the low watermark, the
* peer will be dialed.
* @private
* @param {PeerInfo} peerInfo
*/
_onDiscoveryPeer (peerInfo) {
if (peerInfo.id.toString() === this.peerInfo.id.toString()) {
_peerDiscovered (peerInfo) {
if (peerInfo.id.toB58String() === this.peerInfo.id.toB58String()) {
log.error(new Error(codes.ERR_DISCOVERED_SELF))
return
}
this.peerStore.put(peerInfo)
peerInfo = this.peerStore.put(peerInfo)
if (!this.isStarted()) return
this.emit('peer:discovery', peerInfo)
this._maybeConnect(peerInfo)
}
/**
@ -433,17 +435,15 @@ class Libp2p extends EventEmitter {
* @private
* @param {PeerInfo} peerInfo
*/
async _maybeConnect (peerInfo) {
// If auto dialing is on and we have no connection to the peer, check if we should dial
if (this._config.peerDiscovery.autoDial === true && !this.registrar.getConnection(peerInfo)) {
_maybeConnect (peerInfo) {
// If auto dialing is on, check if we should dial
if (this._config.peerDiscovery.autoDial === true && !peerInfo.isConnected()) {
const minPeers = this._options.connectionManager.minPeers || 0
if (minPeers > this.connectionManager._connections.size) {
log('connecting to discovered peer %s', peerInfo.id.toString())
try {
await this.dialer.connectToPeer(peerInfo)
} catch (err) {
log.error('could not connect to discovered peer', err)
}
if (minPeers > Object.keys(this._switch.connection.connections).length) {
log('connecting to discovered peer')
this._switch.dialer.connect(peerInfo, (err) => {
err && log.error('could not connect to discovered peer', err)
})
}
}
}
@ -452,10 +452,10 @@ class Libp2p extends EventEmitter {
* Initializes and starts peer discovery services
*
* @private
* @returns {Promise<void>}
* @param {function(Error)} callback
*/
_setupPeerDiscovery () {
const setupService = (DiscoveryService) => {
_setupPeerDiscovery (callback) {
for (const DiscoveryService of this._modules.peerDiscovery) {
let config = {
enabled: true // on by default
}
@ -466,8 +466,7 @@ class Libp2p extends EventEmitter {
config = { ...config, ...this._config.peerDiscovery[DiscoveryService.tag] }
}
if (config.enabled &&
!this._discovery.has(DiscoveryService.tag)) { // not already added
if (config.enabled) {
let discoveryService
if (typeof DiscoveryService === 'function') {
@ -476,24 +475,14 @@ class Libp2p extends EventEmitter {
discoveryService = DiscoveryService
}
discoveryService.on('peer', this._onDiscoveryPeer)
this._discovery.set(DiscoveryService.tag, discoveryService)
discoveryService.on('peer', this._peerDiscovered)
this._discovery.push(discoveryService)
}
}
// Discovery modules
for (const DiscoveryService of this._modules.peerDiscovery || []) {
setupService(DiscoveryService)
}
// Transport modules with discovery
for (const Transport of this.transportManager.getTransports()) {
if (Transport.discovery) {
setupService(Transport.discovery)
}
}
return Promise.all(Array.from(this._discovery.values(), d => d.start()))
each(this._discovery, (d, cb) => {
d.start(cb)
}, callback)
}
}

View File

@ -1,247 +0,0 @@
'use strict'
const mergeOptions = require('merge-options')
const pipe = require('it-pipe')
const { tap } = require('streaming-iterables')
const oldPeerLRU = require('./old-peers')
const { METRICS: defaultOptions } = require('../constants')
const Stats = require('./stats')
const initialCounters = [
'dataReceived',
'dataSent'
]
const directionToEvent = {
in: 'dataReceived',
out: 'dataSent'
}
class Metrics {
/**
*
* @param {object} options
* @param {number} options.computeThrottleMaxQueueSize
* @param {number} options.computeThrottleTimeout
* @param {Array<number>} options.movingAverageIntervals
* @param {number} options.maxOldPeersRetention
*/
constructor (options) {
this._options = mergeOptions(defaultOptions, options)
this._globalStats = new Stats(initialCounters, this._options)
this._peerStats = new Map()
this._protocolStats = new Map()
this._oldPeers = oldPeerLRU(this._options.maxOldPeersRetention)
this._running = false
this._onMessage = this._onMessage.bind(this)
}
/**
* Must be called for stats to saved. Any data pushed for tracking
* will be ignored.
*/
start () {
this._running = true
}
/**
* Stops all averages timers and prevents new data from being tracked.
* Once `stop` is called, `start` must be called to resume stats tracking.
*/
stop () {
this._running = false
this._globalStats.stop()
for (const stats of this._peerStats.values()) {
stats.stop()
}
for (const stats of this._protocolStats.values()) {
stats.stop()
}
}
/**
* Gets the global `Stats` object
* @returns {Stats}
*/
get global () {
return this._globalStats
}
/**
* Returns a list of `PeerId` strings currently being tracked
* @returns {Array<string>}
*/
get peers () {
return Array.from(this._peerStats.keys())
}
/**
* Returns the `Stats` object for the given `PeerId` whether it
* is a live peer, or in the disconnected peer LRU cache.
* @param {PeerId} peerId
* @returns {Stats}
*/
forPeer (peerId) {
const idString = peerId.toString()
return this._peerStats.get(idString) || this._oldPeers.get(idString)
}
/**
* Returns a list of all protocol strings currently being tracked.
* @returns {Array<string>}
*/
get protocols () {
return Array.from(this._protocolStats.keys())
}
/**
* Returns the `Stats` object for the given `protocol`.
* @param {string} protocol
* @returns {Stats}
*/
forProtocol (protocol) {
return this._protocolStats.get(protocol)
}
/**
* Should be called when all connections to a given peer
* have closed. The `Stats` collection for the peer will
* be stopped and moved to an LRU for temporary retention.
* @param {PeerId} peerId
*/
onPeerDisconnected (peerId) {
const idString = peerId.toString()
const peerStats = this._peerStats.get(idString)
if (peerStats) {
peerStats.stop()
this._peerStats.delete(idString)
this._oldPeers.set(idString, peerStats)
}
}
/**
* Takes the metadata for a message and tracks it in the
* appropriate categories. If the protocol is present, protocol
* stats will also be tracked.
*
* @private
* @param {object} params
* @param {PeerId} params.remotePeer Remote peer
* @param {string} [params.protocol] Protocol string the stream is running
* @param {string} params.direction One of ['in','out']
* @param {number} params.dataLength Size of the message
* @returns {void}
*/
_onMessage ({ remotePeer, protocol, direction, dataLength }) {
if (!this._running) return
const key = directionToEvent[direction]
let peerStats = this.forPeer(remotePeer)
if (!peerStats) {
peerStats = new Stats(initialCounters, this._options)
this._peerStats.set(remotePeer.toString(), peerStats)
}
// Peer and global stats
peerStats.push(key, dataLength)
this._globalStats.push(key, dataLength)
// Protocol specific stats
if (protocol) {
let protocolStats = this.forProtocol(protocol)
if (!protocolStats) {
protocolStats = new Stats(initialCounters, this._options)
this._protocolStats.set(protocol, protocolStats)
}
protocolStats.push(key, dataLength)
}
}
/**
* Replaces the `PeerId` string with the given `peerId`.
* If stats are already being tracked for the given `peerId`, the
* placeholder stats will be merged with the existing stats.
* @param {string} placeholder A peerId string
* @param {PeerId} peerId
*/
updatePlaceholder (placeholder, peerId) {
if (!this._running) return
const placeholderStats = this.forPeer(placeholder)
const peerIdString = peerId.toString()
const existingStats = this.forPeer(peerId)
let mergedStats = placeholderStats
// If we already have stats, merge the two
if (existingStats) {
// If existing, merge
mergedStats = Metrics.mergeStats(existingStats, mergedStats)
// Attempt to delete from the old peers list just in case it was tracked there
this._oldPeers.delete(peerIdString)
}
this._peerStats.delete(placeholder.toString())
this._peerStats.set(peerIdString, mergedStats)
mergedStats.start()
}
/**
* Tracks data running through a given Duplex Iterable `stream`. If
* the `peerId` is not provided, a placeholder string will be created and
* returned. This allows lazy tracking of a peer when the peer is not yet known.
* When the `PeerId` is known, `Metrics.updatePlaceholder` should be called
* with the placeholder string returned from here, and the known `PeerId`.
*
* @param {Object} options
* @param {{ sink: function(*), source: function() }} options.stream A duplex iterable stream
* @param {PeerId} [options.peerId] The id of the remote peer that's connected
* @param {string} [options.protocol] The protocol the stream is running
* @returns {string} The peerId string or placeholder string
*/
trackStream ({ stream, remotePeer, protocol }) {
const metrics = this
const _source = stream.source
stream.source = tap(chunk => metrics._onMessage({
remotePeer,
protocol,
direction: 'in',
dataLength: chunk.length
}))(_source)
const _sink = stream.sink
stream.sink = source => {
pipe(
source,
tap(chunk => metrics._onMessage({
remotePeer,
protocol,
direction: 'out',
dataLength: chunk.length
})),
_sink
)
}
return stream
}
/**
* Merges `other` into `target`. `target` will be modified
* and returned.
* @param {Stats} target
* @param {Stats} other
* @returns {Stats}
*/
static mergeStats (target, other) {
target.stop()
other.stop()
// Merge queues
target._queue = [...target._queue, ...other._queue]
// TODO: how to merge moving averages?
return target
}
}
module.exports = Metrics

View File

@ -1,7 +1,8 @@
'use strict'
const tryEach = require('async/tryEach')
const errCode = require('err-code')
const pAny = require('p-any')
const promisify = require('promisify-es6')
module.exports = (node) => {
const routers = node._modules.peerRouting || []
@ -16,25 +17,43 @@ module.exports = (node) => {
* Iterates over all peer routers in series to find the given peer.
*
* @param {String} id The id of the peer to find
* @param {object} [options]
* @param {number} [options.timeout] How long the query should run
* @returns {Promise<PeerInfo>}
* @param {object} options
* @param {number} options.maxTimeout How long the query should run
* @param {function(Error, Result<Array>)} callback
* @returns {void}
*/
findPeer: async (id, options) => { // eslint-disable-line require-await
if (!routers.length) {
throw errCode(new Error('No peer routers available'), 'NO_ROUTERS_AVAILABLE')
findPeer: promisify((id, options, callback) => {
if (typeof options === 'function') {
callback = options
options = {}
}
return pAny(routers.map(async (router) => {
const result = await router.findPeer(id, options)
if (!routers.length) {
callback(errCode(new Error('No peer routers available'), 'NO_ROUTERS_AVAILABLE'))
}
// If we don't have a result, we need to provide an error to keep trying
if (!result || Object.keys(result).length === 0) {
throw errCode(new Error('not found'), 'NOT_FOUND')
const tasks = routers.map((router) => {
return (cb) => router.findPeer(id, options, (err, result) => {
if (err) {
return cb(err)
}
// If we don't have a result, we need to provide an error to keep trying
if (!result || Object.keys(result).length === 0) {
return cb(errCode(new Error('not found'), 'NOT_FOUND'), null)
}
cb(null, result)
})
})
tryEach(tasks, (err, results) => {
if (err) {
return callback(err)
}
return result
}))
}
results = results || []
callback(null, results)
})
})
}
}

View File

@ -7,7 +7,6 @@ log.error = debug('libp2p:peer-store:error')
const { EventEmitter } = require('events')
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
/**
@ -36,35 +35,26 @@ class PeerStore extends EventEmitter {
/**
* Stores the peerInfo of a new peer.
* If already exist, its info is updated. If `silent` is set to
* true, no 'peer' event will be emitted. This can be useful if you
* are already in the process of dialing the peer. The peer is technically
* known, but may not have been added to the PeerStore yet.
* If already exist, its info is updated.
* @param {PeerInfo} peerInfo
* @param {object} [options]
* @param {boolean} [options.silent] (Default=false)
* @return {PeerInfo}
*/
put (peerInfo, options = { silent: false }) {
put (peerInfo) {
assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
let peer
// Already know the peer?
if (this.has(peerInfo.id)) {
peer = this.update(peerInfo)
if (this.peers.has(peerInfo.id.toB58String())) {
this.update(peerInfo)
} else {
peer = this.add(peerInfo)
this.add(peerInfo)
// Emit the peer if silent = false
!options.silent && this.emit('peer', peerInfo)
// Emit the new peer found
this.emit('peer', peerInfo)
}
return peer
}
/**
* Add a new peer to the store.
* @param {PeerInfo} peerInfo
* @return {PeerInfo}
*/
add (peerInfo) {
assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
@ -96,13 +86,11 @@ class PeerStore extends EventEmitter {
})
this.peers.set(peerInfo.id.toB58String(), peerProxy)
return peerProxy
}
/**
* Updates an already known peer.
* @param {PeerInfo} peerInfo
* @return {PeerInfo}
*/
update (peerInfo) {
assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
@ -123,12 +111,15 @@ class PeerStore extends EventEmitter {
if (multiaddrsIntersection.length !== peerInfo.multiaddrs.size ||
multiaddrsIntersection.length !== recorded.multiaddrs.size) {
// recorded.multiaddrs = peerInfo.multiaddrs
recorded.multiaddrs.clear()
for (const ma of peerInfo.multiaddrs.toArray()) {
recorded.multiaddrs.add(ma)
}
this.emit('change:multiaddrs', {
peerInfo: recorded,
peerInfo: peerInfo,
multiaddrs: recorded.multiaddrs.toArray()
})
}
@ -141,12 +132,14 @@ class PeerStore extends EventEmitter {
if (protocolsIntersection.size !== peerInfo.protocols.size ||
protocolsIntersection.size !== recorded.protocols.size) {
recorded.protocols.clear()
for (const protocol of peerInfo.protocols) {
recorded.protocols.add(protocol)
}
this.emit('change:protocols', {
peerInfo: recorded,
peerInfo: peerInfo,
protocols: Array.from(recorded.protocols)
})
}
@ -155,49 +148,29 @@ class PeerStore extends EventEmitter {
if (!recorded.id.pubKey && peerInfo.id.pubKey) {
recorded.id.pubKey = peerInfo.id.pubKey
}
return recorded
}
/**
* Get the info to the given id.
* @param {PeerId|string} peerId b58str id
* @param {string} peerId b58str id
* @returns {PeerInfo}
*/
get (peerId) {
// TODO: deprecate this and just accept `PeerId` instances
if (PeerId.isPeerId(peerId)) {
peerId = peerId.toB58String()
const peerInfo = this.peers.get(peerId)
if (peerInfo) {
return peerInfo
}
return this.peers.get(peerId)
}
/**
* Has the info to the given id.
* @param {PeerId|string} peerId b58str id
* @returns {boolean}
*/
has (peerId) {
// TODO: deprecate this and just accept `PeerId` instances
if (PeerId.isPeerId(peerId)) {
peerId = peerId.toB58String()
}
return this.peers.has(peerId)
return undefined
}
/**
* Removes the Peer with the matching `peerId` from the PeerStore
* @param {PeerId|string} peerId b58str id
* @param {string} peerId b58str id
* @returns {boolean} true if found and removed
*/
remove (peerId) {
// TODO: deprecate this and just accept `PeerId` instances
if (PeerId.isPeerId(peerId)) {
peerId = peerId.toB58String()
}
return this.peers.delete(peerId)
}
@ -211,25 +184,6 @@ class PeerStore extends EventEmitter {
this.remove(peerInfo.id.toB58String())
this.add(peerInfo)
// This should be cleaned up in PeerStore v2
this.emit('change:multiaddrs', {
peerInfo,
multiaddrs: peerInfo.multiaddrs.toArray()
})
this.emit('change:protocols', {
peerInfo,
protocols: Array.from(peerInfo.protocols)
})
}
/**
* Returns the known multiaddrs for a given `PeerInfo`
* @param {PeerInfo} peer
* @returns {Array<Multiaddr>}
*/
multiaddrsForPeer (peer) {
return this.put(peer, true).multiaddrs.toArray()
}
}

View File

@ -8,11 +8,16 @@ libp2p-ping JavaScript Implementation
## Usage
```javascript
var Ping = require('libp2p/src/ping')
var Ping = require('libp2p-ping')
Ping.mount(libp2p) // Enable this peer to echo Ping requests
Ping.mount(swarm) // Enable this peer to echo Ping requests
const latency = await Ping(libp2p, peerDst)
var p = new Ping(swarm, peerDst) // Ping peerDst, peerDst must be a peer-info object
Ping.unmount(libp2p)
p.on('ping', function (time) {
console.log(time + 'ms')
p.stop() // stop sending pings
})
p.start()
```

50
src/ping/handler.js Normal file
View File

@ -0,0 +1,50 @@
'use strict'
const pull = require('pull-stream/pull')
const handshake = require('pull-handshake')
const constants = require('./constants')
const PROTOCOL = constants.PROTOCOL
const PING_LENGTH = constants.PING_LENGTH
const debug = require('debug')
const log = debug('libp2p-ping')
log.error = debug('libp2p-ping:error')
function mount (swarm) {
swarm.handle(PROTOCOL, (protocol, conn) => {
const stream = handshake({ timeout: 0 })
const shake = stream.handshake
// receive and echo back
function next () {
shake.read(PING_LENGTH, (err, buf) => {
if (err === true) {
// stream closed
return
}
if (err) {
return log.error(err)
}
shake.write(buf)
return next()
})
}
pull(
conn,
stream,
conn
)
next()
})
}
function unmount (swarm) {
swarm.unhandle(PROTOCOL)
}
exports = module.exports
exports.mount = mount
exports.unmount = unmount

View File

@ -1,62 +1,7 @@
'use strict'
const debug = require('debug')
const log = debug('libp2p-ping')
log.error = debug('libp2p-ping:error')
const errCode = require('err-code')
const handler = require('./handler')
const crypto = require('libp2p-crypto')
const pipe = require('it-pipe')
const { toBuffer } = require('it-buffer')
const { collect } = require('streaming-iterables')
const { PROTOCOL, PING_LENGTH } = require('./constants')
/**
* Ping a given peer and wait for its response, getting the operation latency.
* @param {Libp2p} node
* @param {PeerInfo} peer
* @returns {Promise<Number>}
*/
async function ping (node, peer) {
log('dialing %s to %s', PROTOCOL, peer.id.toB58String())
const { stream } = await node.dialProtocol(peer, PROTOCOL)
const start = new Date()
const data = crypto.randomBytes(PING_LENGTH)
const [result] = await pipe(
[data],
stream,
toBuffer,
collect
)
const end = Date.now()
if (!data.equals(result)) {
throw errCode(new Error('Received wrong ping ack'), 'ERR_WRONG_PING_ACK')
}
return end - start
}
/**
* Subscribe ping protocol handler.
* @param {Libp2p} node
*/
function mount (node) {
node.handle(PROTOCOL, ({ stream }) => pipe(stream, stream))
}
/**
* Unsubscribe ping protocol handler.
* @param {Libp2p} node
*/
function unmount (node) {
node.unhandle(PROTOCOL)
}
exports = module.exports = ping
exports.mount = mount
exports.unmount = unmount
exports = module.exports = require('./ping')
exports.mount = handler.mount
exports.unmount = handler.unmount

83
src/ping/ping.js Normal file
View File

@ -0,0 +1,83 @@
'use strict'
const EventEmitter = require('events').EventEmitter
const pull = require('pull-stream/pull')
const empty = require('pull-stream/sources/empty')
const handshake = require('pull-handshake')
const constants = require('./constants')
const util = require('./util')
const rnd = util.rnd
const debug = require('debug')
const log = debug('libp2p-ping')
log.error = debug('libp2p-ping:error')
const PROTOCOL = constants.PROTOCOL
const PING_LENGTH = constants.PING_LENGTH
class Ping extends EventEmitter {
constructor (swarm, peer) {
super()
this._stopped = false
this.peer = peer
this.swarm = swarm
}
start () {
log('dialing %s to %s', PROTOCOL, this.peer.id.toB58String())
this.swarm.dial(this.peer, PROTOCOL, (err, conn) => {
if (err) {
return this.emit('error', err)
}
const stream = handshake({ timeout: 0 })
this.shake = stream.handshake
pull(
stream,
conn,
stream
)
// write and wait to see ping back
const self = this
function next () {
const start = new Date()
const buf = rnd(PING_LENGTH)
self.shake.write(buf)
self.shake.read(PING_LENGTH, (err, bufBack) => {
const end = new Date()
if (err || !buf.equals(bufBack)) {
const err = new Error('Received wrong ping ack')
return self.emit('error', err)
}
self.emit('ping', end - start)
if (self._stopped) {
return
}
next()
})
}
next()
})
}
stop () {
if (this._stopped || !this.shake) {
return
}
this._stopped = true
pull(
empty(),
this.shake.rest()
)
}
}
module.exports = Ping

View File

@ -7,15 +7,12 @@ js-libp2p-pnet
## Table of Contents
- [js-libp2p-pnet](#js-libp2p-pnet)
- [Table of Contents](#table-of-contents)
- [Usage](#usage)
- [Examples](#examples)
- [Private Shared Keys](#private-shared-keys)
- [PSK Generation](#psk-generation)
- [From libp2p-pnet](#from-libp2p-pnet)
- [From a module using libp2p](#from-a-module-using-libp2p)
- [Programmatically](#programmatically)
- [Usage](#usage)
- [Examples](#examples)
- [Private Shared Keys (PSK)](#private-shared-keys)
- [PSK Generation](#psk-generation)
- [Contribute](#contribute)
- [License](#license)
## Usage
@ -26,7 +23,7 @@ const privateConnection = protector.protect(myPublicConnection, (err) => { })
```
### Examples
[Private Networks with IPFS](../../examples/pnet-ipfs)
[Private Networks with IPFS](./examples/pnet-ipfs)
### Private Shared Keys
@ -45,25 +42,25 @@ use one of the methods below to generate your key.
#### From libp2p-pnet
If you have libp2p locally, you can run the following from the projects root.
If you have libp2p-pnet locally, you can run the following from the projects root.
```sh
node ./src/pnet/key-generator.js > swarm.key
node ./key-generator.js > swarm.key
```
#### From a module using libp2p
If you have a module locally that depends on libp2p, you can run the following from
If you have a module locally that depends on libp2p-pnet, you can run the following from
that project, assuming the node_modules are installed.
```sh
node -e "require('libp2p/src/pnet').generate(process.stdout)" > swarm.key
node -e "require('libp2p-pnet').generate(process.stdout)" > swarm.key
```
#### Programmatically
```js
const writeKey = require('libp2p/src/pnet').generate
const writeKey = require('libp2p-pnet').generate
const swarmKey = Buffer.alloc(95)
writeKey(swarmKey)
fs.writeFileSync('swarm.key', swarmKey)

View File

@ -83,12 +83,12 @@ module.exports = (node, Pubsub, config) => {
* @param {string} topic
* @returns {Array<string>}
*/
getSubscribers: (topic) => {
getPeersSubscribed: (topic) => {
if (!node.isStarted() && !pubsub.started) {
throw errCode(new Error(messages.NOT_STARTED_YET), codes.PUBSUB_NOT_STARTED)
}
return pubsub.getSubscribers(topic)
return pubsub.getPeersSubscribed(topic)
},
setMaxListeners (n) {

View File

@ -46,23 +46,6 @@ class Registrar {
this._handle = handle
}
/**
* Cleans up the registrar
* @async
*/
async close () {
// Close all connections we're tracking
const tasks = []
for (const connectionList of this.connections.values()) {
for (const connection of connectionList) {
tasks.push(connection.close())
}
}
await tasks
this.connections.clear()
}
/**
* Add a new connected peer to the record
* TODO: this should live in the ConnectionManager
@ -74,7 +57,7 @@ class Registrar {
assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
assert(Connection.isConnection(conn), 'conn must be an instance of interface-connection')
const id = peerInfo.id.toString()
const id = peerInfo.id.toB58String()
const storedConn = this.connections.get(id)
if (storedConn) {
@ -95,18 +78,17 @@ class Registrar {
onDisconnect (peerInfo, connection, error) {
assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
const id = peerInfo.id.toString()
const id = peerInfo.id.toB58String()
let storedConn = this.connections.get(id)
if (storedConn && storedConn.length > 1) {
storedConn = storedConn.filter((conn) => conn.id === connection.id)
this.connections.set(id, storedConn)
} else if (storedConn) {
for (const [, topology] of this.topologies) {
topology.disconnect(peerInfo, error)
}
this.connections.delete(peerInfo.id.toString())
this.connections.delete(peerInfo.id.toB58String())
}
}
@ -118,12 +100,8 @@ class Registrar {
getConnection (peerInfo) {
assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
const connections = this.connections.get(peerInfo.id.toString())
// Return the first, open connection
if (connections) {
return connections.find(connection => connection.stat.status === 'open')
}
return null
// TODO: what should we return
return this.connections.get(peerInfo.id.toB58String())[0]
}
/**

423
src/switch/README.md Normal file
View File

@ -0,0 +1,423 @@
libp2p-switch JavaScript implementation
======================================
> libp2p-switch is a dialer machine, it leverages the multiple libp2p transports, stream muxers, crypto channels and other connection upgrades to dial to peers in the libp2p network. It also supports Protocol Multiplexing through a multicodec and multistream-select handshake.
**Note**: git history prior to merging into js-libp2p can be found in the original repository, https://github.com/libp2p/js-libp2p-switch.
## Table of Contents
- [Install](#install)
- [Usage](#usage)
- [Create a libp2p switch](#create-a-libp2p-switch)
- [API](#api)
- [`switch.connection`](#switchconnection)
- [`switch.dial(peer, protocol, callback)`](#switchdialpeer-protocol-callback)
- [`switch.dialFSM(peer, protocol, callback)`](#switchdialfsmpeer-protocol-callback)
- [`switch.handle(protocol, handlerFunc, matchFunc)`](#switchhandleprotocol-handlerfunc-matchfunc)
- [`switch.hangUp(peer, callback)`](#switchhanguppeer-callback)
- [`switch.start(callback)`](#switchstartcallback)
- [`switch.stop(callback)`](#switchstopcallback)
- [`switch.stats`](#stats-api)
- [`switch.unhandle(protocol)`](#switchunhandleprotocol)
- [Internal Transports API](#internal-transports-api)
- [Design Notes](#design-notes)
- [Multitransport](#multitransport)
- [Connection upgrades](#connection-upgrades)
- [Identify](#identify)
- [Notes](#notes)
- [Contribute](#contribute)
- [License](#license)
## Install
```bash
> npm install libp2p-switch --save
```
## Usage
### Create a libp2p Switch
```JavaScript
const switch = require('libp2p-switch')
const sw = new switch(peerInfo , peerBook [, options])
```
If defined, `options` should be an object with the following keys and respective values:
- `denyTTL`: - number of ms a peer should not be dialable to after it errors. Each successive deny will increase the TTL from the base value. Defaults to 5 minutes
- `denyAttempts`: - number of times a peer can be denied before they are permanently denied. Defaults to 5.
- `maxParallelDials`: - number of concurrent dials the switch should allow. Defaults to `100`
- `maxColdCalls`: - number of queued cold calls that are allowed. Defaults to `50`
- `dialTimeout`: - number of ms a dial to a peer should be allowed to run. Defaults to `30000` (30 seconds)
- `stats`: an object with the following keys and respective values:
- `maxOldPeersRetention`: maximum old peers retention. For when peers disconnect and keeping the stats around in case they reconnect. Defaults to `100`.
- `computeThrottleMaxQueueSize`: maximum queue size to perform stats computation throttling. Defaults to `1000`.
- `computeThrottleTimeout`: Throttle timeout, in miliseconds. Defaults to `2000`,
- `movingAverageIntervals`: Array containin the intervals, in miliseconds, for which moving averages are calculated. Defaults to:
```js
[
60 * 1000, // 1 minute
5 * 60 * 1000, // 5 minutes
15 * 60 * 1000 // 15 minutes
]
```
### Private Networks
libp2p-switch supports private networking. In order to enabled private networks, the `switch.protector` must be
set and must contain a `protect` method. You can see an example of this in the [private network
tests]([./test/pnet.node.js]).
## API
- peerInfo is a [PeerInfo](https://github.com/libp2p/js-peer-info) object that has the peer information.
- peerBook is a [PeerBook](https://github.com/libp2p/js-peer-book) object that stores all the known peers.
### `switch.connection`
##### `switch.connection.addUpgrade()`
A connection upgrade must be able to receive and return something that implements the [interface-connection](https://github.com/libp2p/js-interfaces/tree/master/src/connection) specification.
> **WIP**
##### `switch.connection.addStreamMuxer(muxer)`
Upgrading a connection to use a stream muxer is still considered an upgrade, but a special case since once this connection is applied, the returned obj will implement the [interface-stream-muxer](https://github.com/libp2p/interface-stream-muxer) spec.
- `muxer`
##### `switch.connection.reuse()`
Enable the identify protocol.
##### `switch.connection.crypto([tag, encrypt])`
Enable a specified crypto protocol. By default no encryption is used, aka `plaintext`. If called with no arguments it resets to use `plaintext`.
You can use for example [libp2p-secio](https://github.com/libp2p/js-libp2p-secio) like this
```js
const secio = require('libp2p-secio')
switch.connection.crypto(secio.tag, secio.encrypt)
```
##### `switch.connection.enableCircuitRelay(options, callback)`
Enable circuit relaying.
- `options`
- enabled - activates relay dialing and listening functionality
- hop - an object with two properties
- enabled - enables circuit relaying
- active - is it an active or passive relay (default false)
- `callback`
### `switch.dial(peer, protocol, callback)`
dial uses the best transport (whatever works first, in the future we can have some criteria), and jump starts the connection until the point where we have to negotiate the protocol. If a muxer is available, then drop the muxer onto that connection. Good to warm up connections or to check for connectivity. If we have already a muxer for that peerInfo, then do nothing.
- `peer`: can be an instance of [PeerInfo][], [PeerId][] or [multiaddr][]
- `protocol`
- `callback`
### `switch.dialFSM(peer, protocol, callback)`
works like dial, but calls back with a [Connection State Machine](#connection-state-machine)
- `peer`: can be an instance of [PeerInfo][], [PeerId][] or [multiaddr][]
- `protocol`: String that defines the protocol (e.g '/ipfs/bitswap/1.1.0') to be used
- `callback`: Function with signature `function (err, connFSM) {}` where `connFSM` is a [Connection State Machine](#connection-state-machine)
#### Connection State Machine
Connection state machines emit a number of events that can be used to determine the current state of the connection
and to received the underlying connection that can be used to transfer data.
### `switch.dialer.connect(peer, options, callback)`
a low priority dial to the provided peer. Calls to `dial` and `dialFSM` will take priority. This should be used when an application only wishes to establish connections to new peers, such as during peer discovery when there is a low peer count. Currently, anything greater than the HIGH_PRIORITY (10) will be placed into the cold call queue, and anything less than or equal to the HIGH_PRIORITY will be added to the normal queue.
- `peer`: can be an instance of [PeerInfo][], [PeerId][] or [multiaddr][]
- `options`: Optional
- `options.priority`: Number of the priority of the dial, defaults to 20.
- `options.useFSM`: Boolean of whether or not to callback with a [Connection State Machine](#connection-state-machine)
- `callback`: Function with signature `function (err, connFSM) {}` where `connFSM` is a [Connection State Machine](#connection-state-machine)
##### Events
- `error`: emitted whenever a fatal error occurs with the connection; the error will be emitted.
- `error:upgrade_failed`: emitted whenever the connection fails to upgrade with a muxer, this is not fatal.
- `error:connection_attempt_failed`: emitted whenever a dial attempt fails for a given transport. An array of errors is emitted.
- `connection`: emitted whenever a useable connection has been established; the underlying [Connection](https://github.com/libp2p/js-interfaces/tree/master/src/connection) will be emitted.
- `close`: emitted when the connection has closed.
### `switch.handle(protocol, handlerFunc, matchFunc)`
Handle a new protocol.
- `protocol`
- `handlerFunc` - function called when we receive a dial on `protocol. Signature must be `function (protocol, conn) {}`
- `matchFunc` - matchFunc for multistream-select
### `switch.hangUp(peer, callback)`
Hang up the muxed connection we have with the peer.
- `peer`: can be an instance of [PeerInfo][], [PeerId][] or [multiaddr][]
- `callback`
### `switch.on('error', (err) => {})`
Emitted when the switch encounters an error.
- `err`: instance of [Error][]
### `switch.on('peer-mux-established', (peer) => {})`
- `peer`: is instance of [PeerInfo][] that has info of the peer we have just established a muxed connection with.
### `switch.on('peer-mux-closed', (peer) => {})`
- `peer`: is instance of [PeerInfo][] that has info of the peer we have just closed a muxed connection with.
### `switch.on('connection:start', (peer) => {})`
This will be triggered anytime a new connection is created.
- `peer`: is instance of [PeerInfo][] that has info of the peer we have just started a connection with.
### `switch.on('connection:end', (peer) => {})`
This will be triggered anytime an existing connection, regardless of state, is removed from the switch's internal connection tracking.
- `peer`: is instance of [PeerInfo][] that has info of the peer we have just closed a connection with.
### `switch.on('start', () => {})`
Emitted when the switch has successfully started.
### `switch.on('stop', () => {})`
Emitted when the switch has successfully stopped.
### `switch.start(callback)`
Start listening on all added transports that are available on the current `peerInfo`.
### `switch.stop(callback)`
Close all the listeners and muxers.
- `callback`
### Stats API
##### `switch.stats.emit('update')`
Every time any stat value changes, this object emits an `update` event.
#### Global stats
##### `switch.stats.global.snapshot`
Should return a stats snapshot, which is an object containing the following keys and respective values:
- dataSent: amount of bytes sent, [Big](https://github.com/MikeMcl/big.js#readme) number
- dataReceived: amount of bytes received, [Big](https://github.com/MikeMcl/big.js#readme) number
##### `switch.stats.global.movingAverages`
Returns an object containing the following keys:
- dataSent
- dataReceived
Each one of them contains an object that has a key for each interval (`60000`, `300000` and `900000` miliseconds).
Each one of these values is [an exponential moving-average instance](https://github.com/pgte/moving-average#readme).
#### Per-transport stats
##### `switch.stats.transports()`
Returns an array containing the tags (string) for each observed transport.
##### `switch.stats.forTransport(transportTag).snapshot`
Should return a stats snapshot, which is an object containing the following keys and respective values:
- dataSent: amount of bytes sent, [Big](https://github.com/MikeMcl/big.js#readme) number
- dataReceived: amount of bytes received, [Big](https://github.com/MikeMcl/big.js#readme) number
##### `switch.stats.forTransport(transportTag).movingAverages`
Returns an object containing the following keys:
dataSent
dataReceived
Each one of them contains an object that has a key for each interval (`60000`, `300000` and `900000` miliseconds).
Each one of these values is [an exponential moving-average instance](https://github.com/pgte/moving-average#readme).
#### Per-protocol stats
##### `switch.stats.protocols()`
Returns an array containing the tags (string) for each observed protocol.
##### `switch.stats.forProtocol(protocolTag).snapshot`
Should return a stats snapshot, which is an object containing the following keys and respective values:
- dataSent: amount of bytes sent, [Big](https://github.com/MikeMcl/big.js#readme) number
- dataReceived: amount of bytes received, [Big](https://github.com/MikeMcl/big.js#readme) number
##### `switch.stats.forProtocol(protocolTag).movingAverages`
Returns an object containing the following keys:
- dataSent
- dataReceived
Each one of them contains an object that has a key for each interval (`60000`, `300000` and `900000` miliseconds).
Each one of these values is [an exponential moving-average instance](https://github.com/pgte/moving-average#readme).
#### Per-peer stats
##### `switch.stats.peers()`
Returns an array containing the peerIDs (B58-encoded string) for each observed peer.
##### `switch.stats.forPeer(peerId:String).snapshot`
Should return a stats snapshot, which is an object containing the following keys and respective values:
- dataSent: amount of bytes sent, [Big](https://github.com/MikeMcl/big.js#readme) number
- dataReceived: amount of bytes received, [Big](https://github.com/MikeMcl/big.js#readme) number
##### `switch.stats.forPeer(peerId:String).movingAverages`
Returns an object containing the following keys:
- dataSent
- dataReceived
Each one of them contains an object that has a key for each interval (`60000`, `300000` and `900000` miliseconds).
Each one of these values is [an exponential moving-average instance](https://github.com/pgte/moving-average#readme).
#### Stats update interval
Stats are not updated in real-time. Instead, measurements are buffered and stats are updated at an interval. The maximum interval can be defined through the `Switch` constructor option `stats.computeThrottleTimeout`, defined in miliseconds.
### `switch.unhandle(protocol)`
Unhandle a protocol.
- `protocol`
### Internal Transports API
##### `switch.transport.add(key, transport, options)`
libp2p-switch expects transports that implement [interface-transport](https://github.com/libp2p/interface-transport). For example [libp2p-tcp](https://github.com/libp2p/js-libp2p-tcp).
- `key` - the transport identifier.
- `transport` -
- `options` -
##### `switch.transport.dial(key, multiaddrs, callback)`
Dial to a peer on a specific transport.
- `key`
- `multiaddrs`
- `callback`
##### `switch.transport.listen(key, options, handler, callback)`
Set a transport to start listening mode.
- `key`
- `options`
- `handler`
- `callback`
##### `switch.transport.close(key, callback)`
Close the listeners of a given transport.
- `key`
- `callback`
## Design Notes
### Multitransport
libp2p is designed to support multiple transports at the same time. While peers are identified by their ID (which are generated from their public keys), the addresses of each pair may vary, depending the device where they are being run or the network in which they are accessible through.
In order for a transport to be supported, it has to follow the [interface-transport](https://github.com/libp2p/interface-transport) spec.
### Connection upgrades
Each connection in libp2p follows the [interface-connection](https://github.com/libp2p/js-interfaces/tree/master/src/connection) spec. This design decision enables libp2p to have upgradable transports.
We think of `upgrade` as a very important notion when we are talking about connections, we can see mechanisms like: stream multiplexing, congestion control, encrypted channels, multipath, simulcast, etc, as `upgrades` to a connection. A connection can be a simple and with no guarantees, drop a packet on the network with a destination thing, a transport in the other hand can be a connection and or a set of different upgrades that are mounted on top of each other, giving extra functionality to that connection and therefore `upgrading` it.
Types of upgrades to a connection:
- encrypted channel (with TLS for e.g)
- congestion flow (some transports don't have it by default)
- multipath (open several connections and abstract it as a single connection)
- simulcast (still really thinking this one through, it might be interesting to send a packet through different connections under some hard network circumstances)
- stream-muxer - this a special case, because once we upgrade a connection to a stream-muxer, we can open more streams (multiplex them) on a single stream, also enabling us to reuse the underlying dialed transport
We also want to enable flexibility when it comes to upgrading a connection, for example, we might that all dialed transports pass through the encrypted channel upgrade, but not the congestion flow, specially when a transport might have already some underlying properties (UDP vs TCP vs WebRTC vs every other transport protocol)
### Identify
Identify is a protocol that switchs mounts on top of itself, to identify the connections between any two peers. E.g:
- a) peer A dials a conn to peer B
- b) that conn gets upgraded to a stream multiplexer that both peers agree
- c) peer B executes de identify protocol
- d) peer B now can open streams to peer A, knowing which is the
identity of peer A
In addition to this, we also share the "observed addresses" by the other peer, which is extremely useful information for different kinds of network topologies.
### Notes
To avoid the confusion between connection, stream, transport, and other names that represent an abstraction of data flow between two points, we use terms as:
- connection - something that implements the transversal expectations of a stream between two peers, including the benefits of using a stream plus having a way to do half duplex, full duplex
- transport - something that as a dial/listen interface and return objs that implement a connection interface
### This module uses `pull-streams`
We expose a streaming interface based on `pull-streams`, rather then on the Node.js core streams implementation (aka Node.js streams). `pull-streams` offers us a better mechanism for error handling and flow control guarantees. If you would like to know more about why we did this, see the discussion at this [issue](https://github.com/ipfs/js-ipfs/issues/362).
You can learn more about pull-streams at:
- [The history of Node.js streams, nodebp April 2014](https://www.youtube.com/watch?v=g5ewQEuXjsQ)
- [The history of streams, 2016](http://dominictarr.com/post/145135293917/history-of-streams)
- [pull-streams, the simple streaming primitive](http://dominictarr.com/post/149248845122/pull-streams-pull-streams-are-a-very-simple)
- [pull-streams documentation](https://pull-stream.github.io/)
#### Converting `pull-streams` to Node.js Streams
If you are a Node.js streams user, you can convert a pull-stream to a Node.js stream using the module [`pull-stream-to-stream`](https://github.com/pull-stream/pull-stream-to-stream), giving you an instance of a Node.js stream that is linked to the pull-stream. For example:
```js
const pullToStream = require('pull-stream-to-stream')
const nodeStreamInstance = pullToStream(pullStreamInstance)
// nodeStreamInstance is an instance of a Node.js Stream
```
To learn more about this utility, visit https://pull-stream.github.io/#pull-stream-to-stream.

View File

@ -0,0 +1,126 @@
'use strict'
const EventEmitter = require('events').EventEmitter
const debug = require('debug')
const withIs = require('class-is')
class BaseConnection extends EventEmitter {
constructor ({ _switch, name }) {
super()
this.switch = _switch
this.ourPeerInfo = this.switch._peerInfo
this.log = debug(`libp2p:conn:${name}`)
this.log.error = debug(`libp2p:conn:${name}:error`)
}
/**
* Puts the state into its disconnecting flow
*
* @param {Error} err Will be emitted if provided
* @returns {void}
*/
close (err) {
if (this._state._state === 'DISCONNECTING') return
this.log('closing connection to %s', this.theirB58Id)
if (err && this._events.error) {
this.emit('error', err)
}
this._state('disconnect')
}
emit (eventName, ...args) {
if (eventName === 'error' && !this._events.error) {
this.log.error(...args)
} else {
super.emit(eventName, ...args)
}
}
/**
* Gets the current state of the connection
*
* @returns {string} The current state of the connection
*/
getState () {
return this._state._state
}
/**
* Puts the state into encrypting mode
*
* @returns {void}
*/
encrypt () {
this._state('encrypt')
}
/**
* Puts the state into privatizing mode
*
* @returns {void}
*/
protect () {
this._state('privatize')
}
/**
* Puts the state into muxing mode
*
* @returns {void}
*/
upgrade () {
this._state('upgrade')
}
/**
* Event handler for disconnected.
*
* @fires BaseConnection#close
* @returns {void}
*/
_onDisconnected () {
this.switch.connection.remove(this)
this.log('disconnected from %s', this.theirB58Id)
this.emit('close')
this.removeAllListeners()
}
/**
* Event handler for privatized
*
* @fires BaseConnection#private
* @returns {void}
*/
_onPrivatized () {
this.emit('private', this.conn)
}
/**
* Wraps this.conn with the Switch.protector for private connections
*
* @private
* @fires ConnectionFSM#error
* @returns {void}
*/
_onPrivatizing () {
if (!this.switch.protector) {
return this._state('done')
}
this.conn = this.switch.protector.protect(this.conn, (err) => {
if (err) {
return this.close(err)
}
this.log('successfully privatized conn to %s', this.theirB58Id)
this.conn.setPeerInfo(this.theirPeerInfo)
this._state('done')
})
}
}
module.exports = withIs(BaseConnection, {
className: 'BaseConnection',
symbolName: 'libp2p-switch/BaseConnection'
})

View File

@ -0,0 +1,47 @@
'use strict'
const debug = require('debug')
const IncomingConnection = require('./incoming')
const observeConn = require('../observe-connection')
function listener (_switch) {
const log = debug('libp2p:switch:listener')
/**
* Takes a transport key and returns a connection handler function
*
* @param {string} transportKey The key of the transport to handle connections for
* @param {function} handler A custom handler to use
* @returns {function(Connection)} A connection handler function
*/
return function (transportKey, handler) {
/**
* Takes a base connection and manages listening behavior
*
* @param {Connection} conn The connection to manage
* @returns {void}
*/
return function (conn) {
log('received incoming connection for transport %s', transportKey)
conn.getPeerInfo((_, peerInfo) => {
// Add a transport level observer, if needed
const connection = transportKey ? observeConn(transportKey, null, conn, _switch.observer) : conn
const connFSM = new IncomingConnection({ connection, _switch, transportKey, peerInfo })
connFSM.once('error', (err) => log(err))
connFSM.once('private', (_conn) => {
// Use the custom handler, if it was provided
if (handler) {
return handler(_conn)
}
connFSM.encrypt()
})
connFSM.once('encrypted', () => connFSM.upgrade())
connFSM.protect()
})
}
}
}
module.exports = listener

View File

@ -0,0 +1,115 @@
'use strict'
const FSM = require('fsm-event')
const multistream = require('multistream-select')
const withIs = require('class-is')
const BaseConnection = require('./base')
class IncomingConnectionFSM extends BaseConnection {
constructor ({ connection, _switch, transportKey, peerInfo }) {
super({
_switch,
name: `inc:${_switch._peerInfo.id.toB58String().slice(0, 8)}`
})
this.conn = connection
this.theirPeerInfo = peerInfo || null
this.theirB58Id = this.theirPeerInfo ? this.theirPeerInfo.id.toB58String() : null
this.ourPeerInfo = this.switch._peerInfo
this.transportKey = transportKey
this.protocolMuxer = this.switch.protocolMuxer(this.transportKey)
this.msListener = new multistream.Listener()
this._state = FSM('DIALED', {
DISCONNECTED: {
disconnect: 'DISCONNECTED'
},
DIALED: { // Base connection to peer established
privatize: 'PRIVATIZING',
encrypt: 'ENCRYPTING'
},
PRIVATIZING: { // Protecting the base connection
done: 'PRIVATIZED',
disconnect: 'DISCONNECTING'
},
PRIVATIZED: { // Base connection is protected
encrypt: 'ENCRYPTING'
},
ENCRYPTING: { // Encrypting the base connection
done: 'ENCRYPTED',
disconnect: 'DISCONNECTING'
},
ENCRYPTED: { // Upgrading could not happen, the connection is encrypted and waiting
upgrade: 'UPGRADING',
disconnect: 'DISCONNECTING'
},
UPGRADING: { // Attempting to upgrade the connection with muxers
done: 'MUXED'
},
MUXED: {
disconnect: 'DISCONNECTING'
},
DISCONNECTING: { // Shutting down the connection
done: 'DISCONNECTED'
}
})
this._state.on('DISCONNECTED', () => this._onDisconnected())
this._state.on('PRIVATIZING', () => this._onPrivatizing())
this._state.on('PRIVATIZED', () => this._onPrivatized())
this._state.on('ENCRYPTING', () => this._onEncrypting())
this._state.on('ENCRYPTED', () => {
this.log('successfully encrypted connection to %s', this.theirB58Id || 'unknown peer')
this.emit('encrypted', this.conn)
})
this._state.on('UPGRADING', () => this._onUpgrading())
this._state.on('MUXED', () => {
this.log('successfully muxed connection to %s', this.theirB58Id || 'unknown peer')
this.emit('muxed', this.conn)
})
this._state.on('DISCONNECTING', () => {
this._state('done')
})
}
/**
* Attempts to encrypt `this.conn` with the Switch's crypto.
*
* @private
* @fires IncomingConnectionFSM#error
* @returns {void}
*/
_onEncrypting () {
this.log('encrypting connection via %s', this.switch.crypto.tag)
this.msListener.addHandler(this.switch.crypto.tag, (protocol, _conn) => {
this.conn = this.switch.crypto.encrypt(this.ourPeerInfo.id, _conn, undefined, (err) => {
if (err) {
return this.close(err)
}
this.conn.getPeerInfo((_, peerInfo) => {
this.theirPeerInfo = peerInfo
this._state('done')
})
})
}, null)
// Start handling the connection
this.msListener.handle(this.conn, (err) => {
if (err) {
this.emit('crypto handshaking failed', err)
}
})
}
_onUpgrading () {
this.log('adding the protocol muxer to the connection')
this.protocolMuxer(this.conn, this.msListener)
this._state('done')
}
}
module.exports = withIs(IncomingConnectionFSM, {
className: 'IncomingConnectionFSM',
symbolName: 'libp2p-switch/IncomingConnectionFSM'
})

View File

@ -0,0 +1,498 @@
'use strict'
const FSM = require('fsm-event')
const Circuit = require('../../circuit')
const multistream = require('multistream-select')
const withIs = require('class-is')
const BaseConnection = require('./base')
const parallel = require('async/parallel')
const nextTick = require('async/nextTick')
const identify = require('../../identify')
const errCode = require('err-code')
const { msHandle, msSelect, identifyDialer } = require('../utils')
const observeConnection = require('../observe-connection')
const {
CONNECTION_FAILED,
DIAL_SELF,
INVALID_STATE_TRANSITION,
NO_TRANSPORTS_REGISTERED,
maybeUnexpectedEnd
} = require('../errors')
/**
* @typedef {Object} ConnectionOptions
* @property {Switch} _switch Our switch instance
* @property {PeerInfo} peerInfo The PeerInfo of the peer to dial
* @property {Muxer} muxer Optional - A muxed connection
* @property {Connection} conn Optional - The base connection
* @property {string} type Optional - identify the connection as incoming or outgoing. Defaults to out.
*/
/**
* ConnectionFSM handles the complex logic of managing a connection
* between peers. ConnectionFSM is internally composed of a state machine
* to help improve the usability and debuggability of connections. The
* state machine also helps to improve the ability to handle dial backoff,
* coalescing dials and dial locks.
*/
class ConnectionFSM extends BaseConnection {
/**
* @param {ConnectionOptions} connectionOptions
* @constructor
*/
constructor ({ _switch, peerInfo, muxer, conn, type = 'out' }) {
super({
_switch,
name: `${type}:${_switch._peerInfo.id.toB58String().slice(0, 8)}`
})
this.theirPeerInfo = peerInfo
this.theirB58Id = this.theirPeerInfo.id.toB58String()
this.conn = conn // The base connection
this.muxer = muxer // The upgraded/muxed connection
let startState = 'DISCONNECTED'
if (this.muxer) {
startState = 'MUXED'
}
this._state = FSM(startState, {
DISCONNECTED: { // No active connections exist for the peer
dial: 'DIALING',
disconnect: 'DISCONNECTED',
done: 'DISCONNECTED'
},
DIALING: { // Creating an initial connection
abort: 'ABORTED',
// emit events for different transport dials?
done: 'DIALED',
error: 'ERRORED',
disconnect: 'DISCONNECTING'
},
DIALED: { // Base connection to peer established
encrypt: 'ENCRYPTING',
privatize: 'PRIVATIZING'
},
PRIVATIZING: { // Protecting the base connection
done: 'PRIVATIZED',
abort: 'ABORTED',
disconnect: 'DISCONNECTING'
},
PRIVATIZED: { // Base connection is protected
encrypt: 'ENCRYPTING'
},
ENCRYPTING: { // Encrypting the base connection
done: 'ENCRYPTED',
error: 'ERRORED',
disconnect: 'DISCONNECTING'
},
ENCRYPTED: { // Upgrading could not happen, the connection is encrypted and waiting
upgrade: 'UPGRADING',
disconnect: 'DISCONNECTING'
},
UPGRADING: { // Attempting to upgrade the connection with muxers
stop: 'CONNECTED', // If we cannot mux, stop upgrading
done: 'MUXED',
error: 'ERRORED',
disconnect: 'DISCONNECTING'
},
MUXED: {
disconnect: 'DISCONNECTING'
},
CONNECTED: { // A non muxed connection is established
disconnect: 'DISCONNECTING'
},
DISCONNECTING: { // Shutting down the connection
done: 'DISCONNECTED',
disconnect: 'DISCONNECTING'
},
ABORTED: { }, // A severe event occurred
ERRORED: { // An error occurred, but future dials may be allowed
disconnect: 'DISCONNECTING' // There could be multiple options here, but this is a likely action
}
})
this._state.on('DISCONNECTED', () => this._onDisconnected())
this._state.on('DIALING', () => this._onDialing())
this._state.on('DIALED', () => this._onDialed())
this._state.on('PRIVATIZING', () => this._onPrivatizing())
this._state.on('PRIVATIZED', () => this._onPrivatized())
this._state.on('ENCRYPTING', () => this._onEncrypting())
this._state.on('ENCRYPTED', () => {
this.log('successfully encrypted connection to %s', this.theirB58Id)
this.emit('encrypted', this.conn)
})
this._state.on('UPGRADING', () => this._onUpgrading())
this._state.on('MUXED', () => {
this.log('successfully muxed connection to %s', this.theirB58Id)
delete this.switch.conns[this.theirB58Id]
this.emit('muxed', this.muxer)
})
this._state.on('CONNECTED', () => {
this.log('unmuxed connection opened to %s', this.theirB58Id)
this.emit('unmuxed', this.conn)
})
this._state.on('DISCONNECTING', () => this._onDisconnecting())
this._state.on('ABORTED', () => this._onAborted())
this._state.on('ERRORED', () => this._onErrored())
this._state.on('error', (err) => this._onStateError(err))
}
/**
* Puts the state into dialing mode
*
* @fires ConnectionFSM#Error May emit a DIAL_SELF error
* @returns {void}
*/
dial () {
if (this.theirB58Id === this.ourPeerInfo.id.toB58String()) {
return this.emit('error', DIAL_SELF())
} else if (this.getState() === 'DIALING') {
return this.log('attempted to dial while already dialing, ignoring')
}
this._state('dial')
}
/**
* Initiates a handshake for the given protocol
*
* @param {string} protocol The protocol to negotiate
* @param {function(Error, Connection)} callback
* @returns {void}
*/
shake (protocol, callback) {
// If there is no protocol set yet, don't perform the handshake
if (!protocol) {
return callback(null, null)
}
if (this.muxer && this.muxer.newStream) {
return this.muxer.newStream((err, stream) => {
if (err) {
return callback(err, null)
}
this.log('created new stream to %s', this.theirB58Id)
this._protocolHandshake(protocol, stream, callback)
})
}
this._protocolHandshake(protocol, this.conn, callback)
}
/**
* Puts the state into muxing mode
*
* @returns {void}
*/
upgrade () {
this._state('upgrade')
}
/**
* Event handler for dialing. Transitions state when successful.
*
* @private
* @fires ConnectionFSM#error
* @returns {void}
*/
_onDialing () {
this.log('dialing %s', this.theirB58Id)
if (!this.switch.hasTransports()) {
return this.close(NO_TRANSPORTS_REGISTERED())
}
const tKeys = this.switch.availableTransports(this.theirPeerInfo)
const circuitEnabled = Boolean(this.switch.transports[Circuit.tag])
if (circuitEnabled && !tKeys.includes(Circuit.tag)) {
tKeys.push(Circuit.tag)
}
const nextTransport = (key) => {
const transport = key
if (!transport) {
if (!circuitEnabled) {
return this.close(
CONNECTION_FAILED(`Circuit not enabled and all transports failed to dial peer ${this.theirB58Id}!`)
)
}
return this.close(
CONNECTION_FAILED(`No available transports to dial peer ${this.theirB58Id}!`)
)
}
if (transport === Circuit.tag) {
this.theirPeerInfo.multiaddrs.add(`/p2p-circuit/p2p/${this.theirB58Id}`)
}
this.log('dialing transport %s', transport)
this.switch.transport.dial(transport, this.theirPeerInfo, (errors, _conn) => {
if (errors) {
this.emit('error:connection_attempt_failed', errors)
this.log(errors)
return nextTransport(tKeys.shift())
}
this.conn = observeConnection(transport, null, _conn, this.switch.observer)
this._state('done')
})
}
nextTransport(tKeys.shift())
}
/**
* Once a connection has been successfully dialed, the connection
* will be privatized or encrypted depending on the presence of the
* Switch.protector.
*
* @returns {void}
*/
_onDialed () {
this.log('successfully dialed %s', this.theirB58Id)
this.emit('connected', this.conn)
}
/**
* Event handler for disconnecting. Handles any needed cleanup
*
* @returns {void}
*/
_onDisconnecting () {
this.log('disconnecting from %s', this.theirB58Id, Boolean(this.muxer))
delete this.switch.conns[this.theirB58Id]
const tasks = []
// Clean up stored connections
if (this.muxer) {
tasks.push((cb) => {
this.muxer.end(() => {
delete this.muxer
cb()
})
})
}
// If we have the base connection, abort it
// Ignore abort errors, since we're closing
if (this.conn) {
try {
this.conn.source.abort()
} catch (_) { }
delete this.conn
}
parallel(tasks, () => {
this._state('done')
})
}
/**
* Attempts to encrypt `this.conn` with the Switch's crypto.
*
* @private
* @fires ConnectionFSM#error
* @returns {void}
*/
_onEncrypting () {
const msDialer = new multistream.Dialer()
msDialer.handle(this.conn, (err) => {
if (err) {
return this.close(maybeUnexpectedEnd(err))
}
this.log('selecting crypto %s to %s', this.switch.crypto.tag, this.theirB58Id)
msDialer.select(this.switch.crypto.tag, (err, _conn) => {
if (err) {
return this.close(maybeUnexpectedEnd(err))
}
const observedConn = observeConnection(null, this.switch.crypto.tag, _conn, this.switch.observer)
const encryptedConn = this.switch.crypto.encrypt(this.ourPeerInfo.id, observedConn, this.theirPeerInfo.id, (err) => {
if (err) {
return this.close(err)
}
this.conn = encryptedConn
this.conn.setPeerInfo(this.theirPeerInfo)
this._state('done')
})
})
})
}
/**
* Iterates over each Muxer on the Switch and attempts to upgrade
* the given `connection`. Successful muxed connections will be stored
* on the Switch.muxedConns with `b58Id` as their key for future reference.
*
* @private
* @returns {void}
*/
_onUpgrading () {
const muxers = Object.keys(this.switch.muxers)
this.log('upgrading connection to %s', this.theirB58Id)
if (muxers.length === 0) {
return this._state('stop')
}
const msDialer = new multistream.Dialer()
msDialer.handle(this.conn, (err) => {
if (err) {
return this._didUpgrade(err)
}
// 1. try to handshake in one of the muxers available
// 2. if succeeds
// - add the muxedConn to the list of muxedConns
// - add incomming new streams to connHandler
const nextMuxer = (key) => {
this.log('selecting %s', key)
msDialer.select(key, (err, _conn) => {
if (err) {
if (muxers.length === 0) {
return this._didUpgrade(err)
}
return nextMuxer(muxers.shift())
}
// observe muxed connections
const conn = observeConnection(null, key, _conn, this.switch.observer)
this.muxer = this.switch.muxers[key].dialer(conn)
this.muxer.once('close', () => {
this.close()
})
// For incoming streams, in case identify is on
this.muxer.on('stream', (conn) => {
this.log('new stream created via muxer to %s', this.theirB58Id)
conn.setPeerInfo(this.theirPeerInfo)
this.switch.protocolMuxer(null)(conn)
})
this._didUpgrade(null)
// Run identify on the connection
if (this.switch.identify) {
this._identify((err, results) => {
if (err) {
return this.close(err)
}
this.theirPeerInfo = this.switch._peerBook.put(results.peerInfo)
})
}
})
}
nextMuxer(muxers.shift())
})
}
/**
* Runs the identify protocol on the connection
* @private
* @param {function(error, { PeerInfo })} callback
* @returns {void}
*/
_identify (callback) {
if (!this.muxer) {
return nextTick(callback, errCode('The connection was already closed', 'ERR_CONNECTION_CLOSED'))
}
this.muxer.newStream(async (err, conn) => {
if (err) return callback(err)
const ms = new multistream.Dialer()
let results
try {
await msHandle(ms, conn)
const msConn = await msSelect(ms, identify.multicodec)
results = await identifyDialer(msConn, this.theirPeerInfo)
} catch (err) {
return callback(err)
}
callback(null, results)
})
}
/**
* Analyses the given error, if it exists, to determine where the state machine
* needs to go.
*
* @param {Error} err
* @returns {void}
*/
_didUpgrade (err) {
if (err) {
this.log('Error upgrading connection:', err)
this.switch.conns[this.theirB58Id] = this
this.emit('error:upgrade_failed', err)
// Cant upgrade, hold the encrypted connection
return this._state('stop')
}
// move the state machine forward
this._state('done')
}
/**
* Performs the protocol handshake for the given protocol
* over the given connection. The resulting error or connection
* will be returned via the callback.
*
* @private
* @param {string} protocol
* @param {Connection} connection
* @param {function(Error, Connection)} callback
* @returns {void}
*/
_protocolHandshake (protocol, connection, callback) {
const msDialer = new multistream.Dialer()
msDialer.handle(connection, (err) => {
if (err) {
return callback(err, null)
}
msDialer.select(protocol, (err, _conn) => {
if (err) {
this.log('could not perform protocol handshake:', err)
return callback(err, null)
}
const conn = observeConnection(null, protocol, _conn, this.switch.observer)
this.log('successfully performed handshake of %s to %s', protocol, this.theirB58Id)
this.emit('connection', conn)
callback(null, conn)
})
})
}
/**
* Event handler for state transition errors
*
* @param {Error} err
* @returns {void}
*/
_onStateError (err) {
this.emit('error', INVALID_STATE_TRANSITION(err))
this.log(err)
}
}
module.exports = withIs(ConnectionFSM, {
className: 'ConnectionFSM',
symbolName: 'libp2p-switch/ConnectionFSM'
})

View File

@ -0,0 +1,289 @@
'use strict'
const identify = require('../../identify')
const multistream = require('multistream-select')
const debug = require('debug')
const log = debug('libp2p:switch:conn-manager')
const once = require('once')
const ConnectionFSM = require('../connection')
const { msHandle, msSelect, identifyDialer } = require('../utils')
const Circuit = require('../../circuit')
const plaintext = require('../plaintext')
/**
* Contains methods for binding handlers to the Switch
* in order to better manage its connections.
*/
class ConnectionManager {
constructor (_switch) {
this.switch = _switch
this.connections = {}
}
/**
* Adds the connection for tracking if it's not already added
* @private
* @param {ConnectionFSM} connection
* @returns {void}
*/
add (connection) {
this.connections[connection.theirB58Id] = this.connections[connection.theirB58Id] || []
// Only add it if it's not there
if (!this.get(connection)) {
this.connections[connection.theirB58Id].push(connection)
this.switch.emit('connection:start', connection.theirPeerInfo)
if (connection.getState() === 'MUXED') {
this.switch.emit('peer-mux-established', connection.theirPeerInfo)
// Clear the denylist of the peer
this.switch.dialer.clearDenylist(connection.theirPeerInfo)
} else {
connection.once('muxed', () => {
this.switch.emit('peer-mux-established', connection.theirPeerInfo)
// Clear the denylist of the peer
this.switch.dialer.clearDenylist(connection.theirPeerInfo)
})
}
}
}
/**
* Gets the connection from the list if it exists
* @private
* @param {ConnectionFSM} connection
* @returns {ConnectionFSM|null} The found connection or null
*/
get (connection) {
if (!this.connections[connection.theirB58Id]) return null
for (let i = 0; i < this.connections[connection.theirB58Id].length; i++) {
if (this.connections[connection.theirB58Id][i] === connection) {
return this.connections[connection.theirB58Id][i]
}
}
return null
}
/**
* Gets a connection associated with the given peer
* @private
* @param {string} peerId The peers id
* @returns {ConnectionFSM|null} The found connection or null
*/
getOne (peerId) {
if (this.connections[peerId]) {
// Only return muxed connections
for (var i = 0; i < this.connections[peerId].length; i++) {
if (this.connections[peerId][i].getState() === 'MUXED') {
return this.connections[peerId][i]
}
}
}
return null
}
/**
* Removes the connection from tracking
* @private
* @param {ConnectionFSM} connection The connection to remove
* @returns {void}
*/
remove (connection) {
// No record of the peer, disconnect it
if (!this.connections[connection.theirB58Id]) {
if (connection.theirPeerInfo) {
connection.theirPeerInfo.disconnect()
this.switch.emit('peer-mux-closed', connection.theirPeerInfo)
}
return
}
for (let i = 0; i < this.connections[connection.theirB58Id].length; i++) {
if (this.connections[connection.theirB58Id][i] === connection) {
this.connections[connection.theirB58Id].splice(i, 1)
break
}
}
// The peer is fully disconnected
if (this.connections[connection.theirB58Id].length === 0) {
delete this.connections[connection.theirB58Id]
connection.theirPeerInfo.disconnect()
this.switch.emit('peer-mux-closed', connection.theirPeerInfo)
}
// A tracked connection was closed, let the world know
this.switch.emit('connection:end', connection.theirPeerInfo)
}
/**
* Returns all connections being tracked
* @private
* @returns {ConnectionFSM[]}
*/
getAll () {
let connections = []
for (const conns of Object.values(this.connections)) {
connections = [...connections, ...conns]
}
return connections
}
/**
* Returns all connections being tracked for a given peer id
* @private
* @param {string} peerId Stringified peer id
* @returns {ConnectionFSM[]}
*/
getAllById (peerId) {
return this.connections[peerId] || []
}
/**
* Adds a listener for the given `muxer` and creates a handler for it
* leveraging the Switch.protocolMuxer handler factory
*
* @param {Muxer} muxer
* @returns {void}
*/
addStreamMuxer (muxer) {
// for dialing
this.switch.muxers[muxer.multicodec] = muxer
// for listening
this.switch.handle(muxer.multicodec, (protocol, conn) => {
const muxedConn = muxer.listener(conn)
muxedConn.on('stream', this.switch.protocolMuxer(null))
// If identify is enabled
// 1. overload getPeerInfo
// 2. call getPeerInfo
// 3. add this conn to the pool
if (this.switch.identify) {
// Get the peer info from the crypto exchange
conn.getPeerInfo((err, cryptoPI) => {
if (err || !cryptoPI) {
log('crypto peerInfo wasnt found')
}
// overload peerInfo to use Identify instead
conn.getPeerInfo = async (callback) => {
const conn = muxedConn.newStream()
const ms = new multistream.Dialer()
callback = once(callback)
let results
try {
await msHandle(ms, conn)
const msConn = await msSelect(ms, identify.multicodec)
results = await identifyDialer(msConn, cryptoPI)
} catch (err) {
return muxedConn.end(() => {
callback(err, null)
})
}
const { peerInfo } = results
if (peerInfo) {
conn.setPeerInfo(peerInfo)
}
callback(null, peerInfo)
}
conn.getPeerInfo((err, peerInfo) => {
/* eslint no-warning-comments: off */
if (err) {
return log('identify not successful')
}
const b58Str = peerInfo.id.toB58String()
peerInfo = this.switch._peerBook.put(peerInfo)
const connection = new ConnectionFSM({
_switch: this.switch,
peerInfo,
muxer: muxedConn,
conn: conn,
type: 'inc'
})
this.switch.connection.add(connection)
// Only update if it's not already connected
if (!peerInfo.isConnected()) {
if (peerInfo.multiaddrs.size > 0) {
// with incomming conn and through identify, going to pick one
// of the available multiaddrs from the other peer as the one
// I'm connected to as we really can't be sure at the moment
// TODO add this consideration to the connection abstraction!
peerInfo.connect(peerInfo.multiaddrs.toArray()[0])
} else {
// for the case of websockets in the browser, where peers have
// no addr, use just their IPFS id
peerInfo.connect(`/ipfs/${b58Str}`)
}
}
muxedConn.once('close', () => {
connection.close()
})
})
})
}
return conn
})
}
/**
* Adds the `encrypt` handler for the given `tag` and also sets the
* Switch's crypto to passed `encrypt` function
*
* @param {String} tag
* @param {function(PeerID, Connection, PeerId, Callback)} encrypt
* @returns {void}
*/
crypto (tag, encrypt) {
if (!tag && !encrypt) {
tag = plaintext.tag
encrypt = plaintext.encrypt
}
this.switch.crypto = { tag, encrypt }
}
/**
* If config.enabled is true, a Circuit relay will be added to the
* available Switch transports.
*
* @param {any} config
* @returns {void}
*/
enableCircuitRelay (config) {
config = config || {}
if (config.enabled) {
if (!config.hop) {
Object.assign(config, { hop: { enabled: false, active: false } })
}
this.switch.transport.add(Circuit.tag, new Circuit(this.switch, config))
}
}
/**
* Sets identify to true on the Switch and performs handshakes
* for libp2p-identify leveraging the Switch's muxer.
*
* @returns {void}
*/
reuse () {
this.switch.identify = true
this.switch.handle(identify.multicodec, (protocol, conn) => {
identify.listener(conn, this.switch._peerInfo)
})
}
}
module.exports = ConnectionManager

12
src/switch/constants.js Normal file
View File

@ -0,0 +1,12 @@
'use strict'
module.exports = {
DENY_TTL: 5 * 60 * 1e3, // How long before an errored peer can be dialed again
DENY_ATTEMPTS: 5, // Num of unsuccessful dials before a peer is permanently denied
DIAL_TIMEOUT: 30e3, // How long in ms a dial attempt is allowed to take
MAX_COLD_CALLS: 50, // How many dials w/o protocols that can be queued
MAX_PARALLEL_DIALS: 100, // Maximum allowed concurrent dials
QUARTER_HOUR: 15 * 60e3,
PRIORITY_HIGH: 10,
PRIORITY_LOW: 20
}

119
src/switch/dialer/index.js Normal file
View File

@ -0,0 +1,119 @@
'use strict'
const DialQueueManager = require('./queueManager')
const { getPeerInfo } = require('../../get-peer-info')
const {
DENY_ATTEMPTS,
DENY_TTL,
MAX_COLD_CALLS,
MAX_PARALLEL_DIALS,
PRIORITY_HIGH,
PRIORITY_LOW
} = require('../constants')
module.exports = function (_switch) {
const dialQueueManager = new DialQueueManager(_switch)
_switch.state.on('STARTED:enter', start)
_switch.state.on('STOPPING:enter', stop)
/**
* @param {DialRequest} dialRequest
* @returns {void}
*/
function _dial ({ peerInfo, protocol, options, callback }) {
if (typeof protocol === 'function') {
callback = protocol
protocol = null
}
try {
peerInfo = getPeerInfo(peerInfo, _switch._peerBook)
} catch (err) {
return callback(err)
}
// Add it to the queue, it will automatically get executed
dialQueueManager.add({ peerInfo, protocol, options, callback })
}
/**
* Starts the `DialQueueManager`
*
* @param {function} callback
*/
function start (callback) {
dialQueueManager.start()
callback()
}
/**
* Aborts all dials that are queued. This should
* only be used when the Switch is being stopped
*
* @param {function} callback
*/
function stop (callback) {
dialQueueManager.stop()
callback()
}
/**
* Clears the denylist for a given peer
* @param {PeerInfo} peerInfo
*/
function clearDenylist (peerInfo) {
dialQueueManager.clearDenylist(peerInfo)
}
/**
* Attempts to establish a connection to the given `peerInfo` at
* a lower priority than a standard dial.
* @param {PeerInfo} peerInfo
* @param {object} options
* @param {boolean} options.useFSM Whether or not to return a `ConnectionFSM`. Defaults to false.
* @param {number} options.priority Lowest priority goes first. Defaults to 20.
* @param {function(Error, Connection)} callback
*/
function connect (peerInfo, options, callback) {
if (typeof options === 'function') {
callback = options
options = null
}
options = { useFSM: false, priority: PRIORITY_LOW, ...options }
_dial({ peerInfo, protocol: null, options, callback })
}
/**
* Adds the dial request to the queue for the given `peerInfo`
* The request will be added with a high priority (10).
* @param {PeerInfo} peerInfo
* @param {string} protocol
* @param {function(Error, Connection)} callback
*/
function dial (peerInfo, protocol, callback) {
_dial({ peerInfo, protocol, options: { useFSM: false, priority: PRIORITY_HIGH }, callback })
}
/**
* Behaves like dial, except it calls back with a ConnectionFSM
*
* @param {PeerInfo} peerInfo
* @param {string} protocol
* @param {function(Error, ConnectionFSM)} callback
*/
function dialFSM (peerInfo, protocol, callback) {
_dial({ peerInfo, protocol, options: { useFSM: true, priority: PRIORITY_HIGH }, callback })
}
return {
connect,
dial,
dialFSM,
clearDenylist,
DENY_ATTEMPTS: isNaN(_switch._options.denyAttempts) ? DENY_ATTEMPTS : _switch._options.denyAttempts,
DENY_TTL: isNaN(_switch._options.denyTTL) ? DENY_TTL : _switch._options.denyTTL,
MAX_COLD_CALLS: isNaN(_switch._options.maxColdCalls) ? MAX_COLD_CALLS : _switch._options.maxColdCalls,
MAX_PARALLEL_DIALS: isNaN(_switch._options.maxParallelDials) ? MAX_PARALLEL_DIALS : _switch._options.maxParallelDials
}
}

281
src/switch/dialer/queue.js Normal file
View File

@ -0,0 +1,281 @@
'use strict'
const ConnectionFSM = require('../connection')
const { DIAL_ABORTED, ERR_DENIED } = require('../errors')
const nextTick = require('async/nextTick')
const once = require('once')
const debug = require('debug')
const log = debug('libp2p:switch:dial')
log.error = debug('libp2p:switch:dial:error')
/**
* Components required to execute a dial
* @typedef {Object} DialRequest
* @property {PeerInfo} peerInfo - The peer to dial to
* @property {string} [protocol] - The protocol to create a stream for
* @property {object} options
* @property {boolean} options.useFSM - If `callback` should return a ConnectionFSM
* @property {number} options.priority - The priority of the dial
* @property {function(Error, Connection|ConnectionFSM)} callback
*/
/**
* @typedef {Object} NewConnection
* @property {ConnectionFSM} connectionFSM
* @property {boolean} didCreate
*/
/**
* Attempts to create a new connection or stream (when muxed),
* via negotiation of the given `protocol`. If no `protocol` is
* provided, no action will be taken and `callback` will be called
* immediately with no error or values.
*
* @param {object} options
* @param {string} options.protocol
* @param {ConnectionFSM} options.connection
* @param {function(Error, Connection)} options.callback
* @returns {void}
*/
function createConnectionWithProtocol ({ protocol, connection, callback }) {
if (!protocol) {
return callback()
}
connection.shake(protocol, (err, conn) => {
if (!conn) {
return callback(err)
}
conn.setPeerInfo(connection.theirPeerInfo)
callback(null, conn)
})
}
/**
* A convenience array wrapper for controlling
* a per peer queue
*
* @returns {Queue}
*/
class Queue {
/**
* @constructor
* @param {string} peerId
* @param {Switch} _switch
* @param {function(string)} onStopped Called when the queue stops
*/
constructor (peerId, _switch, onStopped) {
this.id = peerId
this.switch = _switch
this._queue = []
this.denylisted = null
this.denylistCount = 0
this.isRunning = false
this.onStopped = onStopped
}
get length () {
return this._queue.length
}
/**
* Adds the dial request to the queue. The queue is not automatically started
* @param {string} protocol
* @param {boolean} useFSM If callback should use a ConnectionFSM instead
* @param {function(Error, Connection)} callback
* @returns {void}
*/
add (protocol, useFSM, callback) {
if (!this.isDialAllowed()) {
return nextTick(callback, ERR_DENIED())
}
this._queue.push({ protocol, useFSM, callback })
}
/**
* Determines whether or not dialing is currently allowed
* @returns {boolean}
*/
isDialAllowed () {
if (this.denylisted) {
// If the deny ttl has passed, reset it
if (Date.now() > this.denylisted) {
this.denylisted = null
return true
}
// Dial is not allowed
return false
}
return true
}
/**
* Starts the queue. If the queue was started `true` will be returned.
* If the queue was already running `false` is returned.
* @returns {boolean}
*/
start () {
if (!this.isRunning) {
log('starting dial queue to %s', this.id)
this.isRunning = true
this._run()
return true
}
return false
}
/**
* Stops the queue
*/
stop () {
if (this.isRunning) {
log('stopping dial queue to %s', this.id)
this.isRunning = false
this.onStopped(this.id)
}
}
/**
* Stops the queue and errors the callback for each dial request
*/
abort () {
while (this.length > 0) {
const dial = this._queue.shift()
dial.callback(DIAL_ABORTED())
}
this.stop()
}
/**
* Marks the queue as denylisted. The queue will be immediately aborted.
* @returns {void}
*/
denylist () {
this.denylistCount++
if (this.denylistCount >= this.switch.dialer.DENY_ATTEMPTS) {
this.denylisted = Infinity
return
}
let ttl = this.switch.dialer.DENY_TTL * Math.pow(this.denylistCount, 3)
const minTTL = ttl * 0.9
const maxTTL = ttl * 1.1
// Add a random jitter of 20% to the ttl
ttl = Math.floor(Math.random() * (maxTTL - minTTL) + minTTL)
this.denylisted = Date.now() + ttl
this.abort()
}
/**
* Attempts to find a muxed connection for the given peer. If one
* isn't found, a new one will be created.
*
* Returns an array containing two items. The ConnectionFSM and wether
* or not the ConnectionFSM was just created. The latter can be used
* to determine dialing needs.
*
* @private
* @param {PeerInfo} peerInfo
* @returns {NewConnection}
*/
_getOrCreateConnection (peerInfo) {
let connectionFSM = this.switch.connection.getOne(this.id)
let didCreate = false
if (!connectionFSM) {
connectionFSM = new ConnectionFSM({
_switch: this.switch,
peerInfo,
muxer: null,
conn: null
})
this.switch.connection.add(connectionFSM)
// Add control events and start the dialer
connectionFSM.once('connected', () => connectionFSM.protect())
connectionFSM.once('private', () => connectionFSM.encrypt())
connectionFSM.once('encrypted', () => connectionFSM.upgrade())
didCreate = true
}
return { connectionFSM, didCreate }
}
/**
* Executes the next dial in the queue for the given peer
* @private
* @returns {void}
*/
_run () {
// If we have no items in the queue or we're stopped, exit
if (this.length < 1 || !this.isRunning) {
log('stopping the queue for %s', this.id)
return this.stop()
}
const next = once(() => {
log('starting next dial to %s', this.id)
this._run()
})
const peerInfo = this.switch._peerBook.get(this.id)
const queuedDial = this._queue.shift()
const { connectionFSM, didCreate } = this._getOrCreateConnection(peerInfo)
// If the dial expects a ConnectionFSM, we can provide that back now
if (queuedDial.useFSM) {
nextTick(queuedDial.callback, null, connectionFSM)
}
// If we can handshake protocols, get a new stream and call run again
if (['MUXED', 'CONNECTED'].includes(connectionFSM.getState())) {
queuedDial.connection = connectionFSM
createConnectionWithProtocol(queuedDial)
next()
return
}
// If we error, error the queued dial
// In the future, it may be desired to error the other queued dials,
// depending on the error.
connectionFSM.once('error', (err) => {
queuedDial.callback(err)
// Dont denylist peers we have identified and that we are connected to
if (peerInfo.protocols.size > 0 && peerInfo.isConnected()) {
return
}
this.denylist()
})
connectionFSM.once('close', () => {
next()
})
// If we're not muxed yet, add listeners
connectionFSM.once('muxed', () => {
this.denylistCount = 0 // reset denylisting on good connections
queuedDial.connection = connectionFSM
createConnectionWithProtocol(queuedDial)
next()
})
connectionFSM.once('unmuxed', () => {
this.denylistCount = 0
queuedDial.connection = connectionFSM
createConnectionWithProtocol(queuedDial)
next()
})
// If we have a new connection, start dialing
if (didCreate) {
connectionFSM.dial()
}
}
}
module.exports = Queue

View File

@ -0,0 +1,220 @@
'use strict'
const once = require('once')
const Queue = require('./queue')
const { DIAL_ABORTED } = require('../errors')
const nextTick = require('async/nextTick')
const retimer = require('retimer')
const { QUARTER_HOUR, PRIORITY_HIGH } = require('../constants')
const debug = require('debug')
const log = debug('libp2p:switch:dial:manager')
const noop = () => {}
class DialQueueManager {
/**
* @constructor
* @param {Switch} _switch
*/
constructor (_switch) {
this._queue = new Set()
this._coldCallQueue = new Set()
this._dialingQueues = new Set()
this._queues = {}
this.switch = _switch
this._cleanInterval = retimer(this._clean.bind(this), QUARTER_HOUR)
this.start()
}
/**
* Runs through all queues, aborts and removes them if they
* are no longer valid. A queue that is denylisted indefinitely,
* is considered no longer valid.
* @private
*/
_clean () {
const queues = Object.values(this._queues)
queues.forEach(dialQueue => {
// Clear if the queue has reached max denylist
if (dialQueue.denylisted === Infinity) {
dialQueue.abort()
delete this._queues[dialQueue.id]
return
}
// Keep track of denylisted queues
if (dialQueue.denylisted) return
// Clear if peer is no longer active
// To avoid reallocating memory, dont delete queues of
// connected peers, as these are highly likely to leverage the
// queues in the immediate term
if (!dialQueue.isRunning && dialQueue.length < 1) {
let isConnected = false
try {
const peerInfo = this.switch._peerBook.get(dialQueue.id)
isConnected = Boolean(peerInfo.isConnected())
} catch (_) {
// If we get an error, that means the peerbook doesnt have the peer
}
if (!isConnected) {
dialQueue.abort()
delete this._queues[dialQueue.id]
}
}
})
this._cleanInterval.reschedule(QUARTER_HOUR)
}
/**
* Allows the `DialQueueManager` to execute dials
*/
start () {
this.isRunning = true
}
/**
* Iterates over all items in the DialerQueue
* and executes there callback with an error.
*
* This causes the entire DialerQueue to be drained
*/
stop () {
this.isRunning = false
// Clear the general queue
this._queue.clear()
// Clear the cold call queue
this._coldCallQueue.clear()
this._cleanInterval.clear()
// Abort the individual peer queues
const queues = Object.values(this._queues)
queues.forEach(dialQueue => {
dialQueue.abort()
delete this._queues[dialQueue.id]
})
}
/**
* Adds the `dialRequest` to the queue and ensures queue is running
*
* @param {DialRequest} dialRequest
* @returns {void}
*/
add ({ peerInfo, protocol, options, callback }) {
callback = callback ? once(callback) : noop
// Add the dial to its respective queue
const targetQueue = this.getQueue(peerInfo)
// Cold Call
if (options.priority > PRIORITY_HIGH) {
// If we have too many cold calls, abort the dial immediately
if (this._coldCallQueue.size >= this.switch.dialer.MAX_COLD_CALLS) {
return nextTick(callback, DIAL_ABORTED())
}
if (this._queue.has(targetQueue.id)) {
return nextTick(callback, DIAL_ABORTED())
}
}
targetQueue.add(protocol, options.useFSM, callback)
// If we're already connected to the peer, start the queue now
// While it might cause queues to go over the max parallel amount,
// it avoids denying peers we're already connected to
if (peerInfo.isConnected()) {
targetQueue.start()
return
}
// If dialing is not allowed, abort
if (!targetQueue.isDialAllowed()) {
return
}
// Add the id to its respective queue set if the queue isn't running
if (!targetQueue.isRunning) {
if (options.priority <= PRIORITY_HIGH) {
this._queue.add(targetQueue.id)
this._coldCallQueue.delete(targetQueue.id)
// Only add it to the cold queue if it's not in the normal queue
} else {
this._coldCallQueue.add(targetQueue.id)
}
}
this.run()
}
/**
* Will execute up to `MAX_PARALLEL_DIALS` dials
*/
run () {
if (!this.isRunning) return
if (this._dialingQueues.size < this.switch.dialer.MAX_PARALLEL_DIALS) {
let nextQueue = { done: true }
// Check the queue first and fall back to the cold call queue
if (this._queue.size > 0) {
nextQueue = this._queue.values().next()
this._queue.delete(nextQueue.value)
} else if (this._coldCallQueue.size > 0) {
nextQueue = this._coldCallQueue.values().next()
this._coldCallQueue.delete(nextQueue.value)
}
if (nextQueue.done) {
return
}
const targetQueue = this._queues[nextQueue.value]
if (!targetQueue) {
log('missing queue %s, maybe it was aborted?', nextQueue.value)
return
}
this._dialingQueues.add(targetQueue.id)
targetQueue.start()
}
}
/**
* Will remove the `peerInfo` from the dial denylist
* @param {PeerInfo} peerInfo
*/
clearDenylist (peerInfo) {
const queue = this.getQueue(peerInfo)
queue.denylisted = null
queue.denylistCount = 0
}
/**
* A handler for when dialing queues stop. This will trigger
* `run()` in order to keep the queue processing.
* @private
* @param {string} id peer id of the queue that stopped
*/
_onQueueStopped (id) {
this._dialingQueues.delete(id)
this.run()
}
/**
* Returns the `Queue` for the given `peerInfo`
* @param {PeerInfo} peerInfo
* @returns {Queue}
*/
getQueue (peerInfo) {
const id = peerInfo.id.toB58String()
this._queues[id] = this._queues[id] || new Queue(id, this.switch, this._onQueueStopped.bind(this))
return this._queues[id]
}
}
module.exports = DialQueueManager

20
src/switch/errors.js Normal file
View File

@ -0,0 +1,20 @@
'use strict'
const errCode = require('err-code')
module.exports = {
CONNECTION_FAILED: (err) => errCode(err, 'CONNECTION_FAILED'),
DIAL_ABORTED: () => errCode('Dial was aborted', 'DIAL_ABORTED'),
ERR_DENIED: () => errCode('Dial is currently denied for this peer', 'ERR_DENIED'),
DIAL_SELF: () => errCode('A node cannot dial itself', 'DIAL_SELF'),
INVALID_STATE_TRANSITION: (err) => errCode(err, 'INVALID_STATE_TRANSITION'),
NO_TRANSPORTS_REGISTERED: () => errCode('No transports registered, dial not possible', 'NO_TRANSPORTS_REGISTERED'),
PROTECTOR_REQUIRED: () => errCode('No protector provided with private network enforced', 'PROTECTOR_REQUIRED'),
UNEXPECTED_END: () => errCode('Unexpected end of input from reader.', 'UNEXPECTED_END'),
maybeUnexpectedEnd: (err) => {
if (err === true) {
return module.exports.UNEXPECTED_END()
}
return err
}
}

274
src/switch/index.js Normal file
View File

@ -0,0 +1,274 @@
'use strict'
const FSM = require('fsm-event')
const EventEmitter = require('events').EventEmitter
const each = require('async/each')
const eachSeries = require('async/eachSeries')
const series = require('async/series')
const Circuit = require('../circuit')
const TransportManager = require('./transport')
const ConnectionManager = require('./connection/manager')
const { getPeerInfo } = require('../get-peer-info')
const getDialer = require('./dialer')
const connectionHandler = require('./connection/handler')
const ProtocolMuxer = require('./protocol-muxer')
const plaintext = require('./plaintext')
const Observer = require('./observer')
const Stats = require('./stats')
const assert = require('assert')
const Errors = require('./errors')
const debug = require('debug')
const log = debug('libp2p:switch')
log.error = debug('libp2p:switch:error')
/**
* @fires Switch#stop Triggered when the switch has stopped
* @fires Switch#start Triggered when the switch has started
* @fires Switch#error Triggered whenever an error occurs
*/
class Switch extends EventEmitter {
constructor (peerInfo, peerBook, options) {
super()
assert(peerInfo, 'You must provide a `peerInfo`')
assert(peerBook, 'You must provide a `peerBook`')
this._peerInfo = peerInfo
this._peerBook = peerBook
this._options = options || {}
this.setMaxListeners(Infinity)
// transports --
// { key: transport }; e.g { tcp: <tcp> }
this.transports = {}
// connections --
// { peerIdB58: { conn: <conn> }}
this.conns = {}
// { protocol: handler }
this.protocols = {}
// { muxerCodec: <muxer> } e.g { '/spdy/0.3.1': spdy }
this.muxers = {}
// is the Identify protocol enabled?
this.identify = false
// Crypto details
this.crypto = plaintext
this.protector = this._options.protector || null
this.transport = new TransportManager(this)
this.connection = new ConnectionManager(this)
this.observer = Observer(this)
this.stats = Stats(this.observer, this._options.stats)
this.protocolMuxer = ProtocolMuxer(this.protocols, this.observer)
// All purpose connection handler for managing incoming connections
this._connectionHandler = connectionHandler(this)
// Setup the internal state
this.state = new FSM('STOPPED', {
STOPPED: {
start: 'STARTING',
stop: 'STOPPING' // ensures that any transports that were manually started are stopped
},
STARTING: {
done: 'STARTED',
stop: 'STOPPING'
},
STARTED: {
stop: 'STOPPING',
start: 'STARTED'
},
STOPPING: {
stop: 'STOPPING',
done: 'STOPPED'
}
})
this.state.on('STARTING', () => {
log('The switch is starting')
this._onStarting()
})
this.state.on('STOPPING', () => {
log('The switch is stopping')
this._onStopping()
})
this.state.on('STARTED', () => {
log('The switch has started')
this.emit('start')
})
this.state.on('STOPPED', () => {
log('The switch has stopped')
this.emit('stop')
})
this.state.on('error', (err) => {
log.error(err)
this.emit('error', err)
})
// higher level (public) API
this.dialer = getDialer(this)
this.dial = this.dialer.dial
this.dialFSM = this.dialer.dialFSM
}
/**
* Returns a list of the transports peerInfo has addresses for
*
* @param {PeerInfo} peerInfo
* @returns {Array<Transport>}
*/
availableTransports (peerInfo) {
const myAddrs = peerInfo.multiaddrs.toArray()
const myTransports = Object.keys(this.transports)
// Only listen on transports we actually have addresses for
return myTransports.filter((ts) => this.transports[ts].filter(myAddrs).length > 0)
// push Circuit to be the last proto to be dialed, and alphabetize the others
.sort((a, b) => {
if (a === Circuit.tag) return 1
if (b === Circuit.tag) return -1
return a < b ? -1 : 1
})
}
/**
* Adds the `handlerFunc` and `matchFunc` to the Switch's protocol
* handler list for the given `protocol`. If the `matchFunc` returns
* true for a protocol check, the `handlerFunc` will be called.
*
* @param {string} protocol
* @param {function(string, Connection)} handlerFunc
* @param {function(string, string, function(Error, boolean))} matchFunc
* @returns {void}
*/
handle (protocol, handlerFunc, matchFunc) {
this.protocols[protocol] = {
handlerFunc: handlerFunc,
matchFunc: matchFunc
}
this._peerInfo.protocols.add(protocol)
}
/**
* Removes the given protocol from the Switch's protocol list
*
* @param {string} protocol
* @returns {void}
*/
unhandle (protocol) {
if (this.protocols[protocol]) {
delete this.protocols[protocol]
}
this._peerInfo.protocols.delete(protocol)
}
/**
* If a muxed Connection exists for the given peer, it will be closed
* and its reference on the Switch will be removed.
*
* @param {PeerInfo|Multiaddr|PeerId} peer
* @param {function()} callback
* @returns {void}
*/
hangUp (peer, callback) {
const peerInfo = getPeerInfo(peer, this._peerBook)
const key = peerInfo.id.toB58String()
const conns = [...this.connection.getAllById(key)]
each(conns, (conn, cb) => {
conn.once('close', cb)
conn.close()
}, callback)
}
/**
* Returns whether or not the switch has any transports
*
* @returns {boolean}
*/
hasTransports () {
const transports = Object.keys(this.transports).filter((t) => t !== Circuit.tag)
return transports && transports.length > 0
}
/**
* Issues a start on the Switch state.
*
* @param {function} callback deprecated: Listening for the `error` and `start` events are recommended
* @returns {void}
*/
start (callback = () => {}) {
// Add once listener for deprecated callback support
this.once('start', callback)
this.state('start')
}
/**
* Issues a stop on the Switch state.
*
* @param {function} callback deprecated: Listening for the `error` and `stop` events are recommended
* @returns {void}
*/
stop (callback = () => {}) {
// Add once listener for deprecated callback support
this.once('stop', callback)
this.state('stop')
}
/**
* A listener that will start any necessary services and listeners
*
* @private
* @returns {void}
*/
_onStarting () {
this.stats.start()
eachSeries(this.availableTransports(this._peerInfo), (ts, cb) => {
// Listen on the given transport
this.transport.listen(ts, {}, null, cb)
}, (err) => {
if (err) {
log.error(err)
this.emit('error', err)
return this.state('stop')
}
this.state('done')
})
}
/**
* A listener that will turn off all running services and listeners
*
* @private
* @returns {void}
*/
_onStopping () {
this.stats.stop()
series([
(cb) => {
each(this.transports, (transport, cb) => {
each(transport.listeners, (listener, cb) => {
listener.close((err) => {
if (err) log.error(err)
cb()
})
}, cb)
}, cb)
},
(cb) => each(this.connection.getAll(), (conn, cb) => {
conn.once('close', cb)
conn.close()
}, cb)
], (_) => {
this.state('done')
})
}
}
module.exports = Switch
module.exports.errors = Errors

View File

@ -0,0 +1,88 @@
'use strict'
const tryEach = require('async/tryEach')
const debug = require('debug')
const log = debug('libp2p:switch:dialer')
const DialQueue = require('./queue')
/**
* Track dials per peer and limited them.
*/
class LimitDialer {
/**
* Create a new dialer.
*
* @param {number} perPeerLimit
* @param {number} dialTimeout
*/
constructor (perPeerLimit, dialTimeout) {
log('create: %s peer limit, %s dial timeout', perPeerLimit, dialTimeout)
this.perPeerLimit = perPeerLimit
this.dialTimeout = dialTimeout
this.queues = new Map()
}
/**
* Dial a list of multiaddrs on the given transport.
*
* @param {PeerId} peer
* @param {SwarmTransport} transport
* @param {Array<Multiaddr>} addrs
* @param {function(Error, Connection)} callback
* @returns {void}
*/
dialMany (peer, transport, addrs, callback) {
log('dialMany:start')
// we use a token to track if we want to cancel following dials
const token = { cancel: false }
const errors = []
const tasks = addrs.map((m) => {
return (cb) => this.dialSingle(peer, transport, m, token, (err, result) => {
if (err) {
errors.push(err)
return cb(err)
}
return cb(null, result)
})
})
tryEach(tasks, (_, result) => {
if (result && result.conn) {
log('dialMany:success')
return callback(null, result)
}
log('dialMany:error')
callback(errors)
})
}
/**
* Dial a single multiaddr on the given transport.
*
* @param {PeerId} peer
* @param {SwarmTransport} transport
* @param {Multiaddr} addr
* @param {CancelToken} token
* @param {function(Error, Connection)} callback
* @returns {void}
*/
dialSingle (peer, transport, addr, token, callback) {
const ps = peer.toB58String()
log('dialSingle: %s:%s', ps, addr.toString())
let q
if (this.queues.has(ps)) {
q = this.queues.get(ps)
} else {
q = new DialQueue(this.perPeerLimit, this.dialTimeout)
this.queues.set(ps, q)
}
q.push(transport, addr, token, callback)
}
}
module.exports = LimitDialer

View File

@ -0,0 +1,109 @@
'use strict'
const { Connection } = require('libp2p-interfaces/src/connection')
const pull = require('pull-stream/pull')
const empty = require('pull-stream/sources/empty')
const timeout = require('async/timeout')
const queue = require('async/queue')
const debug = require('debug')
const once = require('once')
const log = debug('libp2p:switch:dialer:queue')
log.error = debug('libp2p:switch:dialer:queue:error')
/**
* Queue up the amount of dials to a given peer.
*/
class DialQueue {
/**
* Create a new dial queue.
*
* @param {number} limit
* @param {number} dialTimeout
*/
constructor (limit, dialTimeout) {
this.dialTimeout = dialTimeout
this.queue = queue((task, cb) => {
this._doWork(task.transport, task.addr, task.token, cb)
}, limit)
}
/**
* The actual work done by the queue.
*
* @param {SwarmTransport} transport
* @param {Multiaddr} addr
* @param {CancelToken} token
* @param {function(Error, Connection)} callback
* @returns {void}
* @private
*/
_doWork (transport, addr, token, callback) {
callback = once(callback)
log('work:start')
this._dialWithTimeout(transport, addr, (err, conn) => {
if (err) {
log.error(`${transport.constructor.name}:work`, err)
return callback(err)
}
if (token.cancel) {
log('work:cancel')
// clean up already done dials
pull(empty(), conn)
// If we can close the connection, do it
if (typeof conn.close === 'function') {
return conn.close((_) => callback(null))
}
return callback(null)
}
// one is enough
token.cancel = true
log('work:success')
const proxyConn = new Connection()
proxyConn.setInnerConn(conn)
callback(null, { multiaddr: addr, conn: conn })
})
}
/**
* Dial the given transport, timing out with the set timeout.
*
* @param {SwarmTransport} transport
* @param {Multiaddr} addr
* @param {function(Error, Connection)} callback
* @returns {void}
*
* @private
*/
_dialWithTimeout (transport, addr, callback) {
timeout((cb) => {
const conn = transport.dial(addr, (err) => {
if (err) {
return cb(err)
}
cb(null, conn)
})
}, this.dialTimeout)(callback)
}
/**
* Add new work to the queue.
*
* @param {SwarmTransport} transport
* @param {Multiaddr} addr
* @param {CancelToken} token
* @param {function(Error, Connection)} callback
* @returns {void}
*/
push (transport, addr, token, callback) {
this.queue.push({ transport, addr, token }, callback)
}
}
module.exports = DialQueue

View File

@ -0,0 +1,44 @@
'use strict'
const { Connection } = require('libp2p-interfaces/src/connection')
const pull = require('pull-stream/pull')
/**
* Creates a pull stream to run the given Connection stream through
* the given Observer. This provides a way to more easily monitor connections
* and their metadata. A new Connection will be returned that contains
* has the attached Observer.
*
* @param {Transport} transport
* @param {string} protocol
* @param {Connection} connection
* @param {Observer} observer
* @returns {Connection}
*/
module.exports = (transport, protocol, connection, observer) => {
const peerInfo = new Promise((resolve, reject) => {
connection.getPeerInfo((err, peerInfo) => {
if (!err && peerInfo) {
resolve(peerInfo)
return
}
const setPeerInfo = connection.setPeerInfo
connection.setPeerInfo = (pi) => {
setPeerInfo.call(connection, pi)
resolve(pi)
}
})
})
const stream = {
source: pull(
connection,
observer.incoming(transport, protocol, peerInfo)),
sink: pull(
observer.outgoing(transport, protocol, peerInfo),
connection)
}
return new Connection(stream, connection)
}

48
src/switch/observer.js Normal file
View File

@ -0,0 +1,48 @@
'use strict'
const map = require('pull-stream/throughs/map')
const EventEmitter = require('events')
/**
* Takes a Switch and returns an Observer that can be used in conjunction with
* observe-connection.js. The returned Observer comes with `incoming` and
* `outgoing` properties that can be used in pull streams to emit all metadata
* for messages that pass through a Connection.
*
* @param {Switch} swtch
* @returns {EventEmitter}
*/
module.exports = (swtch) => {
const observer = Object.assign(new EventEmitter(), {
incoming: observe('in'),
outgoing: observe('out')
})
swtch.on('peer-mux-established', (peerInfo) => {
observer.emit('peer:connected', peerInfo.id.toB58String())
})
swtch.on('peer-mux-closed', (peerInfo) => {
observer.emit('peer:closed', peerInfo.id.toB58String())
})
return observer
function observe (direction) {
return (transport, protocol, peerInfo) => {
return map((buffer) => {
willObserve(peerInfo, transport, protocol, direction, buffer.length)
return buffer
})
}
}
function willObserve (peerInfo, transport, protocol, direction, bufferLength) {
peerInfo.then((_peerInfo) => {
if (_peerInfo) {
const peerId = _peerInfo.id.toB58String()
observer.emit('message', peerId, transport, protocol, direction, bufferLength)
}
})
}
}

20
src/switch/plaintext.js Normal file
View File

@ -0,0 +1,20 @@
'use strict'
const setImmediate = require('async/setImmediate')
/**
* An encryption stub in the instance that the default crypto
* has not been overriden for the Switch
*/
module.exports = {
tag: '/plaintext/1.0.0',
encrypt (myId, conn, remoteId, callback) {
if (typeof remoteId === 'function') {
callback = remoteId
remoteId = undefined
}
setImmediate(() => callback())
return conn
}
}

View File

@ -0,0 +1,48 @@
'use strict'
const multistream = require('multistream-select')
const observeConn = require('./observe-connection')
const debug = require('debug')
const log = debug('libp2p:switch:protocol-muxer')
log.error = debug('libp2p:switch:protocol-muxer:error')
module.exports = function protocolMuxer (protocols, observer) {
return (transport) => (_parentConn, msListener) => {
const ms = msListener || new multistream.Listener()
let parentConn
// Only observe the transport if we have one, and there is not already a listener
if (transport && !msListener) {
parentConn = observeConn(transport, null, _parentConn, observer)
} else {
parentConn = _parentConn
}
Object.keys(protocols).forEach((protocol) => {
if (!protocol) {
return
}
const handler = (protocolName, _conn) => {
log('registering handler with protocol %s', protocolName)
const protocol = protocols[protocolName]
if (protocol) {
const handlerFunc = protocol && protocol.handlerFunc
if (handlerFunc) {
const conn = observeConn(null, protocolName, _conn, observer)
handlerFunc(protocol, conn)
}
}
}
ms.addHandler(protocol, handler, protocols[protocol].matchFunc)
})
ms.handle(parentConn, (err) => {
if (err) {
log.error('multistream handshake failed', err)
}
})
}
}

150
src/switch/stats/index.js Normal file
View File

@ -0,0 +1,150 @@
'use strict'
const EventEmitter = require('events')
const Stat = require('./stat')
const OldPeers = require('./old-peers')
const defaultOptions = {
computeThrottleMaxQueueSize: 1000,
computeThrottleTimeout: 2000,
movingAverageIntervals: [
60 * 1000, // 1 minute
5 * 60 * 1000, // 5 minutes
15 * 60 * 1000 // 15 minutes
],
maxOldPeersRetention: 50
}
const initialCounters = [
'dataReceived',
'dataSent'
]
const directionToEvent = {
in: 'dataReceived',
out: 'dataSent'
}
/**
* Binds to message events on the given `observer` to generate stats
* based on the Peer, Protocol and Transport used for the message. Stat
* events will be emitted via the `update` event.
*
* @param {Observer} observer
* @param {any} _options
* @returns {Stats}
*/
module.exports = (observer, _options) => {
const options = Object.assign({}, defaultOptions, _options)
const globalStats = new Stat(initialCounters, options)
const stats = Object.assign(new EventEmitter(), {
start: start,
stop: stop,
global: globalStats,
peers: () => Array.from(peerStats.keys()),
forPeer: (peerId) => {
return peerStats.get(peerId) || oldPeers.get(peerId)
},
transports: () => Array.from(transportStats.keys()),
forTransport: (transport) => transportStats.get(transport),
protocols: () => Array.from(protocolStats.keys()),
forProtocol: (protocol) => protocolStats.get(protocol)
})
globalStats.on('update', propagateChange)
const oldPeers = OldPeers(options.maxOldPeersRetention)
const peerStats = new Map()
const transportStats = new Map()
const protocolStats = new Map()
observer.on('peer:closed', (peerId) => {
const peer = peerStats.get(peerId)
if (peer) {
peer.removeListener('update', propagateChange)
peer.stop()
peerStats.delete(peerId)
oldPeers.set(peerId, peer)
}
})
return stats
function onMessage (peerId, transportTag, protocolTag, direction, bufferLength) {
const event = directionToEvent[direction]
if (transportTag) {
// because it has a transport tag, this message is at the global level, so we account this
// traffic as global.
globalStats.push(event, bufferLength)
// peer stats
let peer = peerStats.get(peerId)
if (!peer) {
peer = oldPeers.get(peerId)
if (peer) {
oldPeers.delete(peerId)
} else {
peer = new Stat(initialCounters, options)
}
peer.on('update', propagateChange)
peer.start()
peerStats.set(peerId, peer)
}
peer.push(event, bufferLength)
}
// transport stats
if (transportTag) {
let transport = transportStats.get(transportTag)
if (!transport) {
transport = new Stat(initialCounters, options)
transport.on('update', propagateChange)
transportStats.set(transportTag, transport)
}
transport.push(event, bufferLength)
}
// protocol stats
if (protocolTag) {
let protocol = protocolStats.get(protocolTag)
if (!protocol) {
protocol = new Stat(initialCounters, options)
protocol.on('update', propagateChange)
protocolStats.set(protocolTag, protocol)
}
protocol.push(event, bufferLength)
}
}
function start () {
observer.on('message', onMessage)
globalStats.start()
for (const peerStat of peerStats.values()) {
peerStat.start()
}
for (const transportStat of transportStats.values()) {
transportStat.start()
}
}
function stop () {
observer.removeListener('message', onMessage)
globalStats.stop()
for (const peerStat of peerStats.values()) {
peerStat.stop()
}
for (const transportStat of transportStats.values()) {
transportStat.stop()
}
}
function propagateChange () {
stats.emit('update')
}
}

Some files were not shown because too many files have changed in this diff Show More