Compare commits

..

1 Commits

Author SHA1 Message Date
10d7212373 fix: protocols change event
I was spelunking last night and found that pubsub was constantly having it's `onConnect` handler fired from the multicodec topology.

It was because bitswap was calling `dialProtocol` to connect to a peer. `dialProtocol` makes a `PeerInfo` out of the multiaddr it's given but it has no `protocols` i.e. `[]`. This is passed to `peerStore.update()` where we compare `[]` with an array of populated protocols. Obviously there is no intersection here so `change:protocols` was being emitted, even though no protocols were added or removed.

This logic needs to be improved and tested properly but I just wanted to send a PR to document my findings.
2020-02-06 09:23:30 +00:00
130 changed files with 1928 additions and 8739 deletions

View File

@ -4,21 +4,21 @@ const Libp2p = require('./src')
const { MULTIADDRS_WEBSOCKETS } = require('./test/fixtures/browser') const { MULTIADDRS_WEBSOCKETS } = require('./test/fixtures/browser')
const Peers = require('./test/fixtures/peers') const Peers = require('./test/fixtures/peers')
const PeerId = require('peer-id') const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const WebSockets = require('libp2p-websockets') const WebSockets = require('libp2p-websockets')
const Muxer = require('libp2p-mplex') const Muxer = require('libp2p-mplex')
const { NOISE: Crypto } = require('libp2p-noise') const Crypto = require('libp2p-secio')
const pipe = require('it-pipe') const pipe = require('it-pipe')
let libp2p let libp2p
const before = async () => { const before = async () => {
// Use the last peer // Use the last peer
const peerId = await PeerId.createFromJSON(Peers[Peers.length - 1]) const peerId = await PeerId.createFromJSON(Peers[Peers.length - 1])
const peerInfo = new PeerInfo(peerId)
peerInfo.multiaddrs.add(MULTIADDRS_WEBSOCKETS[0])
libp2p = new Libp2p({ libp2p = new Libp2p({
addresses: { peerInfo,
listen: [MULTIADDRS_WEBSOCKETS[0]]
},
peerId,
modules: { modules: {
transport: [WebSockets], transport: [WebSockets],
streamMuxer: [Muxer], streamMuxer: [Muxer],
@ -45,7 +45,7 @@ const after = async () => {
} }
module.exports = { module.exports = {
bundlesize: { maxSize: '200kB' }, bundlesize: { maxSize: '220kB' },
hooks: { hooks: {
pre: before, pre: before,
post: after post: after

View File

@ -1,134 +1,3 @@
<a name="0.28.1"></a>
## [0.28.1](https://github.com/libp2p/js-libp2p/compare/v0.28.0...v0.28.1) (2020-06-12)
### Bug Fixes
* throw if no conn encryption module provided ([#665](https://github.com/libp2p/js-libp2p/issues/665)) ([c038550](https://github.com/libp2p/js-libp2p/commit/c038550))
### Features
* add ConnectionManager#getAll ([8f680e2](https://github.com/libp2p/js-libp2p/commit/8f680e2))
<a name="0.28.0"></a>
# [0.28.0](https://github.com/libp2p/js-libp2p/compare/v0.28.0-rc.0...v0.28.0) (2020-06-05)
<a name="0.28.0-rc.0"></a>
# [0.28.0-rc.0](https://github.com/libp2p/js-libp2p/compare/v0.27.8...v0.28.0-rc.0) (2020-05-28)
### Bug Fixes
* always emit when a connection is made ([72f37ac](https://github.com/libp2p/js-libp2p/commit/72f37ac))
* expose the muxed stream interface on inbound streams ([52a615f](https://github.com/libp2p/js-libp2p/commit/52a615f))
* libp2p connections getter ([aaf62a4](https://github.com/libp2p/js-libp2p/commit/aaf62a4))
* onConnect should not add addr to the addressBook ([2b45fee](https://github.com/libp2p/js-libp2p/commit/2b45fee))
* use libp2p.multiaddrs instead of listen ([7fbd155](https://github.com/libp2p/js-libp2p/commit/7fbd155))
* **example:** rename misleading variable ([#645](https://github.com/libp2p/js-libp2p/issues/645)) ([b781911](https://github.com/libp2p/js-libp2p/commit/b781911))
### Chores
* deprecate old peer store api ([#598](https://github.com/libp2p/js-libp2p/issues/598)) ([ed6d5bb](https://github.com/libp2p/js-libp2p/commit/ed6d5bb))
* remove peer-info usage ([12e48ad](https://github.com/libp2p/js-libp2p/commit/12e48ad))
### Features
* address and proto books ([#590](https://github.com/libp2p/js-libp2p/issues/590)) ([e9d225c](https://github.com/libp2p/js-libp2p/commit/e9d225c))
* address manager ([2a7967c](https://github.com/libp2p/js-libp2p/commit/2a7967c))
* keybook ([ce38033](https://github.com/libp2p/js-libp2p/commit/ce38033))
* metadata book ([#638](https://github.com/libp2p/js-libp2p/issues/638)) ([84b935f](https://github.com/libp2p/js-libp2p/commit/84b935f))
* peerStore persistence ([5123a83](https://github.com/libp2p/js-libp2p/commit/5123a83))
* support dial only on transport manager to tolerate errors ([#643](https://github.com/libp2p/js-libp2p/issues/643)) ([698c1df](https://github.com/libp2p/js-libp2p/commit/698c1df))
### BREAKING CHANGES
* all API methods with peer-info parameters or return values were changed. You can check the API.md document, in order to check the new values to use
* the peer-store api changed. Check the API docs for the new specification.
* chore: apply suggestions from code review
Co-Authored-By: Jacob Heun <jacobheun@gmail.com>
* chore: apply suggestions from code review
Co-Authored-By: Jacob Heun <jacobheun@gmail.com>
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
<a name="0.27.8"></a>
## [0.27.8](https://github.com/libp2p/js-libp2p/compare/v0.27.7...v0.27.8) (2020-05-06)
### Bug Fixes
* reset discovery services upon stop ([#618](https://github.com/libp2p/js-libp2p/issues/618)) ([ea0621b](https://github.com/libp2p/js-libp2p/commit/ea0621b))
<a name="0.27.7"></a>
## [0.27.7](https://github.com/libp2p/js-libp2p/compare/v0.27.6...v0.27.7) (2020-04-24)
### Bug Fixes
* remove node global ([#587](https://github.com/libp2p/js-libp2p/issues/587)) ([9b13fe3](https://github.com/libp2p/js-libp2p/commit/9b13fe3))
<a name="0.27.6"></a>
## [0.27.6](https://github.com/libp2p/js-libp2p/compare/v0.27.5...v0.27.6) (2020-04-16)
### Bug Fixes
* add null check in libp2p.hangUp() ([c940f2d](https://github.com/libp2p/js-libp2p/commit/c940f2d))
* make circuit relay listening addresses more forgiving ([#604](https://github.com/libp2p/js-libp2p/issues/604)) ([e192eb6](https://github.com/libp2p/js-libp2p/commit/e192eb6))
<a name="0.27.5"></a>
## [0.27.5](https://github.com/libp2p/js-libp2p/compare/v0.27.4...v0.27.5) (2020-04-06)
### Bug Fixes
* await peer discovery start in libp2p start ([#600](https://github.com/libp2p/js-libp2p/issues/600)) ([bd7fd0f](https://github.com/libp2p/js-libp2p/commit/bd7fd0f))
<a name="0.27.4"></a>
## [0.27.4](https://github.com/libp2p/js-libp2p/compare/v0.27.3...v0.27.4) (2020-03-31)
### Bug Fixes
* only use a single export ([#596](https://github.com/libp2p/js-libp2p/issues/596)) ([3072875](https://github.com/libp2p/js-libp2p/commit/3072875))
* pass libp2p to discovery services ([#597](https://github.com/libp2p/js-libp2p/issues/597)) ([9e35fbc](https://github.com/libp2p/js-libp2p/commit/9e35fbc))
* **test:** improve flakey random walk discovery test ([#574](https://github.com/libp2p/js-libp2p/issues/574)) ([f4ec355](https://github.com/libp2p/js-libp2p/commit/f4ec355))
* remove use of assert module ([#561](https://github.com/libp2p/js-libp2p/issues/561)) ([a8984c6](https://github.com/libp2p/js-libp2p/commit/a8984c6))
<a name="0.27.3"></a>
## [0.27.3](https://github.com/libp2p/js-libp2p/compare/v0.27.2...v0.27.3) (2020-02-11)
### Bug Fixes
* dont allow multiaddr dials without a peer id ([#558](https://github.com/libp2p/js-libp2p/issues/558)) ([a317a8b](https://github.com/libp2p/js-libp2p/commit/a317a8b))
<a name="0.27.2"></a> <a name="0.27.2"></a>
## [0.27.2](https://github.com/libp2p/js-libp2p/compare/v0.27.1...v0.27.2) (2020-02-05) ## [0.27.2](https://github.com/libp2p/js-libp2p/compare/v0.27.1...v0.27.2) (2020-02-05)

View File

@ -37,7 +37,7 @@ One of following:
<!-- <!--
This is for you! Please read, and then delete this text before posting it. This is for you! Please read, and then delete this text before posting it.
The js-libp2p issues are only for bug reports and directly actionable features. The js-ipfs issues are only for bug reports and directly actionable features.
Read https://github.com/ipfs/community/blob/master/CONTRIBUTING.md#reporting-issues if your issue doesn't fit either of those categories. Read https://github.com/ipfs/community/blob/master/CONTRIBUTING.md#reporting-issues if your issue doesn't fit either of those categories.
--> -->

View File

@ -11,8 +11,6 @@
<a href="https://riot.im/app/#/room/#libp2p:matrix.org"><img src="https://img.shields.io/badge/matrix-%23libp2p%3Apermaweb.io-blue.svg?style=flat-square" /> </a> <a href="https://riot.im/app/#/room/#libp2p:matrix.org"><img src="https://img.shields.io/badge/matrix-%23libp2p%3Apermaweb.io-blue.svg?style=flat-square" /> </a>
<a href="https://discord.gg/66KBrm2"><img src="https://img.shields.io/discord/475789330380488707?color=blueviolet&label=discord&style=flat-square" /></a> <a href="https://discord.gg/66KBrm2"><img src="https://img.shields.io/discord/475789330380488707?color=blueviolet&label=discord&style=flat-square" /></a>
<a href="https://discuss.libp2p.io"><img src="https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg" /></a> <a href="https://discuss.libp2p.io"><img src="https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg" /></a>
<a href="https://www.npmjs.com/package/libp2p"><img src="https://img.shields.io/npm/dm/libp2p.svg" /></a>
<a href="https://www.jsdelivr.com/package/npm/libp2p"><img src="https://data.jsdelivr.com/v1/package/npm/libp2p/badge"/></a>
</p> </p>
<p align="center"> <p align="center">
@ -35,11 +33,13 @@ We've come a long way, but this project is still in Alpha, lots of development i
The documentation in the master branch may contain changes from a pre-release. The documentation in the master branch may contain changes from a pre-release.
If you are looking for the documentation of the latest release, you can view the latest release on [**npm**](https://www.npmjs.com/package/libp2p), or select the tag in github that matches the version you are looking for. If you are looking for the documentation of the latest release, you can view the latest release on [**npm**](https://www.npmjs.com/package/libp2p), or select the tag in github that matches the version you are looking for.
**Want to get started?** Check our [GETTING_STARTED.md](./doc/GETTING_STARTED.md) guide and [examples folder](/examples). **Want to get started?** Check our [examples folder](/examples).
**Want to update libp2p in your project?** Check our [migrations folder](./doc/migrations). [**`Weekly Core Dev Calls`**](https://github.com/ipfs/pm/issues/650)
[**`Weekly Core Dev Calls`**](https://github.com/libp2p/team-mgmt/issues/16) ## Tech Lead
[David Dias](https://github.com/diasdavid/)
## Lead Maintainer ## Lead Maintainer
@ -135,45 +135,49 @@ List of packages currently in existence for libp2p
| Package | Version | Deps | CI | Coverage | Lead Maintainer | | Package | Version | Deps | CI | Coverage | Lead Maintainer |
| ---------|---------|---------|---------|---------|--------- | | ---------|---------|---------|---------|---------|--------- |
| **libp2p** | | **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/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`](//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/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`](//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/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-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/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) | | [`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/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) | | [`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** | | **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/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-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-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/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-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-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/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-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-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/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-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** | | **secure channels** |
| [`libp2p-noise`](//github.com/NodeFactoryIo/js-libp2p-noise) | [![npm](https://img.shields.io/npm/v/libp2p-noise.svg?maxAge=86400&style=flat-square)](//github.com/NodeFactoryIo/js-libp2p-noise/releases) | [![Deps](https://david-dm.org/NodeFactoryIo/js-libp2p-noise.svg?style=flat-square)](https://david-dm.org/NodeFactoryIo/js-libp2p-noise) | [![Travis CI](https://flat.badgen.net/travis/NodeFactoryIo/js-libp2p-noise/master)](https://travis-ci.com/NodeFactoryIo/js-libp2p-noise) | [![codecov](https://codecov.io/gh/NodeFactoryIo/js-libp2p-noise/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/NodeFactoryIo/js-libp2p-noise) | N/A | | [`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) |
| [`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/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** | | **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/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-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** | | **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/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-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/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-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/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-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-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/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-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) |
| [`@chainsafe/discv5`](//github.com/ChainSafe/discv5) | [![npm](https://img.shields.io/npm/v/@chainsafe/discv5.svg?maxAge=86400&style=flat-square)](//github.com/ChainSafe/discv5/releases) | [![Deps](https://david-dm.org/ChainSafe/discv5.svg?style=flat-square)](https://david-dm.org/ChainSafe/discv5) | [![Travis CI](https://flat.badgen.net/travis/ChainSafe/discv5/master)](https://travis-ci.com/ChainSafe/discv5) | [![codecov](https://codecov.io/gh/ChainSafe/discv5/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ChainSafe/discv5) | [Cayman Nava](mailto:caymannava@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** | | **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/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-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/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-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** | | **peer routing** |
| [`libp2p-delegated-peer-routing`](//github.com/libp2p/js-libp2p-delegated-peer-routing) | [![npm](https://img.shields.io/npm/v/libp2p-delegated-peer-routing.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-delegated-peer-routing/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-delegated-peer-routing.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-delegated-peer-routing) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-delegated-peer-routing/master)](https://travis-ci.com/libp2p/js-libp2p-delegated-peer-routing) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-delegated-peer-routing/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-delegated-peer-routing) | [Jacob Heun](mailto:jacobheun@gmail.com) | | [`libp2p-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/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-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** | | **utilities** |
| [`libp2p-crypto`](//github.com/libp2p/js-libp2p-crypto) | [![npm](https://img.shields.io/npm/v/libp2p-crypto.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-crypto/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-crypto.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-crypto) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-crypto/master)](https://travis-ci.com/libp2p/js-libp2p-crypto) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-crypto/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-crypto) | [Jacob Heun](mailto:jacobheun@gmail.com) | | [`libp2p-crypto`](//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/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) | | [`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** | | **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/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-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** | | **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/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-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/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-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/js-libp2p-gossipsub) | [![npm](https://img.shields.io/npm/v/libp2p-gossipsub.svg?maxAge=86400&style=flat-square)](//github.com/ChainSafe/js-libp2p-gossipsub/releases) | [![Deps](https://david-dm.org/ChainSafe/js-libp2p-gossipsub.svg?style=flat-square)](https://david-dm.org/ChainSafe/js-libp2p-gossipsub) | [![Travis CI](https://flat.badgen.net/travis/ChainSafe/js-libp2p-gossipsub/master)](https://travis-ci.com/ChainSafe/js-libp2p-gossipsub) | [![codecov](https://codecov.io/gh/ChainSafe/js-libp2p-gossipsub/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ChainSafe/js-libp2p-gossipsub) | [Cayman Nava](mailto:caymannava@gmail.com) | | [`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** | | **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/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-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/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-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) |
## Contribute ## Contribute

1205
doc/API.md

File diff suppressed because it is too large Load Diff

View File

@ -20,12 +20,9 @@
- [Customizing DHT](#customizing-dht) - [Customizing DHT](#customizing-dht)
- [Setup with Content and Peer Routing](#setup-with-content-and-peer-routing) - [Setup with Content and Peer Routing](#setup-with-content-and-peer-routing)
- [Setup with Relay](#setup-with-relay) - [Setup with Relay](#setup-with-relay)
- [Setup with Keychain](#setup-with-keychain)
- [Configuring Dialing](#configuring-dialing) - [Configuring Dialing](#configuring-dialing)
- [Configuring Connection Manager](#configuring-connection-manager) - [Configuring Connection Manager](#configuring-connection-manager)
- [Configuring Transport Manager](#configuring-transport-manager)
- [Configuring Metrics](#configuring-metrics) - [Configuring Metrics](#configuring-metrics)
- [Configuring PeerStore](#configuring-peerstore)
- [Customizing Transports](#customizing-transports) - [Customizing Transports](#customizing-transports)
- [Configuration examples](#configuration-examples) - [Configuration examples](#configuration-examples)
@ -52,7 +49,7 @@ The libp2p ecosystem contains at least one module for each of these subsystems.
After selecting the modules to use, it is also possible to configure each one according to your needs. After selecting the modules to use, it is also possible to configure each one according to your needs.
Bear in mind that a **transport** and **connection encryption** are **required**, while all the other subsystems are optional. Bear in mind that only a **transport** and **connection encryption** are required, while all the other subsystems are optional.
### Transport ### Transport
@ -97,7 +94,6 @@ If you want to know more about libp2p stream multiplexing, you should read the f
Some available connection encryption protocols: Some available connection encryption protocols:
- [NodeFactoryIo/js-libp2p-noise](https://github.com/NodeFactoryIo/js-libp2p-noise)
- [libp2p/js-libp2p-secio](https://github.com/libp2p/js-libp2p-secio) - [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 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).
@ -117,7 +113,6 @@ Some available peer discovery modules are:
- [js-libp2p-bootstrap](https://github.com/libp2p/js-libp2p-bootstrap) - [js-libp2p-bootstrap](https://github.com/libp2p/js-libp2p-bootstrap)
- [js-libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) - [js-libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht)
- [js-libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star) - [js-libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star)
- [discv5](https://github.com/chainsafe/discv5)
**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. **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.
@ -177,7 +172,7 @@ If you want to know more about libp2p DHT, you should read the following content
Some available pubsub routers are: Some available pubsub routers are:
- [libp2p/js-libp2p-floodsub](https://github.com/libp2p/js-libp2p-floodsub) - [libp2p/js-libp2p-floodsub](https://github.com/libp2p/js-libp2p-floodsub)
- [ChainSafe/js-libp2p-gossipsub](https://github.com/ChainSafe/js-libp2p-gossipsub) - [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 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.
@ -208,12 +203,8 @@ Moreover, the majority of the modules can be customized via option parameters. T
Besides the `modules` and `config`, libp2p allows other internal options and configurations: 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. - `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. - This is used in modules such as the DHT. If it is not provided, `js-libp2p` will use an in memory datastore.
- `peerId`: the identity of the node, an instance of [libp2p/js-peer-id](https://github.com/libp2p/js-peer-id). - `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. - 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.
- `addresses`: an object containing `listen`, `announce` and `noAnnounce` properties with `Array<string>`:
- `listen` addresses will be provided to the libp2p underlying transports for listening on them.
- `announce` addresses will be used to compute the advertises that the node should advertise to the network.
- `noAnnounce` addresses will be used as a filter to compute the advertises that the node should advertise to the network.
### Examples ### Examples
@ -378,10 +369,10 @@ const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio') const SECIO = require('libp2p-secio')
const DelegatedPeerRouter = require('libp2p-delegated-peer-routing') const DelegatedPeerRouter = require('libp2p-delegated-peer-routing')
const DelegatedContentRouter = require('libp2p-delegated-content-routing') const DelegatedContentRouter = require('libp2p-delegated-content-routing')
const PeerId = require('peer-id') const PeerInfo = require('peer-info')
// create a peerId // create a peerInfo
const peerId = await PeerId.create() const peerInfo = await PeerInfo.create()
const node = await Libp2p.create({ const node = await Libp2p.create({
modules: { modules: {
@ -389,13 +380,13 @@ const node = await Libp2p.create({
streamMuxer: [MPLEX], streamMuxer: [MPLEX],
connEncryption: [SECIO], connEncryption: [SECIO],
contentRouting: [ contentRouting: [
new DelegatedContentRouter(peerId) new DelegatedContentRouter(peerInfo.id)
], ],
peerRouting: [ peerRouting: [
new DelegatedPeerRouter() new DelegatedPeerRouter()
], ],
}, },
peerId peerInfo
}) })
``` ```
@ -425,48 +416,9 @@ const node = await Libp2p.create({
}) })
``` ```
#### Setup with Keychain
Libp2p allows you to setup a secure keychain to manage your keys. The keychain configuration object should have the following properties:
| Name | Type | Description |
|------|------|-------------|
| pass | `string` | Passphrase to use in the keychain (minimum of 20 characters). |
| datastore | `object` | must implement [ipfs/interface-datastore](https://github.com/ipfs/interface-datastore) |
```js
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const LevelStore = require('datastore-level')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
},
keychain: {
pass: 'notsafepassword123456789',
datastore: new LevelStore('path/to/store')
}
})
await libp2p.loadKeychain()
```
#### Configuring Dialing #### Configuring Dialing
Dialing in libp2p can be configured to limit the rate of dialing, and how long dials are allowed to take. The dialer configuration object should have the following properties: 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.
| Name | Type | Description |
|------|------|-------------|
| maxParallelDials | `number` | How many multiaddrs we can dial in parallel. |
| maxDialsPerPeer | `number` | How many multiaddrs we can dial per peer, in parallel. |
| dialTimeout | `number` | Second dial timeout per peer in ms. |
The below configuration example shows how the dialer should be configured, with the current defaults:
```js ```js
const Libp2p = require('libp2p') const Libp2p = require('libp2p')
@ -481,9 +433,9 @@ const node = await Libp2p.create({
connEncryption: [SECIO] connEncryption: [SECIO]
}, },
dialer: { dialer: {
maxParallelDials: 100, maxParallelDials: 100, // How many multiaddrs we can dial in parallel
maxDialsPerPeer: 4, maxDialsPerPeer: 4, // How many multiaddrs we can dial per peer, in parallel
dialTimeout: 30e3 dialTimeout: 30e3 // 30 second dial timeout per peer
} }
``` ```
@ -518,43 +470,9 @@ const node = await Libp2p.create({
}) })
``` ```
#### Configuring Transport Manager
The Transport Manager is responsible for managing the libp2p transports life cycle. This includes starting listeners for the provided listen addresses, closing these listeners and dialing using the provided transports. By default, if a libp2p node has a list of multiaddrs for listenning on and there are no valid transports for those multiaddrs, libp2p will throw an error on startup and shutdown. However, for some applications it is perfectly acceptable for libp2p nodes to start in dial only mode if all the listen multiaddrs failed. This error tolerance can be enabled as follows:
```js
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { FaultTolerance } = require('libp2p/src/transport-manager')}
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
},
transportManager: {
faultTolerance: FaultTolerance.NO_FATAL
}
})
```
#### Configuring Metrics #### Configuring Metrics
Metrics are disabled in libp2p by default. You can enable and configure them as follows: 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.
| Name | Type | Description |
|------|------|-------------|
| enabled | `boolean` | Enabled metrics collection. |
| computeThrottleMaxQueueSize | `number` | How many messages a stat will queue before processing. |
| computeThrottleTimeout | `number` | Time in milliseconds a stat will wait, after the last item was added, before processing. |
| movingAverageIntervals | `Array<number>` | The moving averages that will be computed. |
| maxOldPeersRetention | `number` | How many disconnected peers we will retain stats for. |
The below configuration example shows how the metrics should be configured. Aside from enabled being `false` by default, the following default configuration options are listed below:
```js ```js
const Libp2p = require('libp2p') const Libp2p = require('libp2p')
@ -570,49 +488,14 @@ const node = await Libp2p.create({
}, },
metrics: { metrics: {
enabled: true, enabled: true,
computeThrottleMaxQueueSize: 1000, computeThrottleMaxQueueSize: 1000, // How many messages a stat will queue before processing
computeThrottleTimeout: 2000, computeThrottleTimeout: 2000, // Time in milliseconds a stat will wait, after the last item was added, before processing
movingAverageIntervals: [ movingAverageIntervals: [ // The moving averages that will be computed
60 * 1000, // 1 minute 60 * 1000, // 1 minute
5 * 60 * 1000, // 5 minutes 5 * 60 * 1000, // 5 minutes
15 * 60 * 1000 // 15 minutes 15 * 60 * 1000 // 15 minutes
], ],
maxOldPeersRetention: 50 maxOldPeersRetention: 50 // How many disconnected peers we will retain stats for
}
})
```
#### Configuring PeerStore
PeerStore persistence is disabled in libp2p by default. You can enable and configure it as follows. Aside from enabled being `false` by default, it will need an implementation of a [datastore](https://github.com/ipfs/interface-datastore). Take into consideration that using the memory datastore will be ineffective for persistence.
The threshold number represents the maximum number of "dirty peers" allowed in the PeerStore, i.e. peers that are not updated in the datastore. In this context, browser nodes should use a threshold of 1, since they might not "stop" properly in several scenarios and the PeerStore might end up with unflushed records when the window is closed.
| Name | Type | Description |
|------|------|-------------|
| persistence | `boolean` | Is persistence enabled. |
| threshold | `number` | Number of dirty peers allowed. |
The below configuration example shows how the PeerStore should be configured. Aside from persistence being `false` by default, the following default configuration options are listed below:
```js
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const LevelStore = require('datastore-level')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
},
datastore: new LevelStore('path/to/store'),
peerStore: {
persistence: true,
threshold: 5
} }
}) })
``` ```
@ -649,6 +532,7 @@ const node = await Libp2p.create({
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: 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-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) - [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)

View File

@ -69,23 +69,23 @@ If you want to know more about libp2p transports, you should read the following
Encryption is an important part of communicating on the libp2p network. Every connection must be encrypted to help ensure security for everyone. As such, Connection Encryption (Crypto) is a required component of libp2p. Encryption is an important part of communicating on the libp2p network. Every connection must be encrypted to help ensure security for everyone. As such, Connection Encryption (Crypto) is a required component of libp2p.
There are a growing number of Crypto modules being developed for libp2p. As those are released they will be tracked in the [Connection Encryption section of the configuration readme](./CONFIGURATION.md#connection-encryption). For now, we are going to configure our node to use the `libp2p-noise` module. There are a growing number of Crypto modules being developed for libp2p. As those are released they will be tracked in the [Connection Encryption section of the configuration readme](./CONFIGURATION.md#connection-encryption). For now, we are going to configure our node to use the `libp2p-secio` module, which is widely supported across the various libp2p implementations.
```sh ```sh
npm install libp2p-noise npm install libp2p-secio
``` ```
With `libp2p-noise` installed, we can add it to our existing configuration by importing it and adding it to the `modules.connEncryption` array: With `libp2p-secio` installed, we can add it to our existing configuration by importing it and adding it to the `modules.connEncryption` array:
```js ```js
const Libp2p = require('libp2p') const Libp2p = require('libp2p')
const WebSockets = require('libp2p-websockets') const WebSockets = require('libp2p-websockets')
const { NOISE } = require('libp2p-noise') const SECIO = require('libp2p-secio')
const node = await Libp2p.create({ const node = await Libp2p.create({
modules: { modules: {
transport: [WebSockets], transport: [WebSockets],
connEncryption: [NOISE] connEncryption: [SECIO]
} }
}) })
``` ```
@ -143,9 +143,6 @@ const SECIO = require('libp2p-secio')
const MPLEX = require('libp2p-mplex') const MPLEX = require('libp2p-mplex')
const node = await Libp2p.create({ const node = await Libp2p.create({
addresses: {
listen: ['/ip4/127.0.0.1/tcp/8000/ws']
},
modules: { modules: {
transport: [WebSockets], transport: [WebSockets],
connEncryption: [SECIO], connEncryption: [SECIO],
@ -157,12 +154,6 @@ const node = await Libp2p.create({
await node.start() await node.start()
console.log('libp2p has started') console.log('libp2p has started')
const listenAddrs = node.transportManager.getAddrs()
console.log('libp2p is listening on the following addresses: ', listenAddrs)
const advertiseAddrs = node.multiaddrs
console.log('libp2p is advertising the following addresses: ', advertiseAddrs)
// stop libp2p // stop libp2p
await node.stop() await node.stop()
console.log('libp2p has stopped') console.log('libp2p has stopped')

View File

@ -1,4 +1,4 @@
# Migrating to the libp2p@0.27 API # Migrating to the new API
A migration guide for refactoring your application code from libp2p v0.26.x to v0.27.0. A migration guide for refactoring your application code from libp2p v0.26.x to v0.27.0.

View File

@ -1,327 +0,0 @@
# Migrating to the libp2p@0.28 API
A migration guide for refactoring your application code from libp2p v0.27.x to v0.28.0.
## Table of Contents
- [PeerStore API](#peerstore-api)
- [Migrating from Peer Info](#migrating-from-peer-info)
- [Create](#create)
- [API Implications](#api-implications)
- [Connection Manager and Registrar](#connection-manager-and-registrar)
- [Events](#events)
## PeerStore API
In `libp2p@0.27` we integrated the PeerStore (former [peer-book](https://github.com/libp2p/js-peer-book)) into the codebase. By that time, it was not documented in the [API DOC](../API.md) since it kept the same API as the `peer-book` and it was expected to be completelly rewritten in `libp2p@0.28`.
Moving towards a separation of concerns regarding known peers' data, as well as enabling PeerStore persistence, the PeerStore is now divided into four main components: `AddressBook`, `ProtoBook`, `KeyBook` and `MetadataBook`. This resulted in API changes in the PeerStore, since each type of peer data should now be added in an atomic fashion.
### Adding a Peer
**Before**
```js
const peerId = ...
const peerInfo = new PeerInfo(peerId)
peerInfo.protocols.add('/ping/1.0.0')
peerInfo.protocols.add('/ping/2.0.0')
peerInfo.multiaddrs.add('/ip4/127.0.0.1/tcp/0')
libp2p.peerStore.put(peerInfo)
```
**After**
```js
const peerId = ...
const protocols = ['/ping/1.0.0', 'ping/2.0.0']
const multiaddrs = ['/ip4/127.0.0.1/tcp/0']
libp2p.peerStore.protoBook.add(peerId, protocols)
libp2p.peerStore.addressBook.add(peerId, multiaddrs)
```
### Getting a Peer
**Before**
```js
const peerId = ...
const peerInfo = libp2p.peerStore.get(peerId)
// { id: PeerId, multiaddrs: MultiaddrSet, protocols: Set<string>}
```
**After**
```js
const peerId = ...
const peer = libp2p.peerStore.get(peerId)
// { id: PeerId, addresses: Array<{ multiaddr: Multiaddr }>, protocols: Array<string> }
```
### Checking for a Peer
**Before**
```js
const peerId = ...
const hasData = libp2p.peerStore.has(peerId)
```
**After**
```js
const peerId = ...
const hasData = Boolean(libp2p.peerStore.get(peerId))
```
### Removing a Peer
**Before**
```js
libp2p.peerStore.remove(peerId)
```
**After**
```js
// Atomic
libp2p.peerStore.protoBook.delete(peerId)
libp2p.peerStore.addressBook.delete(peerId)
// Remove the peer and ALL of its associated data
libp2p.peerStore.delete(peerId)
```
### Get all known Peers
**Before**
```js
const peers = libp2p.peerStore.peers
// Map<string, PeerInfo>
```
**After**
```js
const peers = libp2p.peerStore.peers
// Similar to libp2p.peerStore.get()
// Map<string, { id: PeerId, addresses: Array<{ multiaddr: Multiaddr }>, protocols: Array<string> }
```
## Migrating from Peer Info
[`PeerInfo`][peer-info] is a libp2p peer abstraction layer that combines a [`PeerId`][peer-id] with known data of the peer, namely its multiaddrs and protocols. It has been used for a long time by `js-libp2p` and its modules to carry this data around the libp2p stack, as well as by the libp2p API, both for providing this data to the users or to receive it from them.
Since this PeerInfo instances were navigating through the entire codebases, some data inconsistencies could be observed in libp2p. Different libp2p subsystems were running with different visions of the known peers data. For instance, a libp2p subsystem receives a copy of this instance with the peer multiaddrs and protocols, but if new data of the peer is obtained from other subsystem, it would not be updated on the former. Moreover, considering that several subsystems were modifying the peer data, libp2p had no way to determine the accurate data.
Considering the complete revamp of the libp2p PeerStore towards its second version, the PeerStore now acts as the single source of truth, we do not need to carry [`PeerInfo`][peer-info] instances around. This also solves all the problems stated above, since subsystems will report new observations to the PeerStore.
### Create
While it was possible to create a libp2p node without providing a [`PeerInfo`][peer-info], there were 2 use cases where a [`PeerInfo`][peer-info] was provided when creating a libp2p node.
#### Using an existing PeerId
`libp2p.create` receives a `peerId` property instead of a `peerInfo` property.
**Before**
```js
const peerId = ...
const peerInfo = new PeerInfo(peerId)
const libp2p = await Libp2p.create({
peerInfo
// ...
})
```
**After**
```js
const peerId = ...
const libp2p = await Libp2p.create({
peerId
// ...
})
```
#### Providing listen addresses
**Before**
```js
const peerId = ...
const peerInfo = new PeerInfo(peerId)
peerInfo.multiaddrs.add('/ip4/127.0.0.1/tcp/0')
const libp2p = await Libp2p.create({
peerInfo
// ...
})
await libp2p.start()
```
**After**
```js
const peerId = ...
const libp2p = await Libp2p.create({
peerId,
addresses: {
listen: ['/ip4/127.0.0.1/tcp/0']
}
// ...
})
await libp2p.start()
```
There is also a bonus regarding the peer addresses. `libp2p@0.28` comes with an AddressManager that also allows the configuration of `announce` and `noAnnounce` addresses.
This was possible to achieve before, but in a hacky way by removing or adding addresses to the `peerInfo`, after the node starts.
**Before**
```js
const peerId = ...
const peerInfo = new PeerInfo(peerId)
peerInfo.multiaddrs.add('/ip4/127.0.0.1/tcp/8000')
const libp2p = await Libp2p.create({
peerInfo
// ...
})
await libp2p.start()
peerInfo.multiaddrs.add('/dns4/peer.io') // Announce
peerInfo.multiaddrs.delete('/ip4/127.0.0.1/tcp/8000') // Not announce
```
**After**
```js
const peerId = ...
const libp2p = await Libp2p.create({
peerId,
addresses: {
listen: ['/ip4/127.0.0.1/tcp/8000'],
announce: ['/dns4/peer.io'],
noAnnounce: ['/ip4/127.0.0.1/tcp/8000']
}
// ...
})
await libp2p.start()
```
### API Implications
#### Peer Dialing, Hangup and Ping
`libp2p.dial`, `libp2p.dialProtocol`, `libp2p.hangup` and `libp2p.ping` supported as the target parameter a [`PeerInfo`](peer-info), a [`PeerId`](peer-id), a [`Multiaddr`][multiaddr] and a string representation of the multiaddr. Considering that [`PeerInfo`](peer-info) is being removed from libp2p, all these methods will now support the other 3 possibilities.
There is one relevant aspect to consider with this change. When using a [`PeerId`](peer-id), the PeerStore **MUST** have known addresses for that peer in its AddressBook, so that it can perform the request. This was also true in the past, but it is important pointing it out because it might not be enough to switch from using [`PeerInfo`](peer-info) to [`PeerId`](peer-id). When using a [`PeerInfo`](peer-info), the PeerStore was not required to have the multiaddrs when they existed on the PeerInfo instance.
**Before**
```js
const peerInfo = ... // PeerInfo containing its multiaddrs
const connection = await libp2p.dial(peerInfo)
```
**After**
```js
const peerId = ...
// Known multiaddrs should be added to the PeerStore
libp2p.peerStore.addressBook.add(peerId, multiaddrs)
const connection = await libp2p.dial(peerId)
```
#### Content Routing and Peer Routing
Both [content-routing](https://github.com/libp2p/js-libp2p-interfaces/tree/master/src/content-routing) and [peer-routing](https://github.com/libp2p/js-libp2p-interfaces/tree/master/src/peer-routing) interfaces were modified to not return a ['PeerInfo'][peer-info] instance.
**Before**
```js
for await (const peerInfo of libp2p.contentRouting.findProviders(cid)) {
// peerInfo is a PeerInfo instance
}
```
**After**
```js
for await (const peer of libp2p.contentRouting.findProviders(cid)) {
// { id: PeerId, multiaddrs: Multiaddr[] }
}
```
**Before**
```js
const peerInfo = await libp2p.peerRouting.findPeer(peerId)
// peerInfo is a PeerInfo instance
```
**After**
```js
const peer = await libp2p.peerRouting.findPeer(peerId)
// { id: PeerId, multiaddrs: Multiaddr[] }
```
## Connection Manager and Registrar
Registrar was introduced in `libp2p@0.27` along with [libp2p topologies](https://github.com/libp2p/js-libp2p-interfaces/tree/master/src/topology). `Registrar` and `ConnectionManager` were both listening on new connections and keeping their record of the open connections with other peers.
The registrar API was not documented in the [API DOC](../API.md). However, it exposed a useful method for some libp2p users, `libp2p.registrar.getConnection()`. On the other hand, the connection Manager did not provide any methods to access its stored connections. On `libp2p@0.28` we removed this data duplication and the connections are handled solely by the `ConnectionManager`.
**Before**
```js
const connection = libp2p.registrar.getConnection(peerId)
```
**After**
```js
const connection = libp2p.connectionManager.get(peerId)
```
## Events
### Connection Events
Libp2p emits events whenever new connections are established. These emitted events previously providing the [`PeerInfo`](peer-info) of the peer that connected. In `libp2p@0.28` these events are now emitted from the Connection Manager and will now emit the [`connection`](connection) itself.
**Before**
```js
libp2p.on('peer:connect', (peerInfo) => {
// PeerInfo instance
})
libp2p.on('peer:disconnect', (peerInfo) => {
// PeerInfo instance
})
```
**After**
```js
libp2p.connectionManager.on('peer:connect', (connection) => {
// Connection instance
})
libp2p.connectionManager.on('peer:disconnect', (connection) => {
// Connection instance
})
```
### Peer Discovery
**Before**
```js
libp2p.on('peer:discovery', (peerInfo) => {
// PeerInfo instance
})
```
**After**
```js
libp2p.on('peer:discovery', (peerId) => {
// peerId instance
})
```
[connection]: https://github.com/libp2p/js-interfaces/tree/master/src/connection
[multiaddr]: https://github.com/multiformats/js-multiaddr
[peer-id]: https://github.com/libp2p/js-peer-id
[peer-info]: https://github.com/libp2p/js-peer-info

View File

@ -2,7 +2,7 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
const PeerId = require('peer-id') const PeerId = require('peer-id')
const multiaddr = require('multiaddr') const PeerInfo = require('peer-info')
const Node = require('./libp2p-bundle') const Node = require('./libp2p-bundle')
const { stdinToStream, streamToConsole } = require('./stream') const { stdinToStream, streamToConsole } = require('./stream')
@ -13,25 +13,27 @@ async function run() {
]) ])
// Create a new libp2p node on localhost with a randomly chosen port // Create a new libp2p node on localhost with a randomly chosen port
const peerDialer = new PeerInfo(idDialer)
peerDialer.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const nodeDialer = new Node({ const nodeDialer = new Node({
peerId: idDialer, peerInfo: peerDialer
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
}
}) })
// Create a PeerInfo with the listening peer's address
const peerListener = new PeerInfo(idListener)
peerListener.multiaddrs.add('/ip4/127.0.0.1/tcp/10333')
// Start the libp2p host // Start the libp2p host
await nodeDialer.start() await nodeDialer.start()
// Output this node's address // Output this node's address
console.log('Dialer ready, listening on:') console.log('Dialer ready, listening on:')
nodeDialer.multiaddrs.forEach((ma) => { peerListener.multiaddrs.forEach((ma) => {
console.log(ma.toString() + '/p2p/' + idDialer.toB58String()) console.log(ma.toString() + '/p2p/' + idListener.toB58String())
}) })
// Dial to the remote peer (the "listener") // Dial to the remote peer (the "listener")
const listenerMa = multiaddr(`/ip4/127.0.0.1/tcp/10333/p2p/${idListener.toB58String()}`) const { stream } = await nodeDialer.dialProtocol(peerListener, '/chat/1.0.0')
const { stream } = await nodeDialer.dialProtocol(listenerMa, '/chat/1.0.0')
console.log('Dialer dialed to listener on protocol: /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') console.log('Type a message and see what happens')

View File

@ -3,8 +3,7 @@
const TCP = require('libp2p-tcp') const TCP = require('libp2p-tcp')
const WS = require('libp2p-websockets') const WS = require('libp2p-websockets')
const mplex = require('libp2p-mplex') const mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio') const secio = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const defaultsDeep = require('@nodeutils/defaults-deep') const defaultsDeep = require('@nodeutils/defaults-deep')
const libp2p = require('../../..') const libp2p = require('../../..')
@ -17,7 +16,7 @@ class Node extends libp2p {
WS WS
], ],
streamMuxer: [ mplex ], streamMuxer: [ mplex ],
connEncryption: [ NOISE, SECIO ] connEncryption: [ secio ]
} }
} }

View File

@ -1,24 +1,23 @@
'use strict' 'use strict'
/* eslint-disable no-console */ /* eslint-disable no-console */
const multaddr = require('multiaddr')
const PeerId = require('peer-id') const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const Node = require('./libp2p-bundle.js') const Node = require('./libp2p-bundle.js')
const { stdinToStream, streamToConsole } = require('./stream') const { stdinToStream, streamToConsole } = require('./stream')
async function run() { async function run() {
// Create a new libp2p node with the given multi-address // Create a new libp2p node with the given multi-address
const idListener = await PeerId.createFromJSON(require('./peer-id-listener')) const idListener = await PeerId.createFromJSON(require('./peer-id-listener'))
const peerListener = new PeerInfo(idListener)
peerListener.multiaddrs.add('/ip4/0.0.0.0/tcp/10333')
const nodeListener = new Node({ const nodeListener = new Node({
peerId: idListener, peerInfo: peerListener
addresses: {
listen: ['/ip4/0.0.0.0/tcp/10333']
}
}) })
// Log a message when a remote peer connects to us // Log a message when a remote peer connects to us
nodeListener.connectionManager.on('peer:connect', (connection) => { nodeListener.on('peer:connect', (peerInfo) => {
console.log('connected to: ', connection.remotePeer.toB58String()) console.log(peerInfo.id.toB58String())
}) })
// Handle messages for the protocol // Handle messages for the protocol
@ -34,7 +33,7 @@ async function run() {
// Output listen addresses to the console // Output listen addresses to the console
console.log('Listener ready, listening on:') console.log('Listener ready, listening on:')
nodeListener.multiaddrs.forEach((ma) => { peerListener.multiaddrs.forEach((ma) => {
console.log(ma.toString() + '/p2p/' + idListener.toB58String()) console.log(ma.toString() + '/p2p/' + idListener.toB58String())
}) })
} }

View File

@ -1,7 +1,3 @@
❗❗Outdated: This example is still not refactored with the `0.27.*` release.
WIP on [libp2p/js-libp2p#507](https://github.com/libp2p/js-libp2p/pull/507)
======
# Delegated Routing with Libp2p and IPFS # Delegated Routing with Libp2p and IPFS
This example shows how to use delegated peer and content routing. The [Peer and Content Routing Example](../peer-and-content-routing) focuses This example shows how to use delegated peer and content routing. The [Peer and Content Routing Example](../peer-and-content-routing) focuses

View File

@ -5,7 +5,6 @@ const Libp2p = require('../../')
const TCP = require('libp2p-tcp') const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex') const Mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio') const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const Bootstrap = require('libp2p-bootstrap') const Bootstrap = require('libp2p-bootstrap')
// Find this list at: https://github.com/ipfs/js-ipfs/blob/master/src/core/runtime/config-nodejs.json // Find this list at: https://github.com/ipfs/js-ipfs/blob/master/src/core/runtime/config-nodejs.json
@ -23,13 +22,10 @@ const bootstrapers = [
;(async () => { ;(async () => {
const node = await Libp2p.create({ const node = await Libp2p.create({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: { modules: {
transport: [TCP], transport: [TCP],
streamMuxer: [Mplex], streamMuxer: [Mplex],
connEncryption: [NOISE, SECIO], connEncryption: [SECIO],
peerDiscovery: [Bootstrap] peerDiscovery: [Bootstrap]
}, },
config: { config: {
@ -43,13 +39,15 @@ const bootstrapers = [
} }
}) })
node.connectionManager.on('peer:connect', (connection) => { node.peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
console.log('Connection established to:', connection.remotePeer.toB58String()) // Emitted when a peer has been found
node.on('peer:connect', (peer) => {
console.log('Connection established to:', peer.id.toB58String()) // Emitted when a peer has been found
}) })
node.on('peer:discovery', (peerId) => { node.on('peer:discovery', (peer) => {
// No need to dial, autoDial is on // No need to dial, autoDial is on
console.log('Discovered:', peerId.toB58String()) console.log('Discovered:', peer.id.toB58String())
}) })
await node.start() await node.start()

View File

@ -5,29 +5,26 @@ const Libp2p = require('../../')
const TCP = require('libp2p-tcp') const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex') const Mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio') const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const MulticastDNS = require('libp2p-mdns') const MulticastDNS = require('libp2p-mdns')
const createNode = async () => { const createNode = async () => {
const node = await Libp2p.create({ const node = await Libp2p.create({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: { modules: {
transport: [TCP], transport: [TCP],
streamMuxer: [Mplex], streamMuxer: [Mplex],
connEncryption: [NOISE, SECIO], connEncryption: [SECIO],
peerDiscovery: [MulticastDNS] peerDiscovery: [MulticastDNS]
}, },
config: { config: {
peerDiscovery: { peerDiscovery: {
[MulticastDNS.tag]: { mdns: {
interval: 20e3, interval: 20e3,
enabled: true enabled: true
} }
} }
} }
}) })
node.peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
return node return node
} }
@ -38,8 +35,8 @@ const createNode = async () => {
createNode() createNode()
]) ])
node1.on('peer:discovery', (peerId) => console.log('Discovered:', peerId.toB58String())) node1.on('peer:discovery', (peer) => console.log('Discovered:', peer.id.toB58String()))
node2.on('peer:discovery', (peerId) => console.log('Discovered:', peerId.toB58String())) node2.on('peer:discovery', (peer) => console.log('Discovered:', peer.id.toB58String()))
await Promise.all([ await Promise.all([
node1.start(), node1.start(),

View File

@ -4,7 +4,7 @@ A Peer Discovery module enables libp2p to find peers to connect to. Think of the
With these system, a libp2p node can both have a set of nodes to always connect on boot (bootstraper nodes), discover nodes through locality (e.g connected in the same LAN) or through serendipity (random walks on a DHT). With these system, a libp2p node can both have a set of nodes to always connect on boot (bootstraper nodes), discover nodes through locality (e.g connected in the same LAN) or through serendipity (random walks on a DHT).
These mechanisms save configuration and enable a node to operate without any explicit dials, it will just work. Once new peers are discovered, their known data is stored in the peer's PeerStore. These mechanisms save configuration and enable a node to operate without any explicit dials, it will just work.
## 1. Bootstrap list of Peers when booting a node ## 1. Bootstrap list of Peers when booting a node
@ -20,7 +20,7 @@ const node = Libp2p.create({
modules: { modules: {
transport: [ TCP ], transport: [ TCP ],
streamMuxer: [ Mplex ], streamMuxer: [ Mplex ],
connEncryption: [ NOISE, SECIO ], connEncryption: [ SECIO ],
peerDiscovery: [ Bootstrap ] peerDiscovery: [ Bootstrap ]
}, },
config: { config: {
@ -55,14 +55,11 @@ Now, once we create and start the node, we can listen for events such as `peer:d
```JavaScript ```JavaScript
const node = await Libp2p.create({ const node = await Libp2p.create({
peerId, peerInfo,
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
}
modules: { modules: {
transport: [ TCP ], transport: [ TCP ],
streamMuxer: [ Mplex ], streamMuxer: [ Mplex ],
connEncryption: [ NOISE, SECIO ], connEncryption: [ SECIO ],
peerDiscovery: [ Bootstrap ] peerDiscovery: [ Bootstrap ]
}, },
config: { config: {
@ -76,13 +73,15 @@ const node = await Libp2p.create({
} }
}) })
node.connectionManager.on('peer:connect', (connection) => { node.peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
console.log('Connection established to:', connection.remotePeer.toB58String()) // Emitted when a new connection has been created
node.on('peer:connect', (peer) => {
console.log('Connection established to:', peer.id.toB58String()) // Emitted when a peer has been found
}) })
node.on('peer:discovery', (peerId) => { // Emitted when a peer has been found
// No need to dial, autoDial is on node.on('peer:discovery', (peer) => {
console.log('Discovered:', peerId.toB58String()) console.log('Discovered:', peer.id.toB58String())
}) })
await node.start() await node.start()
@ -101,15 +100,6 @@ Discovered: QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64
Discovered: QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd Discovered: QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd
Discovered: QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3 Discovered: QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3
Discovered: QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx Discovered: QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx
Connection established to: QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ
Connection established to: QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z
Connection established to: QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM
Connection established to: QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm
Connection established to: QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu
Connection established to: QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64
Connection established to: QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd
Connection established to: QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3
Connection established to: QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx
``` ```
## 2. MulticastDNS to find other peers in the network ## 2. MulticastDNS to find other peers in the network
@ -124,13 +114,10 @@ const MulticastDNS = require('libp2p-mdns')
const createNode = () => { const createNode = () => {
return Libp2p.create({ return Libp2p.create({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
}
modules: { modules: {
transport: [ TCP ], transport: [ TCP ],
streamMuxer: [ Mplex ], streamMuxer: [ Mplex ],
connEncryption: [ NOISE, SECIO ], connEncryption: [ SECIO ],
peerDiscovery: [ MulticastDNS ] peerDiscovery: [ MulticastDNS ]
}, },
config: { config: {

View File

@ -5,8 +5,8 @@
* Dialer Node * Dialer Node
*/ */
const multiaddr = require('multiaddr')
const PeerId = require('peer-id') const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const Node = require('./libp2p-bundle') const Node = require('./libp2p-bundle')
const pipe = require('it-pipe') const pipe = require('it-pipe')
@ -17,26 +17,28 @@ async function run() {
]) ])
// Dialer // Dialer
const dialerPeerInfo = new PeerInfo(dialerId)
dialerPeerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const dialerNode = new Node({ const dialerNode = new Node({
addresses: { peerInfo: dialerPeerInfo
listen: ['/ip4/0.0.0.0/tcp/0']
},
peerId: dialerId
}) })
// Add peer to Dial (the listener) into the PeerStore // Peer to Dial (the listener)
const listenerMultiaddr = '/ip4/127.0.0.1/tcp/10333/p2p/' + listenerId.toB58String() const listenerPeerInfo = new PeerInfo(listenerId)
const listenerMultiaddr = '/ip4/127.0.0.1/tcp/10333/p2p/' +
listenerId.toB58String()
listenerPeerInfo.multiaddrs.add(listenerMultiaddr)
// Start the dialer libp2p node // Start the dialer libp2p node
await dialerNode.start() await dialerNode.start()
console.log('Dialer ready, listening on:') console.log('Dialer ready, listening on:')
dialerNode.multiaddrs.forEach((ma) => console.log(ma.toString() + dialerPeerInfo.multiaddrs.forEach((ma) => console.log(ma.toString() +
'/p2p/' + dialerId.toB58String())) '/p2p/' + dialerId.toB58String()))
// Dial the listener node // Dial the listener node
console.log('Dialing to peer:', listenerMultiaddr) console.log('Dialing to peer:', listenerMultiaddr.toString())
const { stream } = await dialerNode.dialProtocol(listenerMultiaddr, '/echo/1.0.0') const { stream } = await dialerNode.dialProtocol(listenerPeerInfo, '/echo/1.0.0')
console.log('nodeA dialed to nodeB on protocol: /echo/1.0.0') console.log('nodeA dialed to nodeB on protocol: /echo/1.0.0')

View File

@ -3,9 +3,7 @@
const TCP = require('libp2p-tcp') const TCP = require('libp2p-tcp')
const WS = require('libp2p-websockets') const WS = require('libp2p-websockets')
const mplex = require('libp2p-mplex') const mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio') const secio = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const defaultsDeep = require('@nodeutils/defaults-deep') const defaultsDeep = require('@nodeutils/defaults-deep')
const libp2p = require('../../..') const libp2p = require('../../..')
@ -18,7 +16,7 @@ class Node extends libp2p {
WS WS
], ],
streamMuxer: [ mplex ], streamMuxer: [ mplex ],
connEncryption: [ NOISE, SECIO ] connEncryption: [ secio ]
} }
} }

View File

@ -6,6 +6,7 @@
*/ */
const PeerId = require('peer-id') const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const Node = require('./libp2p-bundle') const Node = require('./libp2p-bundle')
const pipe = require('it-pipe') const pipe = require('it-pipe')
@ -13,16 +14,15 @@ async function run() {
const listenerId = await PeerId.createFromJSON(require('./id-l')) const listenerId = await PeerId.createFromJSON(require('./id-l'))
// Listener libp2p node // Listener libp2p node
const listenerPeerInfo = new PeerInfo(listenerId)
listenerPeerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/10333')
const listenerNode = new Node({ const listenerNode = new Node({
addresses: { peerInfo: listenerPeerInfo
listen: ['/ip4/0.0.0.0/tcp/10333']
},
peerId: listenerId
}) })
// Log a message when we receive a connection // Log a message when we receive a connection
listenerNode.connectionManager.on('peer:connect', (connection) => { listenerNode.on('peer:connect', (peerInfo) => {
console.log('received dial to me from:', connection.remotePeer.toB58String()) console.log('received dial to me from:', peerInfo.id.toB58String())
}) })
// Handle incoming connections for the protocol by piping from the stream // Handle incoming connections for the protocol by piping from the stream
@ -33,7 +33,7 @@ async function run() {
await listenerNode.start() await listenerNode.start()
console.log('Listener ready, listening on:') console.log('Listener ready, listening on:')
listenerNode.multiaddrs.forEach((ma) => { listenerNode.peerInfo.multiaddrs.forEach((ma) => {
console.log(ma.toString() + '/p2p/' + listenerId.toB58String()) console.log(ma.toString() + '/p2p/' + listenerId.toB58String())
}) })
} }

View File

@ -3,20 +3,21 @@
const Libp2p = require('../../') const Libp2p = require('../../')
const TCP = require('libp2p-tcp') const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex') const Mplex = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio') const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
const pipe = require('it-pipe') const pipe = require('it-pipe')
const createNode = async () => { const createNode = async () => {
const peerInfo = await PeerInfo.create()
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const node = await Libp2p.create({ const node = await Libp2p.create({
addresses: { peerInfo,
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: { modules: {
transport: [TCP], transport: [TCP],
streamMuxer: [Mplex], streamMuxer: [Mplex],
connEncryption: [NOISE, SECIO] connEncryption: [SECIO]
} }
}) })
@ -31,8 +32,6 @@ const createNode = async () => {
createNode() createNode()
]) ])
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
node2.handle('/a-protocol', ({ stream }) => { node2.handle('/a-protocol', ({ stream }) => {
pipe( pipe(
stream, stream,
@ -44,7 +43,7 @@ const createNode = async () => {
) )
}) })
const { stream } = await node1.dialProtocol(node2.peerId, '/a-protocol') const { stream } = await node1.dialProtocol(node2.peerInfo, '/a-protocol')
await pipe( await pipe(
['This information is sent out encrypted to the other peer'], ['This information is sent out encrypted to the other peer'],

View File

@ -6,15 +6,16 @@ We call this usage a _connection upgrade_ where given a connection between peer
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/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.
# 1. Set up encrypted communications # 1. Set up encrypted communications with SECIO
We will build this example on top of example for [Protocol and Stream Multiplexing](../protocol-and-stream-multiplexing). You will need the modules `libp2p-secio`<sup>*</sup> and `libp2p-noise` to complete it, go ahead and `npm install libp2p-secio libp2p-noise`. We will build this example on top of example for [Protocol and Stream Multiplexing](../protocol-and-stream-multiplexing). You will need the module `libp2p-secio` to complete it, go ahead and `npm install libp2p-secio`.
To add them to your libp2p configuration, all you have to do is: 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:
```JavaScript ```JavaScript
const Libp2p = require('libp2p') const Libp2p = require('libp2p')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio') const SECIO = require('libp2p-secio')
const createNode = () => { const createNode = () => {
@ -23,7 +24,7 @@ const createNode = () => {
transport: [ TCP ], transport: [ TCP ],
streamMuxer: [ Mplex ], streamMuxer: [ Mplex ],
// Attach secio as the crypto channel to use // Attach secio as the crypto channel to use
connEncryption: [ NOISE, SECIO ] connEncryption: [ SECIO ]
} }
}) })
} }
@ -31,8 +32,6 @@ const createNode = () => {
And that's it, from now on, all your libp2p communications are encrypted. Try running the example [1.js](./1.js) to see it working. And that's it, from now on, all your libp2p communications are encrypted. Try running the example [1.js](./1.js) to see it working.
_<sup>*</sup> 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._
If you want to want to learn more about how SECIO works, you can read the [great write up done by Dominic Tarr](https://github.com/auditdrivencrypto/secure-channel/blob/master/prior-art.md#ipfss-secure-channel). If you want to want to learn more about how SECIO works, you can read the [great write up done by Dominic Tarr](https://github.com/auditdrivencrypto/secure-channel/blob/master/prior-art.md#ipfss-secure-channel).
Important note: SECIO hasn't been audited and so, we do not recommend to trust its security. We intent to move to TLS 1.3 once the specification is finalized and an implementation exists that we can use. Important note: SECIO hasn't been audited and so, we do not recommend to trust its security. We intent to move to TLS 1.3 once the specification is finalized and an implementation exists that we can use.

View File

@ -2,7 +2,6 @@ import 'babel-polyfill'
import Libp2p from 'libp2p' import Libp2p from 'libp2p'
import Websockets from 'libp2p-websockets' import Websockets from 'libp2p-websockets'
import WebRTCStar from 'libp2p-webrtc-star' import WebRTCStar from 'libp2p-webrtc-star'
import { NOISE } from 'libp2p-noise'
import Secio from 'libp2p-secio' import Secio from 'libp2p-secio'
import Mplex from 'libp2p-mplex' import Mplex from 'libp2p-mplex'
import Boostrap from 'libp2p-bootstrap' import Boostrap from 'libp2p-bootstrap'
@ -10,15 +9,9 @@ import Boostrap from 'libp2p-bootstrap'
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
// Create our libp2p node // Create our libp2p node
const libp2p = await Libp2p.create({ const libp2p = await Libp2p.create({
addresses: {
// Add the signaling server address, along with our PeerId to our multiaddrs list
// libp2p will automatically attempt to dial to the signaling server so that it can
// receive inbound connections from other peers
listen: ['/ip4/0.0.0.0/tcp/9090/wss/p2p-webrtc-star']
},
modules: { modules: {
transport: [Websockets, WebRTCStar], transport: [Websockets, WebRTCStar],
connEncryption: [NOISE, Secio], connEncryption: [Secio],
streamMuxer: [Mplex], streamMuxer: [Mplex],
peerDiscovery: [Boostrap] peerDiscovery: [Boostrap]
}, },
@ -50,24 +43,30 @@ document.addEventListener('DOMContentLoaded', async () => {
output.textContent += `${txt.trim()}\n` output.textContent += `${txt.trim()}\n`
} }
// Add the signaling server address, along with our PeerId to our multiaddrs list
// libp2p will automatically attempt to dial to the signaling server so that it can
// receive inbound connections from other peers
const webrtcAddr = '/ip4/0.0.0.0/tcp/9090/wss/p2p-webrtc-star'
libp2p.peerInfo.multiaddrs.add(webrtcAddr)
// Listen for new peers // Listen for new peers
libp2p.on('peer:discovery', (peerId) => { libp2p.on('peer:discovery', (peerInfo) => {
log(`Found peer ${peerId.toB58String()}`) log(`Found peer ${peerInfo.id.toB58String()}`)
}) })
// Listen for new connections to peers // Listen for new connections to peers
libp2p.connectionManager.on('peer:connect', (connection) => { libp2p.on('peer:connect', (peerInfo) => {
log(`Connected to ${connection.remotePeer.toB58String()}`) log(`Connected to ${peerInfo.id.toB58String()}`)
}) })
// Listen for peers disconnecting // Listen for peers disconnecting
libp2p.connectionManager.on('peer:disconnect', (connection) => { libp2p.on('peer:disconnect', (peerInfo) => {
log(`Disconnected from ${connection.remotePeer.toB58String()}`) log(`Disconnected from ${peerInfo.id.toB58String()}`)
}) })
await libp2p.start() await libp2p.start()
status.innerText = 'libp2p started!' status.innerText = 'libp2p started!'
log(`libp2p id is ${libp2p.peerId.toB58String()}`) log(`libp2p id is ${libp2p.peerInfo.id.toB58String()}`)
// Export libp2p to the window so you can play with the API // Export libp2p to the window so you can play with the API
window.libp2p = libp2p window.libp2p = libp2p

View File

@ -17,11 +17,10 @@
"dependencies": { "dependencies": {
"@babel/preset-env": "^7.8.3", "@babel/preset-env": "^7.8.3",
"libp2p": "../../", "libp2p": "../../",
"libp2p-bootstrap": "^0.11", "libp2p-bootstrap": "^0.10.3",
"libp2p-mplex": "^0.9.3", "libp2p-mplex": "^0.9.3",
"libp2p-noise": "^1.1.0",
"libp2p-secio": "^0.12.2", "libp2p-secio": "^0.12.2",
"libp2p-webrtc-star": "^0.18.0", "libp2p-webrtc-star": "^0.17.3",
"libp2p-websockets": "^0.13.2" "libp2p-websockets": "^0.13.2"
}, },
"devDependencies": { "devDependencies": {

View File

@ -4,21 +4,22 @@
const Libp2p = require('../../') const Libp2p = require('../../')
const TCP = require('libp2p-tcp') const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex') const Mplex = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio') const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
const KadDHT = require('libp2p-kad-dht') const KadDHT = require('libp2p-kad-dht')
const delay = require('delay') const delay = require('delay')
const createNode = async () => { const createNode = async () => {
const peerInfo = await PeerInfo.create()
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const node = await Libp2p.create({ const node = await Libp2p.create({
addresses: { peerInfo,
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: { modules: {
transport: [TCP], transport: [TCP],
streamMuxer: [Mplex], streamMuxer: [Mplex],
connEncryption: [NOISE, SECIO], connEncryption: [SECIO],
dht: KadDHT dht: KadDHT
}, },
config: { config: {
@ -39,19 +40,16 @@ const createNode = async () => {
createNode() createNode()
]) ])
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
await Promise.all([ await Promise.all([
node1.dial(node2.peerId), node1.dial(node2.peerInfo),
node2.dial(node3.peerId) node2.dial(node3.peerInfo)
]) ])
// The DHT routing tables need a moment to populate // The DHT routing tables need a moment to populate
await delay(100) await delay(100)
const peer = await node1.peerRouting.findPeer(node3.peerId) const peer = await node1.peerRouting.findPeer(node3.peerInfo.id)
console.log('Found it, multiaddrs are:') console.log('Found it, multiaddrs are:')
peer.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${peer.id.toB58String()}`)) peer.multiaddrs.forEach((ma) => console.log(ma.toString()))
})(); })();

View File

@ -5,7 +5,7 @@ const Libp2p = require('../../')
const TCP = require('libp2p-tcp') const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex') const Mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio') const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise') const PeerInfo = require('peer-info')
const CID = require('cids') const CID = require('cids')
const KadDHT = require('libp2p-kad-dht') const KadDHT = require('libp2p-kad-dht')
@ -13,14 +13,15 @@ const all = require('it-all')
const delay = require('delay') const delay = require('delay')
const createNode = async () => { const createNode = async () => {
const peerInfo = await PeerInfo.create()
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const node = await Libp2p.create({ const node = await Libp2p.create({
addresses: { peerInfo,
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: { modules: {
transport: [TCP], transport: [TCP],
streamMuxer: [Mplex], streamMuxer: [Mplex],
connEncryption: [NOISE, SECIO], connEncryption: [SECIO],
dht: KadDHT dht: KadDHT
}, },
config: { config: {
@ -41,21 +42,15 @@ const createNode = async () => {
createNode() createNode()
]) ])
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
await Promise.all([ await Promise.all([
node1.dial(node2.peerId), node1.dial(node2.peerInfo),
node2.dial(node3.peerId) node2.dial(node3.peerInfo)
]) ])
// Wait for onConnect handlers in the DHT
await delay(100)
const cid = new CID('QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL') const cid = new CID('QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL')
await node1.contentRouting.provide(cid) await node1.contentRouting.provide(cid)
console.log('Node %s is providing %s', node1.peerId.toB58String(), cid.toBaseEncodedString()) console.log('Node %s is providing %s', node1.peerInfo.id.toB58String(), cid.toBaseEncodedString())
// wait for propagation // wait for propagation
await delay(300) await delay(300)

View File

@ -17,13 +17,10 @@ const Libp2p = require('libp2p')
const KadDHT = require('libp2p-kad-dht') const KadDHT = require('libp2p-kad-dht')
const node = await Libp2p.create({ const node = await Libp2p.create({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: { modules: {
transport: [ TCP ], transport: [ TCP ],
streamMuxer: [ Mplex ], streamMuxer: [ Mplex ],
connEncryption: [ NOISE, SECIO ], connEncryption: [ SECIO ],
// we add the DHT module that will enable Peer and Content Routing // we add the DHT module that will enable Peer and Content Routing
dht: KadDHT dht: KadDHT
}, },
@ -43,21 +40,18 @@ const node1 = nodes[0]
const node2 = nodes[1] const node2 = nodes[1]
const node3 = nodes[2] const node3 = nodes[2]
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
await Promise.all([ await Promise.all([
node1.dial(node2.peerId), node1.dial(node2.peerInfo),
node2.dial(node3.peerId) node2.dial(node3.peerInfo)
]) ])
// Set up of the cons might take time // Set up of the cons might take time
await delay(100) await delay(100)
const peer = await node1.peerRouting.findPeer(node3.peerId) const peer = await node1.peerRouting.findPeer(node3.peerInfo.id)
console.log('Found it, multiaddrs are:') console.log('Found it, multiaddrs are:')
peer.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${peer.id.toB58String()}`)) peer.multiaddrs.forEach((ma) => console.log(ma.toString()))
``` ```
You should see the output being something like: You should see the output being something like:
@ -65,8 +59,8 @@ You should see the output being something like:
```Bash ```Bash
> node 1.js > node 1.js
Found it, multiaddrs are: Found it, multiaddrs are:
/ip4/127.0.0.1/tcp/63617 /ip4/127.0.0.1/tcp/63617/p2p/QmWrFXvZr9S4iDqycyoyc2zDdrT1jg9wpdenUTdd1LTar6
/ip4/192.168.86.41/tcp/63617 /ip4/192.168.86.41/tcp/63617/p2p/QmWrFXvZr9S4iDqycyoyc2zDdrT1jg9wpdenUTdd1LTar6
``` ```
You have successfully used Peer Routing to find a peer that you were not directly connected. Now all you have to do is to dial to the multiaddrs you discovered. You have successfully used Peer Routing to find a peer that you were not directly connected. Now all you have to do is to dial to the multiaddrs you discovered.
@ -81,7 +75,7 @@ Instead of calling `peerRouting.findPeer`, we will use `contentRouting.provide`
```JavaScript ```JavaScript
await node1.contentRouting.provide(cid) await node1.contentRouting.provide(cid)
console.log('Node %s is providing %s', node1.peerId.toB58String(), cid.toBaseEncodedString()) console.log('Node %s is providing %s', node1.peerInfo.id.toB58String(), cid.toBaseEncodedString())
const provs = await all(node3.contentRouting.findProviders(cid, { timeout: 5000 })) const provs = await all(node3.contentRouting.findProviders(cid, { timeout: 5000 }))

View File

@ -1,7 +1,6 @@
/* eslint no-console: ["off"] */ /* eslint no-console: ["off"] */
'use strict' 'use strict'
const { Buffer } = require('buffer')
const { generate } = require('libp2p/src/pnet') const { generate } = require('libp2p/src/pnet')
const privateLibp2pNode = require('./libp2p-node') const privateLibp2pNode = require('./libp2p-node')
@ -18,7 +17,7 @@ generate(otherSwarmKey)
;(async () => { ;(async () => {
const node1 = await privateLibp2pNode(swarmKey) 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 // 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(swarmKey)
// const node2 = await privateLibp2pNode(otherSwarmKey) // const node2 = await privateLibp2pNode(otherSwarmKey)
@ -29,9 +28,7 @@ generate(otherSwarmKey)
console.log('nodes started...') console.log('nodes started...')
// Add node 2 data to node1's PeerStore await node1.dial(node2.peerInfo)
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
await node1.dial(node2.peerId)
node2.handle('/private', ({ stream }) => { node2.handle('/private', ({ stream }) => {
pipe( pipe(
@ -44,10 +41,10 @@ generate(otherSwarmKey)
) )
}) })
const { stream } = await node1.dialProtocol(node2.peerId, '/private') const { stream } = await node1.dialProtocol(node2.peerInfo, '/private')
await pipe( await pipe(
['This message is sent on a private network'], ['This message is sent on a private network'],
stream stream
) )
})() })();

View File

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

View File

@ -13,7 +13,6 @@
"dependencies": { "dependencies": {
"libp2p": "../..", "libp2p": "../..",
"libp2p-mplex": "^0.9.3", "libp2p-mplex": "^0.9.3",
"libp2p-noise": "^1.1.0",
"libp2p-secio": "^0.12.1", "libp2p-secio": "^0.12.1",
"libp2p-tcp": "^0.14.2" "libp2p-tcp": "^0.14.2"
} }

View File

@ -3,20 +3,21 @@
const Libp2p = require('../../') const Libp2p = require('../../')
const TCP = require('libp2p-tcp') const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex') const MPLEX = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio') const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
const pipe = require('it-pipe') const pipe = require('it-pipe')
const createNode = async () => { const createNode = async () => {
const peerInfo = await PeerInfo.create()
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const node = await Libp2p.create({ const node = await Libp2p.create({
addresses: { peerInfo,
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: { modules: {
transport: [TCP], transport: [TCP],
streamMuxer: [MPLEX], streamMuxer: [MPLEX],
connEncryption: [NOISE, SECIO] connEncryption: [SECIO]
} }
}) })
@ -31,9 +32,6 @@ const createNode = async () => {
createNode() createNode()
]) ])
// Add node's 2 data to the PeerStore
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
// exact matching // exact matching
node2.handle('/your-protocol', ({ stream }) => { node2.handle('/your-protocol', ({ stream }) => {
pipe( pipe(
@ -64,14 +62,14 @@ const createNode = async () => {
}) })
*/ */
const { stream } = await node1.dialProtocol(node2.peerId, ['/your-protocol']) const { stream } = await node1.dialProtocol(node2.peerInfo, ['/your-protocol'])
await pipe( await pipe(
['my own protocol, wow!'], ['my own protocol, wow!'],
stream stream
) )
/* /*
const { stream } = node1.dialProtocol(node2.peerId, ['/another-protocol/1.0.0']) const { stream } = node1.dialProtocol(node2.peerInfo, ['/another-protocol/1.0.0'])
await pipe( await pipe(
['my own protocol, wow!'], ['my own protocol, wow!'],

View File

@ -3,20 +3,21 @@
const Libp2p = require('../../') const Libp2p = require('../../')
const TCP = require('libp2p-tcp') const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex') const MPLEX = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio') const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
const pipe = require('it-pipe') const pipe = require('it-pipe')
const createNode = async () => { const createNode = async () => {
const peerInfo = await PeerInfo.create()
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const node = await Libp2p.create({ const node = await Libp2p.create({
addresses: { peerInfo,
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: { modules: {
transport: [TCP], transport: [TCP],
streamMuxer: [MPLEX], streamMuxer: [MPLEX],
connEncryption: [NOISE, SECIO] connEncryption: [SECIO]
} }
}) })
@ -31,9 +32,6 @@ const createNode = async () => {
createNode() createNode()
]) ])
// Add node's 2 data to the PeerStore
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
node2.handle(['/a', '/b'], ({ protocol, stream }) => { node2.handle(['/a', '/b'], ({ protocol, stream }) => {
pipe( pipe(
stream, stream,
@ -45,19 +43,19 @@ const createNode = async () => {
) )
}) })
const { stream: stream1 } = await node1.dialProtocol(node2.peerId, ['/a']) const { stream: stream1 } = await node1.dialProtocol(node2.peerInfo, ['/a'])
await pipe( await pipe(
['protocol (a)'], ['protocol (a)'],
stream1 stream1
) )
const { stream: stream2 } = await node1.dialProtocol(node2.peerId, ['/b']) const { stream: stream2 } = await node1.dialProtocol(node2.peerInfo, ['/b'])
await pipe( await pipe(
['protocol (b)'], ['protocol (b)'],
stream2 stream2
) )
const { stream: stream3 } = await node1.dialProtocol(node2.peerId, ['/b']) const { stream: stream3 } = await node1.dialProtocol(node2.peerInfo, ['/b'])
await pipe( await pipe(
['another stream on protocol (b)'], ['another stream on protocol (b)'],
stream3 stream3

View File

@ -4,20 +4,21 @@
const Libp2p = require('../../') const Libp2p = require('../../')
const TCP = require('libp2p-tcp') const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex') const MPLEX = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio') const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
const pipe = require('it-pipe') const pipe = require('it-pipe')
const createNode = async () => { const createNode = async () => {
const peerInfo = await PeerInfo.create()
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const node = await Libp2p.create({ const node = await Libp2p.create({
addresses: { peerInfo,
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: { modules: {
transport: [TCP], transport: [TCP],
streamMuxer: [MPLEX], streamMuxer: [MPLEX],
connEncryption: [NOISE, SECIO] connEncryption: [SECIO]
} }
}) })
@ -31,9 +32,6 @@ const createNode = async () => {
createNode(), createNode(),
createNode() createNode()
]) ])
// Add node's 2 data to the PeerStore
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
node1.handle('/node-1', ({ stream }) => { node1.handle('/node-1', ({ stream }) => {
pipe( pipe(
@ -57,13 +55,13 @@ const createNode = async () => {
) )
}) })
const { stream: stream1 } = await node1.dialProtocol(node2.peerId, ['/node-2']) const { stream: stream1 } = await node1.dialProtocol(node2.peerInfo, ['/node-2'])
await pipe( await pipe(
['from 1 to 2'], ['from 1 to 2'],
stream1 stream1
) )
const { stream: stream2 } = await node2.dialProtocol(node1.peerId, ['/node-1']) const { stream: stream2 } = await node2.dialProtocol(node1.peerInfo, ['/node-1'])
await pipe( await pipe(
['from 2 to 1'], ['from 2 to 1'],
stream2 stream2

View File

@ -6,7 +6,7 @@ The feature of agreeing on a protocol over an established connection is what we
# 1. Handle multiple protocols # 1. Handle multiple protocols
Let's see _protocol multiplexing_ in action! You will need the following modules for this example: `libp2p`, `libp2p-tcp`, `peer-id`, `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`, `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).
After creating the nodes, we need to tell libp2p which protocols to handle. After creating the nodes, we need to tell libp2p which protocols to handle.
@ -19,9 +19,6 @@ const { toBuffer } = require('it-buffer')
const node1 = nodes[0] const node1 = nodes[0]
const node2 = nodes[1] const node2 = nodes[1]
// Add node's 2 data to the PeerStore
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
// Here we are telling libp2p that if someone dials this node to talk with the `/your-protocol` // 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 // multicodec, the protocol identifier, please call this handler and give it the stream
// so that incomming data can be handled // so that incomming data can be handled
@ -40,7 +37,7 @@ node2.handle('/your-protocol', ({ stream }) => {
After the protocol is _handled_, now we can dial to it. After the protocol is _handled_, now we can dial to it.
```JavaScript ```JavaScript
const { stream } = await node1.dialProtocol(node2.peerId, ['/your-protocol']) const { stream } = await node1.dialProtocol(node2.peerInfo, ['/your-protocol'])
await pipe( await pipe(
['my own protocol, wow!'], ['my own protocol, wow!'],
@ -62,7 +59,7 @@ node2.handle('/another-protocol/1.0.1', ({ stream }) => {
) )
}) })
// ... // ...
const { stream } = await node1.dialProtocol(node2.peerId, ['/another-protocol/1.0.0']) const { stream } = await node1.dialProtocol(node2.peerInfo, ['/another-protocol/1.0.0'])
await pipe( await pipe(
['my own protocol, wow!'], ['my own protocol, wow!'],
@ -131,19 +128,19 @@ node2.handle(['/a', '/b'], ({ protocol, stream }) => {
) )
}) })
const { stream } = await node1.dialProtocol(node2.peerId, ['/a']) const { stream } = await node1.dialProtocol(node2.peerInfo, ['/a'])
await pipe( await pipe(
['protocol (a)'], ['protocol (a)'],
stream stream
) )
const { stream: stream2 } = await node1.dialProtocol(node2.peerId, ['/b']) const { stream: stream2 } = await node1.dialProtocol(node2.peerInfo, ['/b'])
await pipe( await pipe(
['protocol (b)'], ['protocol (b)'],
stream2 stream2
) )
const { stream: stream3 } = await node1.dialProtocol(node2.peerId, ['/b']) const { stream: stream3 } = await node1.dialProtocol(node2.peerInfo, ['/b'])
await pipe( await pipe(
['another stream on protocol (b)'], ['another stream on protocol (b)'],
stream3 stream3

View File

@ -1,23 +1,23 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
'use strict' 'use strict'
const { Buffer } = require('buffer')
const Libp2p = require('../../') const Libp2p = require('../../')
const TCP = require('libp2p-tcp') const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex') const Mplex = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio') const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
const Gossipsub = require('libp2p-gossipsub') const Gossipsub = require('libp2p-gossipsub')
const createNode = async () => { const createNode = async () => {
const peerInfo = await PeerInfo.create()
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const node = await Libp2p.create({ const node = await Libp2p.create({
addresses: { peerInfo,
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: { modules: {
transport: [TCP], transport: [TCP],
streamMuxer: [Mplex], streamMuxer: [Mplex],
connEncryption: [NOISE, SECIO], connEncryption: [SECIO],
pubsub: Gossipsub pubsub: Gossipsub
} }
}) })
@ -31,12 +31,10 @@ const createNode = async () => {
const [node1, node2] = await Promise.all([ const [node1, node2] = await Promise.all([
createNode(), createNode(),
createNode() createNode(),
]) ])
// Add node's 2 data to the PeerStore await node1.dial(node2.peerInfo)
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
await node1.dial(node2.peerId)
await node1.pubsub.subscribe(topic, (msg) => { await node1.pubsub.subscribe(topic, (msg) => {
console.log(`node1 received: ${msg.data.toString()}`) console.log(`node1 received: ${msg.data.toString()}`)
@ -50,4 +48,4 @@ const createNode = async () => {
setInterval(() => { setInterval(() => {
node2.pubsub.publish(topic, Buffer.from('Bird bird bird, bird is the word!')) node2.pubsub.publish(topic, Buffer.from('Bird bird bird, bird is the word!'))
}, 1000) }, 1000)
})() })();

View File

@ -1,6 +1,6 @@
# Publish Subscribe # Publish Subscribe
Publish Subscribe is also included on the stack. Currently, we have two PubSub implementation available [libp2p-floodsub](https://github.com/libp2p/js-libp2p-floodsub) and [libp2p-gossipsub](https://github.com/ChainSafe/js-libp2p-gossipsub), with many more being researched at [research-pubsub](https://github.com/libp2p/research-pubsub). Publish Subscribe is also included on the stack. Currently, we have two PubSub implementation available [libp2p-floodsub](https://github.com/libp2p/js-libp2p-floodsub) and [libp2p-gossipsub](https://github.com/ChainSafe/gossipsub-js), with many more being researched at [research-pubsub](https://github.com/libp2p/research-pubsub).
We've seen many interesting use cases appear with this, here are some highlights: We've seen many interesting use cases appear with this, here are some highlights:
@ -21,13 +21,10 @@ const Libp2p = require('libp2p')
const Gossipsub = require('libp2p-gossipsub') const Gossipsub = require('libp2p-gossipsub')
const node = await Libp2p.create({ const node = await Libp2p.create({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: { modules: {
transport: [ TCP ], transport: [ TCP ],
streamMuxer: [ Mplex ], streamMuxer: [ Mplex ],
connEncryption: [ NOISE, SECIO ], connEncryption: [ SECIO ],
// we add the Pubsub module we want // we add the Pubsub module we want
pubsub: Gossipsub pubsub: Gossipsub
} }
@ -42,10 +39,7 @@ const topic = 'news'
const node1 = nodes[0] const node1 = nodes[0]
const node2 = nodes[1] const node2 = nodes[1]
// Add node's 2 data to the PeerStore await node1.dial(node2.peerInfo)
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
await node1.dial(node2.peerId)
await node1.pubsub.subscribe(topic, (msg) => { await node1.pubsub.subscribe(topic, (msg) => {
console.log(`node1 received: ${msg.data.toString()}`) console.log(`node1 received: ${msg.data.toString()}`)

View File

@ -3,19 +3,19 @@
const Libp2p = require('../..') const Libp2p = require('../..')
const TCP = require('libp2p-tcp') const TCP = require('libp2p-tcp')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio') const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
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 createNode = async () => {
const node = await Libp2p.create({ const node = await Libp2p.create({
addresses: { peerInfo,
// To signall the addresses we want to be available, we use
// the multiaddr format, a self describable address
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: { modules: {
transport: [TCP], transport: [TCP],
connEncryption: [NOISE, SECIO] connEncryption: [SECIO]
} }
}) })
@ -24,9 +24,10 @@ const createNode = async () => {
} }
;(async () => { ;(async () => {
const node = await createNode() const peerInfo = await PeerInfo.create()
const node = await createNode(peerInfo)
console.log('node has started (true/false):', node.isStarted()) console.log('node has started (true/false):', node.isStarted())
console.log('listening on:') console.log('listening on:')
node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`)) node.peerInfo.multiaddrs.forEach((ma) => console.log(ma.toString()))
})(); })();

View File

@ -3,23 +3,23 @@
const Libp2p = require('../..') const Libp2p = require('../..')
const TCP = require('libp2p-tcp') const TCP = require('libp2p-tcp')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio') const SECIO = require('libp2p-secio')
const MPLEX = require('libp2p-mplex') const MPLEX = require('libp2p-mplex')
const PeerInfo = require('peer-info')
const pipe = require('it-pipe') const pipe = require('it-pipe')
const concat = require('it-concat') const concat = require('it-concat')
const createNode = async () => { 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({ const node = await Libp2p.create({
addresses: { peerInfo,
// To signall the addresses we want to be available, we use
// the multiaddr format, a self describable address
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: { modules: {
transport: [TCP], transport: [TCP],
connEncryption: [NOISE, SECIO], connEncryption: [SECIO],
streamMuxer: [MPLEX] streamMuxer: [MPLEX]
} }
}) })
@ -30,13 +30,17 @@ const createNode = async () => {
function printAddrs (node, number) { function printAddrs (node, number) {
console.log('node %s is listening on:', number) console.log('node %s is listening on:', number)
node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`)) node.peerInfo.multiaddrs.forEach((ma) => console.log(ma.toString()))
} }
;(async () => { ;(async () => {
const [peerInfo1, peerInfo2] = await Promise.all([
PeerInfo.create(),
PeerInfo.create()
])
const [node1, node2] = await Promise.all([ const [node1, node2] = await Promise.all([
createNode(), createNode(peerInfo1),
createNode() createNode(peerInfo2)
]) ])
printAddrs(node1, '1') printAddrs(node1, '1')
@ -50,8 +54,7 @@ function printAddrs (node, number) {
console.log(result.toString()) console.log(result.toString())
}) })
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) const { stream } = await node1.dialProtocol(node2.peerInfo, '/print')
const { stream } = await node1.dialProtocol(node2.peerId, '/print')
await pipe( await pipe(
['Hello', ' ', 'p2p', ' ', 'world', '!'], ['Hello', ' ', 'p2p', ' ', 'world', '!'],

View File

@ -4,24 +4,24 @@
const Libp2p = require('../..') const Libp2p = require('../..')
const TCP = require('libp2p-tcp') const TCP = require('libp2p-tcp')
const WebSockets = require('libp2p-websockets') const WebSockets = require('libp2p-websockets')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio') const SECIO = require('libp2p-secio')
const MPLEX = require('libp2p-mplex') const MPLEX = require('libp2p-mplex')
const PeerInfo = require('peer-info')
const pipe = require('it-pipe') const pipe = require('it-pipe')
const createNode = async (transports, addresses = []) => { const createNode = async (peerInfo, transports, multiaddrs = []) => {
if (!Array.isArray(addresses)) { if (!Array.isArray(multiaddrs)) {
addresses = [addresses] multiaddrs = [multiaddrs]
} }
multiaddrs.forEach((addr) => peerInfo.multiaddrs.add(addr))
const node = await Libp2p.create({ const node = await Libp2p.create({
addresses: { peerInfo,
listen: addresses.map((a) => a)
},
modules: { modules: {
transport: transports, transport: transports,
connEncryption: [NOISE, SECIO], connEncryption: [SECIO],
streamMuxer: [MPLEX] streamMuxer: [MPLEX]
} }
}) })
@ -32,7 +32,7 @@ const createNode = async (transports, addresses = []) => {
function printAddrs(node, number) { function printAddrs(node, number) {
console.log('node %s is listening on:', number) console.log('node %s is listening on:', number)
node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`)) node.peerInfo.multiaddrs.forEach((ma) => console.log(ma.toString()))
} }
function print ({ stream }) { function print ({ stream }) {
@ -47,10 +47,15 @@ function print ({ stream }) {
} }
;(async () => { ;(async () => {
const [peerInfo1, peerInfo2, peerInfo3] = await Promise.all([
PeerInfo.create(),
PeerInfo.create(),
PeerInfo.create()
])
const [node1, node2, node3] = await Promise.all([ const [node1, node2, node3] = await Promise.all([
createNode([TCP], '/ip4/0.0.0.0/tcp/0'), createNode(peerInfo1, [TCP], '/ip4/0.0.0.0/tcp/0'),
createNode([TCP, WebSockets], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']), createNode(peerInfo2, [TCP, WebSockets], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']),
createNode([WebSockets], '/ip4/127.0.0.1/tcp/20000/ws') createNode(peerInfo3, [WebSockets], '/ip4/127.0.0.1/tcp/20000/ws')
]) ])
printAddrs(node1, '1') printAddrs(node1, '1')
@ -61,19 +66,15 @@ function print ({ stream }) {
node2.handle('/print', print) node2.handle('/print', print)
node3.handle('/print', print) node3.handle('/print', print)
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
node3.peerStore.addressBook.set(node1.peerId, node1.multiaddrs)
// node 1 (TCP) dials to node 2 (TCP+WebSockets) // node 1 (TCP) dials to node 2 (TCP+WebSockets)
const { stream } = await node1.dialProtocol(node2.peerId, '/print') const { stream } = await node1.dialProtocol(node2.peerInfo, '/print')
await pipe( await pipe(
['node 1 dialed to node 2 successfully'], ['node 1 dialed to node 2 successfully'],
stream stream
) )
// node 2 (TCP+WebSockets) dials to node 2 (WebSockets) // node 2 (TCP+WebSockets) dials to node 2 (WebSockets)
const { stream: stream2 } = await node2.dialProtocol(node3.peerId, '/print') const { stream: stream2 } = await node2.dialProtocol(node3.peerInfo, '/print')
await pipe( await pipe(
['node 2 dialed to node 3 successfully'], ['node 2 dialed to node 3 successfully'],
stream2 stream2
@ -81,7 +82,7 @@ function print ({ stream }) {
// node 3 (listening WebSockets) can dial node 1 (TCP) // node 3 (listening WebSockets) can dial node 1 (TCP)
try { try {
await node3.dialProtocol(node1.peerId, '/print') await node3.dialProtocol(node1.peerInfo, '/print')
} catch (err) { } catch (err) {
console.log('node 3 failed to dial to node 1 with:', err.message) console.log('node 3 failed to dial to node 1 with:', err.message)
} }

View File

@ -25,19 +25,18 @@ First thing is to create our own libp2p node! Insert:
const Libp2p = require('libp2p') const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp') const TCP = require('libp2p-tcp')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio') const SECIO = require('libp2p-secio')
const createNode = async () => { 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({ const node = await Libp2p.create({
addresses: { peerInfo,
// To signall the addresses we want to be available, we use
// the multiaddr format, a self describable address
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: { modules: {
transport: [ TCP ], transport: [ TCP ],
connEncryption: [ NOISE, SECIO ] connEncryption: [ SECIO ]
} }
}) })
@ -49,7 +48,8 @@ const createNode = async () => {
Now that we have a function to create our own libp2p node, let's create a node with it. Now that we have a function to create our own libp2p node, let's create a node with it.
```JavaScript ```JavaScript
const node = await createNode() const peerInfo = await PeerInfo.create()
const node = await createNode(peerInfo)
// At this point the node has started // At this point the node has started
console.log('node has started (true/false):', node.isStarted()) console.log('node has started (true/false):', node.isStarted())
@ -59,7 +59,7 @@ console.log('node has started (true/false):', node.isStarted())
// 0, which means "listen in any network interface and pick // 0, which means "listen in any network interface and pick
// a port for me // a port for me
console.log('listening on:') console.log('listening on:')
node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`)) node.peerInfo.multiaddrs.forEach((ma) => console.log(ma.toString()))
``` ```
Running this should result in something like: Running this should result in something like:
@ -96,7 +96,7 @@ We are going to reuse the `createNode` function from step 1, but this time to ma
```JavaScript ```JavaScript
function printAddrs (node, number) { function printAddrs (node, number) {
console.log('node %s is listening on:', number) console.log('node %s is listening on:', number)
node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`)) node.peerInfo.multiaddrs.forEach((ma) => console.log(ma.toString()))
} }
``` ```
@ -104,6 +104,10 @@ Then,
```js ```js
;(async () => { ;(async () => {
const [peerInfo1, peerInfo2] = await Promise.all([
PeerInfo.create(),
PeerInfo.create()
])
const [node1, node2] = await Promise.all([ const [node1, node2] = await Promise.all([
createNode(), createNode(),
createNode() createNode()
@ -123,8 +127,7 @@ Then,
) )
}) })
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) const { stream } = await node1.dialProtocol(node2.peerInfo, '/print')
const { stream } = await node1.dialProtocol(node2.peerId, '/print')
await pipe( await pipe(
['Hello', ' ', 'p2p', ' ', 'world', '!'], ['Hello', ' ', 'p2p', ' ', 'world', '!'],
@ -163,19 +166,18 @@ We want to create 3 nodes, one with TCP, one with TCP+WebSockets and one with ju
```JavaScript ```JavaScript
// ... // ...
const createNode = async (transports, multiaddrs = []) => { const createNode = async (peerInfo, transports, multiaddrs = []) => {
if (!Array.isArray(multiaddrs)) { if (!Array.isArray(multiaddrs)) {
multiaddrs = [multiaddrs] multiaddrs = [multiaddrs]
} }
multiaddrs.forEach((addr) => peerInfo.multiaddrs.add(addr))
const node = await Libp2p.create({ const node = await Libp2p.create({
addresses: { peerInfo,
listen: multiaddrs.map((a) => multiaddr(a))
},
modules: { modules: {
transport: transports, transport: transports,
connEncryption: [SECIO], connEncryption: [ SECIO ]
streamMuxer: [MPLEX]
} }
}) })
@ -192,10 +194,15 @@ Let's update our flow to create nodes and see how they behave when dialing to ea
const WebSockets = require('libp2p-websockets') const WebSockets = require('libp2p-websockets')
const TCP = require('libp2p-tcp') const TCP = require('libp2p-tcp')
const [peerInfo1, peerInfo2, peerInfo3] = await Promise.all([
PeerInfo.create(),
PeerInfo.create(),
PeerInfo.create()
])
const [node1, node2, node3] = await Promise.all([ const [node1, node2, node3] = await Promise.all([
createNode([TCP], '/ip4/0.0.0.0/tcp/0'), createNode(peerInfo1, [TCP], '/ip4/0.0.0.0/tcp/0'),
createNode([TCP, WebSockets], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']), createNode(peerInfo2, [TCP, WebSockets], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']),
createNode([WebSockets], '/ip4/127.0.0.1/tcp/20000/ws') createNode(peerInfo3, [WebSockets], '/ip4/127.0.0.1/tcp/20000/ws')
]) ])
printAddrs(node1, '1') printAddrs(node1, '1')
@ -206,19 +213,15 @@ node1.handle('/print', print)
node2.handle('/print', print) node2.handle('/print', print)
node3.handle('/print', print) node3.handle('/print', print)
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
node3.peerStore.addressBook.set(node1.peerId, node1.multiaddrs)
// node 1 (TCP) dials to node 2 (TCP+WebSockets) // node 1 (TCP) dials to node 2 (TCP+WebSockets)
const { stream } = await node1.dialProtocol(node2.peerId, '/print') const { stream } = await node1.dialProtocol(node2.peerInfo, '/print')
await pipe( await pipe(
['node 1 dialed to node 2 successfully'], ['node 1 dialed to node 2 successfully'],
stream stream
) )
// node 2 (TCP+WebSockets) dials to node 2 (WebSockets) // node 2 (TCP+WebSockets) dials to node 2 (WebSockets)
const { stream: stream2 } = await node2.dialProtocol(node3.peerId, '/print') const { stream: stream2 } = await node2.dialProtocol(node3.peerInfo, '/print')
await pipe( await pipe(
['node 2 dialed to node 3 successfully'], ['node 2 dialed to node 3 successfully'],
stream2 stream2
@ -226,7 +229,7 @@ await pipe(
// node 3 (WebSockets) attempts to dial to node 1 (TCP) // node 3 (WebSockets) attempts to dial to node 1 (TCP)
try { try {
await node3.dialProtocol(node1.peerId, '/print') await node3.dialProtocol(node1.peerInfo, '/print')
} catch (err) { } catch (err) {
console.log('node 3 failed to dial to node 1 with:', err.message) console.log('node 3 failed to dial to node 1 with:', err.message)
} }

View File

@ -17,23 +17,26 @@
"transports", "transports",
["libp2p/js-libp2p-tcp", "libp2p-tcp"], ["libp2p/js-libp2p-tcp", "libp2p-tcp"],
["libp2p/js-libp2p-utp", "libp2p-utp"],
["libp2p/js-libp2p-webrtc-direct", "libp2p-webrtc-direct"], ["libp2p/js-libp2p-webrtc-direct", "libp2p-webrtc-direct"],
["libp2p/js-libp2p-webrtc-star", "libp2p-webrtc-star"], ["libp2p/js-libp2p-webrtc-star", "libp2p-webrtc-star"],
["libp2p/js-libp2p-websockets", "libp2p-websockets"], ["libp2p/js-libp2p-websockets", "libp2p-websockets"],
["libp2p/js-libp2p-websocket-star", "libp2p-websocket-star"],
"secure channels", "secure channels",
["NodeFactoryIo/js-libp2p-noise", "libp2p-noise"],
["libp2p/js-libp2p-secio", "libp2p-secio"], ["libp2p/js-libp2p-secio", "libp2p-secio"],
"stream multiplexers", "stream multiplexers",
["libp2p/js-libp2p-mplex", "libp2p-mplex"], ["libp2p/js-libp2p-mplex", "libp2p-mplex"],
["libp2p/js-libp2p-spdy", "libp2p-spdy"],
"peer discovery", "peer discovery",
["libp2p/js-libp2p-bootstrap", "libp2p-bootstrap"], ["libp2p/js-libp2p-bootstrap", "libp2p-bootstrap"],
["libp2p/js-libp2p-kad-dht", "libp2p-kad-dht"], ["libp2p/js-libp2p-kad-dht", "libp2p-kad-dht"],
["libp2p/js-libp2p-mdns", "libp2p-mdns"], ["libp2p/js-libp2p-mdns", "libp2p-mdns"],
["libp2p/js-libp2p-rendezvous", "libp2p-rendezvous"],
["libp2p/js-libp2p-webrtc-star", "libp2p-webrtc-star"], ["libp2p/js-libp2p-webrtc-star", "libp2p-webrtc-star"],
["ChainSafe/discv5", "@chainsafe/discv5"], ["libp2p/js-libp2p-websocket-star", "libp2p-websocket-star"],
"content routing", "content routing",
["libp2p/js-libp2p-delegated-content-routing", "libp2p-delegated-content-routing"], ["libp2p/js-libp2p-delegated-content-routing", "libp2p-delegated-content-routing"],
@ -49,11 +52,12 @@
"data types", "data types",
["libp2p/js-peer-id", "peer-id"], ["libp2p/js-peer-id", "peer-id"],
["libp2p/js-peer-info", "peer-info"],
"pubsub", "pubsub",
["libp2p/js-libp2p-pubsub", "libp2p-pubsub"], ["libp2p/js-libp2p-pubsub", "libp2p-pubsub"],
["libp2p/js-libp2p-floodsub", "libp2p-floodsub"], ["libp2p/js-libp2p-floodsub", "libp2p-floodsub"],
["ChainSafe/js-libp2p-gossipsub", "libp2p-gossipsub"], ["ChainSafe/gossipsub-js", "libp2p-gossipsub"],
"extensions", "extensions",
["libp2p/js-libp2p-nat-mgnr", "libp2p-nat-mgnr"], ["libp2p/js-libp2p-nat-mgnr", "libp2p-nat-mgnr"],

View File

@ -1,6 +1,6 @@
{ {
"name": "libp2p", "name": "libp2p",
"version": "0.28.1", "version": "0.27.2",
"description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack",
"leadMaintainer": "Jacob Heun <jacobheun@gmail.com>", "leadMaintainer": "Jacob Heun <jacobheun@gmail.com>",
"main": "src/index.js", "main": "src/index.js",
@ -48,119 +48,122 @@
"class-is": "^1.1.0", "class-is": "^1.1.0",
"debug": "^4.1.1", "debug": "^4.1.1",
"err-code": "^2.0.0", "err-code": "^2.0.0",
"events": "^3.1.0",
"hashlru": "^2.3.0", "hashlru": "^2.3.0",
"interface-datastore": "^1.0.4",
"ipfs-utils": "^2.2.0",
"it-all": "^1.0.1", "it-all": "^1.0.1",
"it-buffer": "^0.1.2", "it-buffer": "^0.1.1",
"it-handshake": "^1.0.1", "it-handshake": "^1.0.1",
"it-length-prefixed": "^3.0.1", "it-length-prefixed": "^3.0.0",
"it-pipe": "^1.1.0", "it-pipe": "^1.1.0",
"it-protocol-buffers": "^0.2.0", "it-protocol-buffers": "^0.2.0",
"libp2p-crypto": "^0.17.6", "latency-monitor": "~0.2.1",
"libp2p-interfaces": "^0.3.0", "libp2p-crypto": "^0.17.1",
"libp2p-utils": "^0.1.2", "libp2p-interfaces": "^0.2.3",
"mafmt": "^7.0.0", "mafmt": "^7.0.0",
"merge-options": "^2.0.0", "merge-options": "^2.0.0",
"moving-average": "^1.0.0", "moving-average": "^1.0.0",
"multiaddr": "^7.4.3", "multiaddr": "^7.2.1",
"multistream-select": "^0.15.0", "multistream-select": "^0.15.0",
"mutable-proxy": "^1.0.0", "mutable-proxy": "^1.0.0",
"node-forge": "^0.9.1", "p-any": "^2.1.0",
"p-any": "^3.0.0",
"p-fifo": "^1.0.0", "p-fifo": "^1.0.0",
"p-settle": "^4.0.1", "p-settle": "^3.1.0",
"peer-id": "^0.13.11", "peer-id": "^0.13.4",
"peer-info": "^0.17.0",
"protons": "^1.0.1", "protons": "^1.0.1",
"retimer": "^2.0.0", "retimer": "^2.0.0",
"sanitize-filename": "^1.6.3",
"streaming-iterables": "^4.1.0",
"timeout-abort-controller": "^1.0.0", "timeout-abort-controller": "^1.0.0",
"xsalsa20": "^1.0.2" "xsalsa20": "^1.0.2"
}, },
"devDependencies": { "devDependencies": {
"@nodeutils/defaults-deep": "^1.1.0", "@nodeutils/defaults-deep": "^1.1.0",
"abortable-iterator": "^3.0.0", "abortable-iterator": "^3.0.0",
"aegir": "^22.0.0", "aegir": "^20.5.1",
"chai": "^4.2.0", "chai": "^4.2.0",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"chai-bytes": "^0.1.2", "cids": "^0.7.1",
"chai-string": "^1.5.0",
"cids": "^0.8.0",
"datastore-fs": "^1.1.0",
"datastore-level": "^1.1.0",
"delay": "^4.3.0", "delay": "^4.3.0",
"dirty-chai": "^2.0.1", "dirty-chai": "^2.0.1",
"interop-libp2p": "^0.1.0",
"ipfs-http-client": "^44.0.0",
"it-concat": "^1.0.0", "it-concat": "^1.0.0",
"it-pair": "^1.0.0", "it-pair": "^1.0.0",
"it-pushable": "^1.4.0", "it-pushable": "^1.4.0",
"level": "^6.0.1", "interop-libp2p": "~0.0.1",
"libp2p-bootstrap": "^0.11.0", "libp2p-bootstrap": "^0.10.3",
"libp2p-delegated-content-routing": "^0.5.0", "libp2p-delegated-content-routing": "^0.4.1",
"libp2p-delegated-peer-routing": "^0.5.0", "libp2p-delegated-peer-routing": "^0.4.0",
"libp2p-floodsub": "^0.21.0", "libp2p-floodsub": "^0.20.0",
"libp2p-gossipsub": "^0.4.0", "libp2p-gossipsub": "^0.2.0",
"libp2p-kad-dht": "^0.19.1", "libp2p-kad-dht": "^0.18.2",
"libp2p-mdns": "^0.14.1", "libp2p-mdns": "^0.13.0",
"libp2p-mplex": "^0.9.5", "libp2p-mplex": "^0.9.1",
"libp2p-noise": "^1.1.1", "libp2p-secio": "^0.12.1",
"libp2p-secio": "^0.12.4",
"libp2p-tcp": "^0.14.1", "libp2p-tcp": "^0.14.1",
"libp2p-webrtc-star": "^0.18.0", "libp2p-webrtc-star": "^0.17.0",
"libp2p-websockets": "^0.13.1", "libp2p-websockets": "^0.13.1",
"multihashes": "^0.4.19", "nock": "^11.7.2",
"nock": "^12.0.3",
"p-defer": "^3.0.0", "p-defer": "^3.0.0",
"p-times": "^3.0.0", "p-times": "^2.1.0",
"p-wait-for": "^3.1.0", "p-wait-for": "^3.1.0",
"promisify-es6": "^1.0.3", "sinon": "^8.1.0",
"rimraf": "^3.0.2", "streaming-iterables": "^4.1.0",
"sinon": "^9.0.2" "wrtc": "^0.4.1"
}, },
"contributors": [ "contributors": [
"David Dias <daviddias.p@gmail.com>", "Aditya Bose <13054902+adbose@users.noreply.github.com>",
"Jacob Heun <jacobheun@gmail.com>", "Alan Shaw <alan.shaw@protocol.ai>",
"Vasco Santos <vasco.santos@moxy.studio>",
"Alan Shaw <alan@tableflip.io>", "Alan Shaw <alan@tableflip.io>",
"Cayman <caymannava@gmail.com>",
"Pedro Teixeira <i@pgte.me>",
"Friedel Ziegelmayer <dignifiedquire@gmail.com>",
"Alex Potsides <alex@achingbrain.net>", "Alex Potsides <alex@achingbrain.net>",
"Maciej Krüger <mkg20001@gmail.com>",
"Hugo Dias <mail@hugodias.me>",
"Volker Mische <volker.mische@gmail.com>",
"dirkmc <dirkmdev@gmail.com>",
"Richard Littauer <richard.littauer@gmail.com>",
"Elven <mon.samuel@qq.com>",
"Andrew Nesbitt <andrewnez@gmail.com>", "Andrew Nesbitt <andrewnez@gmail.com>",
"Giovanni T. Parra <fiatjaf@gmail.com>", "Cayman <caymannava@gmail.com>",
"Ryan Bell <ryan@piing.net>", "Chris Bratlien <chrisbratlien@gmail.com>",
"Thomas Eizinger <thomas@eizinger.io>", "Chris Dostert <chrisdostert@users.noreply.github.com>",
"ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>", "Daijiro Wachi <daijiro.wachi@gmail.com>",
"Didrik Nordström <didrik@betamos.se>", "David Dias <daviddias.p@gmail.com>",
"Joel Gustafson <joelg@mit.edu>", "Didrik Nordström <didrik.nordstrom@gmail.com>",
"Julien Bouquillon <contact@revolunet.com>", "Diogo Silva <fsdiogo@gmail.com>",
"Kevin Kwok <antimatter15@gmail.com>", "Dmitriy Ryajov <dryajov@gmail.com>",
"Florian-Merle <florian.david.merle@gmail.com>", "Elven <mon.samuel@qq.com>",
"Nuno Nogueira <nunofmn@gmail.com>",
"Fei Liu <liu.feiwood@gmail.com>", "Fei Liu <liu.feiwood@gmail.com>",
"Florian-Merle <florian.david.merle@gmail.com>",
"Friedel Ziegelmayer <dignifiedquire@gmail.com>",
"Giovanni T. Parra <fiatjaf@gmail.com>",
"Guy Sviry <32539816+guysv@users.noreply.github.com>",
"Henrique Dias <hacdias@gmail.com>",
"Hugo Dias <mail@hugodias.me>",
"Hugo Dias <hugomrdias@gmail.com>",
"Irakli Gozalishvili <rfobic@gmail.com>",
"Jacob Heun <jacobheun@gmail.com>",
"Joel Gustafson <joelg@mit.edu>",
"John Rees <johnrees@users.noreply.github.com>",
"João Santos <joaosantos15@users.noreply.github.com>",
"Kevin Kwok <antimatter15@gmail.com>",
"Lars Gierth <lgierth@users.noreply.github.com>",
"Maciej Krüger <mkg20001@gmail.com>",
"Marcin Tojek <mtojek@users.noreply.github.com>",
"Nuno Nogueira <nunofmn@gmail.com>",
"Pedro Teixeira <pedro@protocol.ai>",
"Pedro Teixeira <i@pgte.me>",
"RasmusErik Voel Jensen <github@solsort.com>", "RasmusErik Voel Jensen <github@solsort.com>",
"robertkiel <robert.kiel@validitylabs.org>", "Richard Littauer <richard.littauer@gmail.com>",
"Ryan Bell <ryan@piing.net>",
"Soeren <nikorpoulsen@gmail.com>", "Soeren <nikorpoulsen@gmail.com>",
"Sönke Hahn <soenkehahn@gmail.com>", "Sönke Hahn <soenkehahn@gmail.com>",
"Thomas Eizinger <thomas@eizinger.io>",
"Tiago Alves <alvesjtiago@gmail.com>", "Tiago Alves <alvesjtiago@gmail.com>",
"Dmitriy Ryajov <dryajov@gmail.com>", "Vasco Santos <vasco.santos@moxy.studio>",
"Diogo Silva <fsdiogo@gmail.com>", "Vasco Santos <vasco.santos@ua.pt>",
"Volker Mische <volker.mische@gmail.com>",
"Yusef Napora <yusef@napora.org>", "Yusef Napora <yusef@napora.org>",
"Zane Starr <zcstarr@gmail.com>", "Zane Starr <zcstarr@gmail.com>",
"Daijiro Wachi <daijiro.wachi@gmail.com>", "a1300 <a1300@users.noreply.github.com>",
"dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>",
"dirkmc <dirkmdev@gmail.com>",
"ebinks <elizabethjbinks@gmail.com>", "ebinks <elizabethjbinks@gmail.com>",
"Chris Bratlien <chrisbratlien@gmail.com>", "greenkeeperio-bot <support@greenkeeper.io>",
"isan_rivkin <isanrivkin@gmail.com>", "isan_rivkin <isanrivkin@gmail.com>",
"Henrique Dias <hacdias@gmail.com>", "mayerwin <mayerwin@users.noreply.github.com>",
"Irakli Gozalishvili <rfobic@gmail.com>" "phillmac <phillmac@users.noreply.github.com>",
"shresthagrawal <34920931+shresthagrawal@users.noreply.github.com>",
"swedneck <40505480+swedneck@users.noreply.github.com>",
"ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>"
] ]
} }

View File

@ -1,49 +0,0 @@
# Address Manager
The Address manager is responsible for keeping an updated register of the peer's addresses. It includes 3 different types of Addresses: `Listen Addresses`, `Announce Addresses` and `No Announce Addresses`.
These Addresses should be specified in your libp2p [configuration](../../doc/CONFIGURATION.md) when you create your node.
## Listen Addresses
A libp2p node should have a set of listen addresses, which will be used by libp2p underlying transports to listen for dials from other nodes in the network.
Before a libp2p node starts, its configured listen addresses will be passed to the AddressManager, so that during startup the libp2p transports can use them to listen for connections. Accordingly, listen addresses should be specified through the libp2p configuration, in order to have the `AddressManager` created with them.
It is important pointing out that libp2p accepts ephemeral listening addresses. In this context, the provided listen addresses might not be exactly the same as the ones used by the transports. For example TCP may replace `/ip4/0.0.0.0/tcp/0` with something like `/ip4/127.0.0.1/tcp/8989`. As a consequence, libp2p should take into account this when determining its advertised addresses.
## Announce Addresses
In some scenarios, a libp2p node will need to announce addresses that it is not listening on. In other words, Announce Addresses are an amendment to the Listen Addresses that aim to enable other nodes to achieve connectivity to this node.
Scenarios for Announce Addresses include:
- when you setup a libp2p node in your private network at home, but you need to announce your public IP Address to the outside world;
- when you want to announce a DNS address, which maps to your public IP Address.
## No Announce Addresses
While we need to add Announce Addresses to enable peers' connectivity, we should also avoid announcing addresses that will not be reachable. No Announce Addresses should be specified so that they are filtered from the advertised multiaddrs.
As stated in the Listen Addresses section, Listen Addresses might be modified by libp2p transports after the successfully bind to those addresses. Libp2p should also take these changes into account so that they can be matched when No Announce Addresses are being filtered out of the advertised multiaddrs.
## Implementation
When a libp2p node is created, the Address Manager will be populated from the provided addresses through the libp2p configuration. Once the node is started, the Transport Manager component will gather the listen addresses from the Address Manager, so that the libp2p transports can attempt to bind to them.
Libp2p will use the the Address Manager as the source of truth when advertising the peers addresses. After all transports are ready, other libp2p components/subsystems will kickoff, namely the Identify Service and the DHT. Both of them will announce the node addresses to the other peers in the network. The announce and noAnnounce addresses will have an important role here and will be gathered by libp2p to compute its current addresses to advertise everytime it is needed.
## Future Considerations
### Dynamic address modifications
In a future iteration, we can enable these addresses to be modified in runtime. For this, the Address Manager should be responsible for notifying interested subsystems of these changes, through an Event Emitter.
#### Modify Listen Addresses
While adding new addresses to listen on runtime should be trivial, removing a listen address might have bad implications for the node, since all the connections using that listen address will be closed. However, libp2p should provide a mechanism for both adding and removing listen addresses in the future.
Every time a new listen address is added, the Address Manager should emit an event with the new multiaddrs to listen. The Transport Manager should listen to this events and act accordingly.
#### Modify Announce Addresses
When the announce addresses are modified, the Address Manager should emit an event so that other subsystems can act accordingly. For example, libp2p identify service should use the libp2p push protocol to inform other peers about these changes.

View File

@ -1,55 +0,0 @@
'use strict'
const debug = require('debug')
const log = debug('libp2p:addresses')
log.error = debug('libp2p:addresses:error')
const multiaddr = require('multiaddr')
/**
* Responsible for managing this peers addresses.
* Peers can specify their listen, announce and noAnnounce addresses.
* The listen addresses will be used by the libp2p transports to listen for new connections,
* while the announce an noAnnounce addresses will be combined with the listen addresses for
* address adverstising to other peers in the network.
*/
class AddressManager {
/**
* @constructor
* @param {object} [options]
* @param {Array<string>} [options.listen = []] list of multiaddrs string representation to listen.
* @param {Array<string>} [options.announce = []] list of multiaddrs string representation to announce.
* @param {Array<string>} [options.noAnnounce = []] list of multiaddrs string representation to not announce.
*/
constructor ({ listen = [], announce = [], noAnnounce = [] } = {}) {
this.listen = new Set(listen)
this.announce = new Set(announce)
this.noAnnounce = new Set(noAnnounce)
}
/**
* Get peer listen multiaddrs.
* @return {Array<Multiaddr>}
*/
getListenAddrs () {
return Array.from(this.listen).map((a) => multiaddr(a))
}
/**
* Get peer announcing multiaddrs.
* @return {Array<Multiaddr>}
*/
getAnnounceAddrs () {
return Array.from(this.announce).map((a) => multiaddr(a))
}
/**
* Get peer noAnnouncing multiaddrs.
* @return {Array<Multiaddr>}
*/
getNoAnnounceAddrs () {
return Array.from(this.noAnnounce).map((a) => multiaddr(a))
}
}
module.exports = AddressManager

View File

@ -1,9 +1,7 @@
'use strict' 'use strict'
const debug = require('debug') const debug = require('debug')
const log = debug('libp2p:circuit:hop') const PeerInfo = require('peer-info')
log.error = debug('libp2p:circuit:hop:error')
const PeerId = require('peer-id') const PeerId = require('peer-id')
const { validateAddrs } = require('./utils') const { validateAddrs } = require('./utils')
const StreamHandler = require('./stream-handler') const StreamHandler = require('./stream-handler')
@ -16,6 +14,9 @@ const { stop } = require('./stop')
const multicodec = require('./../multicodec') const multicodec = require('./../multicodec')
const log = debug('libp2p:circuit:hop')
log.error = debug('libp2p:circuit:hop:error')
module.exports.handleHop = async function handleHop ({ module.exports.handleHop = async function handleHop ({
connection, connection,
request, request,
@ -41,7 +42,7 @@ module.exports.handleHop = async function handleHop ({
// Get the connection to the destination (stop) peer // Get the connection to the destination (stop) peer
const destinationPeer = new PeerId(request.dstPeer.id) const destinationPeer = new PeerId(request.dstPeer.id)
const destinationConnection = circuit._connectionManager.get(destinationPeer) const destinationConnection = circuit._registrar.getConnection(new PeerInfo(destinationPeer))
if (!destinationConnection && !circuit._options.hop.active) { if (!destinationConnection && !circuit._options.hop.active) {
log('HOP request received but we are not connected to the destination peer') log('HOP request received but we are not connected to the destination peer')
return streamHandler.end({ return streamHandler.end({

View File

@ -3,19 +3,20 @@
const mafmt = require('mafmt') const mafmt = require('mafmt')
const multiaddr = require('multiaddr') const multiaddr = require('multiaddr')
const PeerId = require('peer-id') const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const withIs = require('class-is') const withIs = require('class-is')
const { CircuitRelay: CircuitPB } = require('./protocol') const { CircuitRelay: CircuitPB } = require('./protocol')
const debug = require('debug') const debug = require('debug')
const log = debug('libp2p:circuit') const log = debug('libp2p:circuit')
log.error = debug('libp2p:circuit:error') log.error = debug('libp2p:circuit:error')
const toConnection = require('libp2p-utils/src/stream-to-ma-conn')
const { relay: multicodec } = require('./multicodec') const { relay: multicodec } = require('./multicodec')
const createListener = require('./listener') const createListener = require('./listener')
const { handleCanHop, handleHop, hop } = require('./circuit/hop') const { handleCanHop, handleHop, hop } = require('./circuit/hop')
const { handleStop } = require('./circuit/stop') const { handleStop } = require('./circuit/stop')
const StreamHandler = require('./circuit/stream-handler') const StreamHandler = require('./circuit/stream-handler')
const toConnection = require('./stream-to-conn')
class Circuit { class Circuit {
/** /**
@ -29,11 +30,9 @@ class Circuit {
constructor ({ libp2p, upgrader }) { constructor ({ libp2p, upgrader }) {
this._dialer = libp2p.dialer this._dialer = libp2p.dialer
this._registrar = libp2p.registrar this._registrar = libp2p.registrar
this._connectionManager = libp2p.connectionManager
this._upgrader = upgrader this._upgrader = upgrader
this._options = libp2p._config.relay this._options = libp2p._config.relay
this._libp2p = libp2p this.peerInfo = libp2p.peerInfo
this.peerId = libp2p.peerId
this._registrar.handle(multicodec, this._onProtocol.bind(this)) this._registrar.handle(multicodec, this._onProtocol.bind(this))
} }
@ -108,7 +107,7 @@ class Circuit {
const destinationPeer = PeerId.createFromCID(destinationAddr.getPeerId()) const destinationPeer = PeerId.createFromCID(destinationAddr.getPeerId())
let disconnectOnFailure = false let disconnectOnFailure = false
let relayConnection = this._connectionManager.get(relayPeer) let relayConnection = this._registrar.getConnection(new PeerInfo(relayPeer))
if (!relayConnection) { if (!relayConnection) {
relayConnection = await this._dialer.connectToPeer(relayAddr, options) relayConnection = await this._dialer.connectToPeer(relayAddr, options)
disconnectOnFailure = true disconnectOnFailure = true
@ -121,8 +120,8 @@ class Circuit {
request: { request: {
type: CircuitPB.Type.HOP, type: CircuitPB.Type.HOP,
srcPeer: { srcPeer: {
id: this.peerId.toBytes(), id: this.peerInfo.id.toBytes(),
addrs: this._libp2p.multiaddrs.map(addr => addr.buffer) addrs: this.peerInfo.multiaddrs.toArray().map(addr => addr.buffer)
}, },
dstPeer: { dstPeer: {
id: destinationPeer.toBytes(), id: destinationPeer.toBytes(),
@ -131,7 +130,7 @@ class Circuit {
} }
}) })
const localAddr = relayAddr.encapsulate(`/p2p-circuit/p2p/${this.peerId.toB58String()}`) const localAddr = relayAddr.encapsulate(`/p2p-circuit/p2p/${this.peerInfo.id.toB58String()}`)
const maConn = toConnection({ const maConn = toConnection({
stream: virtualConnection, stream: virtualConnection,
remoteAddr: ma, remoteAddr: ma,

View File

@ -22,7 +22,7 @@ module.exports = (circuit) => {
* @return {void} * @return {void}
*/ */
listener.listen = async (addr) => { listener.listen = async (addr) => {
const addrString = String(addr).split('/p2p-circuit').find(a => a !== '') const [addrString] = String(addr).split('/p2p-circuit').slice(-1)
const relayConn = await circuit._dialer.connectToPeer(multiaddr(addrString)) const relayConn = await circuit._dialer.connectToPeer(multiaddr(addrString))
const relayedAddr = relayConn.remoteAddr.encapsulate('/p2p-circuit') const relayedAddr = relayConn.remoteAddr.encapsulate('/p2p-circuit')

View File

@ -0,0 +1,49 @@
'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

@ -3,20 +3,10 @@
const mergeOptions = require('merge-options') const mergeOptions = require('merge-options')
const Constants = require('./constants') const Constants = require('./constants')
const { FaultTolerance } = require('./transport-manager')
const DefaultConfig = { const DefaultConfig = {
addresses: {
listen: [],
announce: [],
noAnnounce: []
},
connectionManager: { connectionManager: {
minPeers: 25 minPeers: 25
}, },
transportManager: {
faultTolerance: FaultTolerance.FATAL_ALL
},
dialer: { dialer: {
maxParallelDials: Constants.MAX_PARALLEL_DIALS, maxParallelDials: Constants.MAX_PARALLEL_DIALS,
maxDialsPerPeer: Constants.MAX_PER_PEER_DIALS, maxDialsPerPeer: Constants.MAX_PER_PEER_DIALS,
@ -25,10 +15,6 @@ const DefaultConfig = {
metrics: { metrics: {
enabled: false enabled: false
}, },
peerStore: {
persistence: false,
threshold: 5
},
config: { config: {
dht: { dht: {
enabled: false, enabled: false,

View File

@ -1,19 +1,11 @@
'use strict' 'use strict'
const errcode = require('err-code') const assert = require('assert')
const mergeOptions = require('merge-options') const mergeOptions = require('merge-options')
const LatencyMonitor = require('./latency-monitor') const LatencyMonitor = require('latency-monitor').default
const debug = require('debug')('libp2p:connection-manager') const debug = require('debug')('libp2p:connection-manager')
const retimer = require('retimer') const retimer = require('retimer')
const { EventEmitter } = require('events')
const PeerId = require('peer-id')
const {
ERR_INVALID_PARAMETERS
} = require('../errors')
const defaultOptions = { const defaultOptions = {
maxConnections: Infinity, maxConnections: Infinity,
minConnections: 0, minConnections: 0,
@ -26,12 +18,7 @@ const defaultOptions = {
defaultPeerValue: 1 defaultPeerValue: 1
} }
/** class ConnectionManager {
* Responsible for managing known connections.
* @fires ConnectionManager#peer:connect Emitted when a new peer is connected.
* @fires ConnectionManager#peer:disconnect Emitted when a peer is disconnected.
*/
class ConnectionManager extends EventEmitter {
/** /**
* @constructor * @constructor
* @param {Libp2p} libp2p * @param {Libp2p} libp2p
@ -47,50 +34,31 @@ class ConnectionManager extends EventEmitter {
* @param {Number} options.defaultPeerValue The value of the peer. Default=1 * @param {Number} options.defaultPeerValue The value of the peer. Default=1
*/ */
constructor (libp2p, options) { constructor (libp2p, options) {
super()
this._libp2p = libp2p this._libp2p = libp2p
this._peerId = libp2p.peerId.toB58String() this._registrar = libp2p.registrar
this._peerId = libp2p.peerInfo.id.toB58String()
this._options = mergeOptions.call({ ignoreUndefined: true }, defaultOptions, options) this._options = mergeOptions.call({ ignoreUndefined: true }, defaultOptions, options)
if (this._options.maxConnections < this._options.minConnections) { assert(
throw errcode(new Error('Connection Manager maxConnections must be greater than minConnections'), ERR_INVALID_PARAMETERS) this._options.maxConnections > this._options.minConnections,
} 'Connection Manager maxConnections must be greater than minConnections'
)
debug('options: %j', this._options) debug('options: %j', this._options)
this._libp2p = libp2p this._metrics = libp2p.metrics
/**
* Map of peer identifiers to their peer value for pruning connections.
* @type {Map<string, number>}
*/
this._peerValues = new Map() this._peerValues = new Map()
this._connections = new Map()
/**
* Map of connections per peer
* @type {Map<string, Array<conn>>}
*/
this.connections = new Map()
this._timer = null this._timer = null
this._checkMetrics = this._checkMetrics.bind(this) this._checkMetrics = this._checkMetrics.bind(this)
} }
/**
* Get current number of open connections.
*/
get size () {
return Array.from(this.connections.values())
.reduce((accumulator, value) => accumulator + value.length, 0)
}
/** /**
* Starts the Connection Manager. If Metrics are not enabled on libp2p * Starts the Connection Manager. If Metrics are not enabled on libp2p
* only event loop and connection limits will be monitored. * only event loop and connection limits will be monitored.
*/ */
start () { start () {
if (this._libp2p.metrics) { if (this._metrics) {
this._timer = this._timer || retimer(this._checkMetrics, this._options.pollInterval) this._timer = this._timer || retimer(this._checkMetrics, this._options.pollInterval)
} }
@ -106,33 +74,13 @@ class ConnectionManager extends EventEmitter {
/** /**
* Stops the Connection Manager * Stops the Connection Manager
* @async
*/ */
async stop () { stop () {
this._timer && this._timer.clear() this._timer && this._timer.clear()
this._latencyMonitor && this._latencyMonitor.removeListener('data', this._onLatencyMeasure) this._latencyMonitor && this._latencyMonitor.removeListener('data', this._onLatencyMeasure)
await this._close()
debug('stopped') debug('stopped')
} }
/**
* Cleans up the connections
* @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()
}
/** /**
* Sets the value of the given peer. Peers with lower values * Sets the value of the given peer. Peers with lower values
* will be disconnected first. * will be disconnected first.
@ -155,7 +103,7 @@ class ConnectionManager extends EventEmitter {
* @private * @private
*/ */
_checkMetrics () { _checkMetrics () {
const movingAverages = this._libp2p.metrics.global.movingAverages const movingAverages = this._metrics.global.movingAverages
const received = movingAverages.dataReceived[this._options.movingAverageInterval].movingAverage() const received = movingAverages.dataReceived[this._options.movingAverageInterval].movingAverage()
this._checkLimit('maxReceivedData', received) this._checkLimit('maxReceivedData', received)
const sent = movingAverages.dataSent[this._options.movingAverageInterval].movingAverage() const sent = movingAverages.dataSent[this._options.movingAverageInterval].movingAverage()
@ -171,24 +119,12 @@ class ConnectionManager extends EventEmitter {
* @param {Connection} connection * @param {Connection} connection
*/ */
onConnect (connection) { onConnect (connection) {
const peerId = connection.remotePeer const peerId = connection.remotePeer.toB58String()
const peerIdStr = peerId.toB58String() this._connections.set(connection.id, connection)
const storedConn = this.connections.get(peerIdStr) if (!this._peerValues.has(peerId)) {
this._peerValues.set(peerId, this._options.defaultPeerValue)
this.emit('peer:connect', connection)
if (storedConn) {
storedConn.push(connection)
} else {
this.connections.set(peerIdStr, [connection])
} }
this._checkLimit('maxConnections', this._connections.size)
this._libp2p.peerStore.keyBook.set(peerId, peerId.pubKey)
if (!this._peerValues.has(peerIdStr)) {
this._peerValues.set(peerIdStr, this._options.defaultPeerValue)
}
this._checkLimit('maxConnections', this.size)
} }
/** /**
@ -196,50 +132,8 @@ class ConnectionManager extends EventEmitter {
* @param {Connection} connection * @param {Connection} connection
*/ */
onDisconnect (connection) { onDisconnect (connection) {
const peerId = connection.remotePeer.toB58String() this._connections.delete(connection.id)
let storedConn = this.connections.get(peerId) this._peerValues.delete(connection.remotePeer.toB58String())
if (storedConn && storedConn.length > 1) {
storedConn = storedConn.filter((conn) => conn.id !== connection.id)
this.connections.set(peerId, storedConn)
} else if (storedConn) {
this.connections.delete(peerId)
this._peerValues.delete(connection.remotePeer.toB58String())
this.emit('peer:disconnect', connection)
}
}
/**
* Get a connection with a peer.
* @param {PeerId} peerId
* @returns {Connection}
*/
get (peerId) {
const connections = this.getAll(peerId)
if (connections.length) {
return connections[0]
}
return null
}
/**
* Get all open connections with a peer.
* @param {PeerId} peerId
* @returns {Array<Connection>}
*/
getAll (peerId) {
if (!PeerId.isPeerId(peerId)) {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
const id = peerId.toB58String()
const connections = this.connections.get(id)
// Return all open connections
if (connections) {
return connections.filter(connection => connection.stat.status === 'open')
}
return []
} }
/** /**
@ -272,7 +166,7 @@ class ConnectionManager extends EventEmitter {
* @private * @private
*/ */
_maybeDisconnectOne () { _maybeDisconnectOne () {
if (this._options.minConnections < this.connections.size) { if (this._options.minConnections < this._connections.size) {
const peerValues = Array.from(this._peerValues).sort(byPeerValue) const peerValues = Array.from(this._peerValues).sort(byPeerValue)
debug('%s: sorted peer values: %j', this._peerId, peerValues) debug('%s: sorted peer values: %j', this._peerId, peerValues)
const disconnectPeer = peerValues[0] const disconnectPeer = peerValues[0]
@ -280,9 +174,9 @@ class ConnectionManager extends EventEmitter {
const peerId = disconnectPeer[0] const peerId = disconnectPeer[0]
debug('%s: lowest value peer is %s', this._peerId, peerId) debug('%s: lowest value peer is %s', this._peerId, peerId)
debug('%s: closing a connection to %j', this._peerId, peerId) debug('%s: closing a connection to %j', this._peerId, peerId)
for (const connections of this.connections.values()) { for (const connection of this._connections.values()) {
if (connections[0].remotePeer.toB58String() === peerId) { if (connection.remotePeer.toB58String() === peerId) {
connections[0].close() connection.close()
break break
} }
} }

View File

@ -1,246 +0,0 @@
'use strict'
/**
* This code is based on `latency-monitor` (https://github.com/mlucool/latency-monitor) by `mlucool` (https://github.com/mlucool), available under Apache License 2.0 (https://github.com/mlucool/latency-monitor/blob/master/LICENSE)
*/
/* global window */
const globalThis = require('ipfs-utils/src/globalthis')
const EventEmitter = require('events')
const VisibilityChangeEmitter = require('./visibility-change-emitter')
const debug = require('debug')('latency-monitor:LatencyMonitor')
/**
* @typedef {Object} SummaryObject
* @property {Number} events How many events were called
* @property {Number} minMS What was the min time for a cb to be called
* @property {Number} maxMS What was the max time for a cb to be called
* @property {Number} avgMs What was the average time for a cb to be called
* @property {Number} lengthMs How long this interval was in ms
*/
/**
* A class to monitor latency of any async function which works in a browser or node. This works by periodically calling
* the asyncTestFn and timing how long it takes the callback to be called. It can also periodically emit stats about this.
* This can be disabled and stats can be pulled via setting dataEmitIntervalMs = 0.
*
* The default implementation is an event loop latency monitor. This works by firing periodic events into the event loop
* and timing how long it takes to get back.
*
* @example
* const monitor = new LatencyMonitor();
* monitor.on('data', (summary) => console.log('Event Loop Latency: %O', summary));
*
* @example
* const monitor = new LatencyMonitor({latencyCheckIntervalMs: 1000, dataEmitIntervalMs: 60000, asyncTestFn:ping});
* monitor.on('data', (summary) => console.log('Ping Pong Latency: %O', summary));
*/
class LatencyMonitor extends EventEmitter {
/**
* @param {Number} [latencyCheckIntervalMs=500] How often to add a latency check event (ms)
* @param {Number} [dataEmitIntervalMs=5000] How often to summarize latency check events. null or 0 disables event firing
* @param {function} [asyncTestFn] What cb-style async function to use
* @param {Number} [latencyRandomPercentage=5] What percent (+/-) of latencyCheckIntervalMs should we randomly use? This helps avoid alignment to other events.
*/
constructor ({ latencyCheckIntervalMs, dataEmitIntervalMs, asyncTestFn, latencyRandomPercentage } = {}) {
super()
const that = this
// 0 isn't valid here, so its ok to use ||
that.latencyCheckIntervalMs = latencyCheckIntervalMs || 500 // 0.5s
that.latencyRandomPercentage = latencyRandomPercentage || 10
that._latecyCheckMultiply = 2 * (that.latencyRandomPercentage / 100.0) * that.latencyCheckIntervalMs
that._latecyCheckSubtract = that._latecyCheckMultiply / 2
that.dataEmitIntervalMs = (dataEmitIntervalMs === null || dataEmitIntervalMs === 0) ? undefined
: dataEmitIntervalMs || 5 * 1000 // 5s
debug('latencyCheckIntervalMs: %s dataEmitIntervalMs: %s',
that.latencyCheckIntervalMs, that.dataEmitIntervalMs)
if (that.dataEmitIntervalMs) {
debug('Expecting ~%s events per summary', that.latencyCheckIntervalMs / that.dataEmitIntervalMs)
} else {
debug('Not emitting summaries')
}
that.asyncTestFn = asyncTestFn // If there is no asyncFn, we measure latency
// If process: use high resolution timer
if (globalThis.process && globalThis.process.hrtime) {
debug('Using process.hrtime for timing')
that.now = globalThis.process.hrtime
that.getDeltaMS = (startTime) => {
const hrtime = that.now(startTime)
return (hrtime[0] * 1000) + (hrtime[1] / 1000000)
}
// Let's try for a timer that only monotonically increases
} else if (typeof window !== 'undefined' && window.performance && window.performance.now) {
debug('Using performance.now for timing')
that.now = window.performance.now.bind(window.performance)
that.getDeltaMS = (startTime) => Math.round(that.now() - startTime)
} else {
debug('Using Date.now for timing')
that.now = Date.now
that.getDeltaMS = (startTime) => that.now() - startTime
}
that._latencyData = that._initLatencyData()
// We check for isBrowser because of browsers set max rates of timeouts when a page is hidden,
// so we fall back to another library
// See: http://stackoverflow.com/questions/6032429/chrome-timeouts-interval-suspended-in-background-tabs
if (isBrowser()) {
that._visibilityChangeEmitter = new VisibilityChangeEmitter()
that._visibilityChangeEmitter.on('visibilityChange', (pageInFocus) => {
if (pageInFocus) {
that._startTimers()
} else {
that._emitSummary()
that._stopTimers()
}
})
}
if (!that._visibilityChangeEmitter || that._visibilityChangeEmitter.isVisible()) {
that._startTimers()
}
}
/**
* Start internal timers
* @private
*/
_startTimers () {
// Timer already started, ignore this
if (this._checkLatencyID) {
return
}
this._checkLatency()
if (this.dataEmitIntervalMs) {
this._emitIntervalID = setInterval(() => this._emitSummary(), this.dataEmitIntervalMs)
if (typeof this._emitIntervalID.unref === 'function') {
this._emitIntervalID.unref() // Doesn't block exit
}
}
}
/**
* Stop internal timers
* @private
*/
_stopTimers () {
if (this._checkLatencyID) {
clearTimeout(this._checkLatencyID)
this._checkLatencyID = undefined
}
if (this._emitIntervalID) {
clearInterval(this._emitIntervalID)
this._emitIntervalID = undefined
}
}
/**
* Emit summary only if there were events. It might not have any events if it was forced via a page hidden/show
* @private
*/
_emitSummary () {
const summary = this.getSummary()
if (summary.events > 0) {
this.emit('data', summary)
}
}
/**
* Calling this function will end the collection period. If a timing event was already fired and somewhere in the queue,
* it will not count for this time period
* @returns {SummaryObject}
*/
getSummary () {
// We might want to adjust for the number of expected events
// Example: first 1 event it comes back, then such a long blocker that the next emit check comes
// Then this fires - looks like no latency!!
const latency = {
events: this._latencyData.events,
minMs: this._latencyData.minMs,
maxMs: this._latencyData.maxMs,
avgMs: this._latencyData.events ? this._latencyData.totalMs / this._latencyData.events
: Number.POSITIVE_INFINITY,
lengthMs: this.getDeltaMS(this._latencyData.startTime)
}
this._latencyData = this._initLatencyData() // Clear
debug('Summary: %O', latency)
return latency
}
/**
* Randomly calls an async fn every roughly latencyCheckIntervalMs (plus some randomness). If no async fn is found,
* it will simply report on event loop latency.
*
* @private
*/
_checkLatency () {
const that = this
// Randomness is needed to avoid alignment by accident to regular things in the event loop
const randomness = (Math.random() * that._latecyCheckMultiply) - that._latecyCheckSubtract
// We use this to ensure that in case some overlap somehow, we don't take the wrong startTime/offset
const localData = {
deltaOffset: Math.ceil(that.latencyCheckIntervalMs + randomness),
startTime: that.now()
}
const cb = () => {
// We are already stopped, ignore this datapoint
if (!this._checkLatencyID) {
return
}
const deltaMS = that.getDeltaMS(localData.startTime) - localData.deltaOffset
that._checkLatency() // Start again ASAP
// Add the data point. If this gets complex, refactor it
that._latencyData.events++
that._latencyData.minMs = Math.min(that._latencyData.minMs, deltaMS)
that._latencyData.maxMs = Math.max(that._latencyData.maxMs, deltaMS)
that._latencyData.totalMs += deltaMS
debug('MS: %s Data: %O', deltaMS, that._latencyData)
}
debug('localData: %O', localData)
this._checkLatencyID = setTimeout(() => {
// This gets rid of including event loop
if (that.asyncTestFn) {
// Clear timing related things
localData.deltaOffset = 0
localData.startTime = that.now()
that.asyncTestFn(cb)
} else {
// setTimeout is not more accurate than 1ms, so this will ensure positive numbers. Add 1 to emitted data to remove.
// This is not the best, but for now it'll be just fine. This isn't meant to be sub ms accurate.
localData.deltaOffset -= 1
// If there is no function to test, we mean check latency which is a special case that is really cb => cb()
// We avoid that for the few extra function all overheads. Also, we want to keep the timers different
cb()
}
}, localData.deltaOffset)
if (typeof this._checkLatencyID.unref === 'function') {
this._checkLatencyID.unref() // Doesn't block exit
}
}
_initLatencyData () {
return {
startTime: this.now(),
minMs: Number.POSITIVE_INFINITY,
maxMs: Number.NEGATIVE_INFINITY,
events: 0,
totalMs: 0
}
}
}
function isBrowser () {
return typeof window !== 'undefined'
}
module.exports = LatencyMonitor

View File

@ -1,118 +0,0 @@
/* global document */
/**
* This code is based on `latency-monitor` (https://github.com/mlucool/latency-monitor) by `mlucool` (https://github.com/mlucool), available under Apache License 2.0 (https://github.com/mlucool/latency-monitor/blob/master/LICENSE)
*/
'use strict'
const EventEmitter = require('events')
const debug = require('debug')('latency-monitor:VisibilityChangeEmitter')
/**
* Listen to page visibility change events (i.e. when the page is focused / blurred) by an event emitter.
*
* Warning: This does not work on all browsers, but should work on all modern browsers
*
* @example
*
* const myVisibilityEmitter = new VisibilityChangeEmitter();
*
* myVisibilityEmitter.on('visibilityChange', (pageInFocus) => {
* if ( pageInFocus ){
* // Page is in focus
* console.log('In focus');
* }
* else {
* // Page is blurred
* console.log('Out of focus');
* }
* });
* // To access the visibility state directly, call:
* console.log('Am I focused now? ' + myVisibilityEmitter.isVisible());
*
* @class VisibilityChangeEmitter
*/
module.exports = class VisibilityChangeEmitter extends EventEmitter {
/**
* Creates a VisibilityChangeEmitter
*/
constructor () {
super()
if (typeof document === 'undefined') {
debug('This is not a browser, no "document" found. Stopping.')
return
}
this._initializeVisibilityVarNames()
this._addVisibilityChangeListener()
}
/**
* document.hidden and document.visibilityChange are the two variables we need to check for;
* Since these variables are named differently in different browsers, this function sets
* the appropriate name based on the browser being used. Once executed, tha actual names of
* document.hidden and document.visibilityChange are found in this._hidden and this._visibilityChange
* respectively
* @private
*/
_initializeVisibilityVarNames () {
let hidden
let visibilityChange
if (typeof document.hidden !== 'undefined') { // Opera 12.10 and Firefox 18 and later support
hidden = 'hidden'
visibilityChange = 'visibilitychange'
} else if (typeof document.mozHidden !== 'undefined') {
hidden = 'mozHidden'
visibilityChange = 'mozvisibilitychange'
} else if (typeof document.msHidden !== 'undefined') {
hidden = 'msHidden'
visibilityChange = 'msvisibilitychange'
} else if (typeof document.webkitHidden !== 'undefined') {
hidden = 'webkitHidden'
visibilityChange = 'webkitvisibilitychange'
}
this._hidden = hidden
this._visibilityChange = visibilityChange
}
/**
* Adds an event listener on the document that listens to changes in document.visibilityChange
* (or whatever name by which the visibilityChange variable is known in the browser)
* @private
*/
_addVisibilityChangeListener () {
if (typeof document.addEventListener === 'undefined' ||
typeof document[this._hidden] === 'undefined') {
debug('Checking page visibility requires a browser that supports the Page Visibility API.')
} else {
// Handle page visibility change
document.addEventListener(this._visibilityChange, this._handleVisibilityChange.bind(this), false)
}
}
/**
* The function returns ```true``` if the page is visible or ```false``` if the page is not visible and
* ```undefined``` if the page visibility API is not supported by the browser.
* @returns {Boolean|void} whether the page is now visible or not (undefined is unknown)
*/
isVisible () {
if (this._hidden === undefined || document[this._hidden] === undefined) {
return undefined
}
return !document[this._hidden]
}
/**
* The function that is called when document.visibilityChange has changed
* It emits an event called visibilityChange and sends the value of document.hidden as a
* parameter
*
* @private
*/
_handleVisibilityChange () {
const visible = !document[this._hidden]
debug(visible ? 'Page Visible' : 'Page Hidden')
// Emit the event
this.emit('visibilityChange', visible)
}
}

View File

@ -24,7 +24,7 @@ module.exports = (node) => {
* @param {object} [options] * @param {object} [options]
* @param {number} [options.timeout] How long the query should run * @param {number} [options.timeout] How long the query should run
* @param {number} [options.maxNumProviders] - maximum number of providers to find * @param {number} [options.maxNumProviders] - maximum number of providers to find
* @returns {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} * @returns {AsyncIterable<PeerInfo>}
*/ */
async * findProviders (key, options) { async * findProviders (key, options) {
if (!routers.length) { if (!routers.length) {
@ -42,8 +42,8 @@ module.exports = (node) => {
}) })
) )
for (const peer of result) { for (const pInfo of result) {
yield peer yield pInfo
} }
}, },

View File

@ -4,12 +4,12 @@ const multiaddr = require('multiaddr')
const errCode = require('err-code') const errCode = require('err-code')
const TimeoutController = require('timeout-abort-controller') const TimeoutController = require('timeout-abort-controller')
const anySignal = require('any-signal') const anySignal = require('any-signal')
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const debug = require('debug') const debug = require('debug')
const log = debug('libp2p:dialer') const log = debug('libp2p:dialer')
log.error = debug('libp2p:dialer:error') log.error = debug('libp2p:dialer:error')
const { DialRequest } = require('./dial-request') const { DialRequest } = require('./dial-request')
const getPeer = require('../get-peer')
const { codes } = require('../errors') const { codes } = require('../errors')
const { const {
@ -58,20 +58,19 @@ class Dialer {
} }
/** /**
* Connects to a given `peer` by dialing all of its known addresses. * 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 * The dial to the first address that is successfully able to upgrade a connection
* will be used. * will be used.
* *
* @param {PeerId|Multiaddr|string} peer The peer to dial * @param {PeerInfo|Multiaddr} peer The peer to dial
* @param {object} [options] * @param {object} [options]
* @param {AbortSignal} [options.signal] An AbortController signal * @param {AbortSignal} [options.signal] An AbortController signal
* @returns {Promise<Connection>} * @returns {Promise<Connection>}
*/ */
async connectToPeer (peer, options = {}) { async connectToPeer (peer, options = {}) {
const dialTarget = this._createDialTarget(peer) const dialTarget = this._createDialTarget(peer)
if (dialTarget.addrs.length === 0) {
if (!dialTarget.addrs.length) { throw errCode(new Error('The dial request has no addresses'), 'ERR_NO_DIAL_MULTIADDRS')
throw errCode(new Error('The dial request has no addresses'), codes.ERR_NO_VALID_ADDRESSES)
} }
const pendingDial = this._pendingDials.get(dialTarget.id) || this._createPendingDial(dialTarget, options) const pendingDial = this._pendingDials.get(dialTarget.id) || this._createPendingDial(dialTarget, options)
@ -100,29 +99,21 @@ class Dialer {
/** /**
* Creates a DialTarget. The DialTarget is used to create and track * Creates a DialTarget. The DialTarget is used to create and track
* the DialRequest to a given peer. * the DialRequest to a given peer.
* If a multiaddr is received it should be the first address attempted.
* @private * @private
* @param {PeerId|Multiaddr|string} peer A PeerId or Multiaddr * @param {PeerInfo|Multiaddr} peer A PeerId or Multiaddr
* @returns {DialTarget} * @returns {DialTarget}
*/ */
_createDialTarget (peer) { _createDialTarget (peer) {
const { id, multiaddrs } = getPeer(peer) const dialable = Dialer.getDialable(peer)
if (multiaddr.isMultiaddr(dialable)) {
if (multiaddrs) { return {
this.peerStore.addressBook.add(id, multiaddrs) id: dialable.toString(),
addrs: [dialable]
}
} }
const addrs = this.peerStore.multiaddrsForPeer(dialable)
let addrs = this.peerStore.addressBook.getMultiaddrsForPeer(id)
// If received a multiaddr to dial, it should be the first to use
// But, if we know other multiaddrs for the peer, we should try them too.
if (multiaddr.isMultiaddr(peer)) {
addrs = addrs.filter((addr) => !peer.equals(addr))
addrs.unshift(peer)
}
return { return {
id: id.toB58String(), id: dialable.id.toB58String(),
addrs addrs
} }
} }
@ -145,7 +136,7 @@ class Dialer {
*/ */
_createPendingDial (dialTarget, options) { _createPendingDial (dialTarget, options) {
const dialAction = (addr, options) => { const dialAction = (addr, options) => {
if (options.signal.aborted) throw errCode(new Error('already aborted'), codes.ERR_ALREADY_ABORTED) if (options.signal.aborted) throw errCode(new Error('already aborted'), 'ERR_ALREADY_ABORTED')
return this.transportManager.dial(addr, options) return this.transportManager.dial(addr, options)
} }
@ -187,6 +178,37 @@ class Dialer {
log('token %d released', token) log('token %d released', token)
this.tokens.push(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 module.exports = Dialer

View File

@ -2,19 +2,16 @@
exports.messages = { exports.messages = {
NOT_STARTED_YET: 'The libp2p node is not started yet', NOT_STARTED_YET: 'The libp2p node is not started yet',
DHT_DISABLED: 'DHT is not available', DHT_DISABLED: 'DHT is not available'
CONN_ENCRYPTION_REQUIRED: 'At least one connection encryption module is required'
} }
exports.codes = { exports.codes = {
DHT_DISABLED: 'ERR_DHT_DISABLED', DHT_DISABLED: 'ERR_DHT_DISABLED',
PUBSUB_NOT_STARTED: 'ERR_PUBSUB_NOT_STARTED', PUBSUB_NOT_STARTED: 'ERR_PUBSUB_NOT_STARTED',
DHT_NOT_STARTED: 'ERR_DHT_NOT_STARTED', DHT_NOT_STARTED: 'ERR_DHT_NOT_STARTED',
CONN_ENCRYPTION_REQUIRED: 'ERR_CONN_ENCRYPTION_REQUIRED',
ERR_CONNECTION_ENDED: 'ERR_CONNECTION_ENDED', ERR_CONNECTION_ENDED: 'ERR_CONNECTION_ENDED',
ERR_CONNECTION_FAILED: 'ERR_CONNECTION_FAILED', ERR_CONNECTION_FAILED: 'ERR_CONNECTION_FAILED',
ERR_NODE_NOT_STARTED: 'ERR_NODE_NOT_STARTED', ERR_NODE_NOT_STARTED: 'ERR_NODE_NOT_STARTED',
ERR_ALREADY_ABORTED: 'ERR_ALREADY_ABORTED',
ERR_NO_VALID_ADDRESSES: 'ERR_NO_VALID_ADDRESSES', ERR_NO_VALID_ADDRESSES: 'ERR_NO_VALID_ADDRESSES',
ERR_DISCOVERED_SELF: 'ERR_DISCOVERED_SELF', ERR_DISCOVERED_SELF: 'ERR_DISCOVERED_SELF',
ERR_DUPLICATE_TRANSPORT: 'ERR_DUPLICATE_TRANSPORT', ERR_DUPLICATE_TRANSPORT: 'ERR_DUPLICATE_TRANSPORT',
@ -22,12 +19,10 @@ exports.codes = {
ERR_HOP_REQUEST_FAILED: 'ERR_HOP_REQUEST_FAILED', ERR_HOP_REQUEST_FAILED: 'ERR_HOP_REQUEST_FAILED',
ERR_INVALID_KEY: 'ERR_INVALID_KEY', ERR_INVALID_KEY: 'ERR_INVALID_KEY',
ERR_INVALID_MESSAGE: 'ERR_INVALID_MESSAGE', ERR_INVALID_MESSAGE: 'ERR_INVALID_MESSAGE',
ERR_INVALID_PARAMETERS: 'ERR_INVALID_PARAMETERS',
ERR_INVALID_PEER: 'ERR_INVALID_PEER', ERR_INVALID_PEER: 'ERR_INVALID_PEER',
ERR_MUXER_UNAVAILABLE: 'ERR_MUXER_UNAVAILABLE', ERR_MUXER_UNAVAILABLE: 'ERR_MUXER_UNAVAILABLE',
ERR_TIMEOUT: 'ERR_TIMEOUT', ERR_TIMEOUT: 'ERR_TIMEOUT',
ERR_TRANSPORT_UNAVAILABLE: 'ERR_TRANSPORT_UNAVAILABLE', ERR_TRANSPORT_UNAVAILABLE: 'ERR_TRANSPORT_UNAVAILABLE',
ERR_TRANSPORT_DIAL_FAILED: 'ERR_TRANSPORT_DIAL_FAILED', ERR_TRANSPORT_DIAL_FAILED: 'ERR_TRANSPORT_DIAL_FAILED',
ERR_UNSUPPORTED_PROTOCOL: 'ERR_UNSUPPORTED_PROTOCOL', ERR_UNSUPPORTED_PROTOCOL: 'ERR_UNSUPPORTED_PROTOCOL'
ERR_INVALID_MULTIADDR: 'ERR_INVALID_MULTIADDR'
} }

73
src/get-peer-info.js Normal file
View File

@ -0,0 +1,73 @@
'use strict'
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const multiaddr = require('multiaddr')
const errCode = require('err-code')
/**
* Converts the given `peer` to a `PeerInfo` instance.
* The `PeerStore` will be checked for the resulting peer, and
* the peer will be updated in the `PeerStore`.
*
* @param {PeerInfo|PeerId|Multiaddr|string} peer
* @param {PeerStore} peerStore
* @returns {PeerInfo}
*/
function getPeerInfo (peer, peerStore) {
if (typeof peer === 'string') {
peer = multiaddr(peer)
}
let addr
if (multiaddr.isMultiaddr(peer)) {
addr = peer
try {
peer = PeerId.createFromB58String(peer.getPeerId())
} catch (err) {
throw errCode(
new Error(`${peer} is not a valid peer type`),
'ERR_INVALID_MULTIADDR'
)
}
}
if (PeerId.isPeerId(peer)) {
peer = new PeerInfo(peer)
}
addr && peer.multiaddrs.add(addr)
return peerStore ? peerStore.put(peer) : peer
}
/**
* If `getPeerInfo` does not return a peer with multiaddrs,
* the `libp2p` PeerRouter will be used to attempt to find the peer.
*
* @async
* @param {PeerInfo|PeerId|Multiaddr|string} peer
* @param {Libp2p} libp2p
* @returns {Promise<PeerInfo>}
*/
function getPeerInfoRemote (peer, libp2p) {
let peerInfo
try {
peerInfo = getPeerInfo(peer, libp2p.peerStore)
} catch (err) {
throw errCode(err, 'ERR_INVALID_PEER_TYPE')
}
// If we don't have an address for the peer, attempt to find it
if (peerInfo.multiaddrs.size < 1) {
return libp2p.peerRouting.findPeer(peerInfo.id)
}
return peerInfo
}
module.exports = {
getPeerInfoRemote,
getPeerInfo
}

View File

@ -1,40 +0,0 @@
'use strict'
const PeerId = require('peer-id')
const multiaddr = require('multiaddr')
const errCode = require('err-code')
const { codes } = require('./errors')
/**
* Converts the given `peer` to a `Peer` object.
* If a multiaddr is received, the addressBook is updated.
* @param {PeerId|Multiaddr|string} peer
* @param {PeerStore} peerStore
* @returns {{ id: PeerId, multiaddrs: Array<Multiaddr> }}
*/
function getPeer (peer) {
if (typeof peer === 'string') {
peer = multiaddr(peer)
}
let addr
if (multiaddr.isMultiaddr(peer)) {
addr = peer
try {
peer = PeerId.createFromB58String(peer.getPeerId())
} catch (err) {
throw errCode(
new Error(`${peer} is not a valid peer type`),
codes.ERR_INVALID_MULTIADDR
)
}
}
return {
id: peer,
multiaddrs: addr ? [addr] : undefined
}
}
module.exports = getPeer

View File

@ -1,12 +1,12 @@
'use strict' 'use strict'
const { Buffer } = require('buffer')
const debug = require('debug') const debug = require('debug')
const pb = require('it-protocol-buffers') const pb = require('it-protocol-buffers')
const lp = require('it-length-prefixed') const lp = require('it-length-prefixed')
const pipe = require('it-pipe') const pipe = require('it-pipe')
const { collect, take, consume } = require('streaming-iterables') const { collect, take, consume } = require('streaming-iterables')
const PeerInfo = require('peer-info')
const PeerId = require('peer-id') const PeerId = require('peer-id')
const multiaddr = require('multiaddr') const multiaddr = require('multiaddr')
const { toBuffer } = require('it-buffer') const { toBuffer } = require('it-buffer')
@ -27,6 +27,39 @@ const errCode = require('err-code')
const { codes } = require('../errors') const { codes } = require('../errors')
class IdentifyService { class IdentifyService {
/**
* Replaces the multiaddrs on the given `peerInfo`,
* with the provided `multiaddrs`
* @param {PeerInfo} peerInfo
* @param {Array<Multiaddr>|Array<Buffer>} multiaddrs
*/
static updatePeerAddresses (peerInfo, multiaddrs) {
if (multiaddrs && multiaddrs.length > 0) {
peerInfo.multiaddrs.clear()
multiaddrs.forEach(ma => {
try {
peerInfo.multiaddrs.add(ma)
} catch (err) {
log.error('could not add multiaddr', err)
}
})
}
}
/**
* Replaces the protocols on the given `peerInfo`,
* with the provided `protocols`
* @static
* @param {PeerInfo} peerInfo
* @param {Array<string>} protocols
*/
static updatePeerProtocols (peerInfo, protocols) {
if (protocols && protocols.length > 0) {
peerInfo.protocols.clear()
protocols.forEach(proto => peerInfo.protocols.add(proto))
}
}
/** /**
* Takes the `addr` and converts it to a Multiaddr if possible * Takes the `addr` and converts it to a Multiaddr if possible
* @param {Buffer|String} addr * @param {Buffer|String} addr
@ -46,37 +79,21 @@ class IdentifyService {
/** /**
* @constructor * @constructor
* @param {object} options * @param {object} options
* @param {Libp2p} options.libp2p * @param {Registrar} options.registrar
* @param {Map<string, handler>} options.protocols A reference to the protocols we support * @param {Map<string, handler>} options.protocols A reference to the protocols we support
* @param {PeerInfo} options.peerInfo The peer running the identify service
*/ */
constructor ({ libp2p, protocols }) { constructor (options) {
/** /**
* @property {PeerStore} * @property {Registrar}
*/ */
this.peerStore = libp2p.peerStore this.registrar = options.registrar
/** /**
* @property {ConnectionManager} * @property {PeerInfo}
*/ */
this.connectionManager = libp2p.connectionManager this.peerInfo = options.peerInfo
this.connectionManager.on('peer:connect', (connection) => { this._protocols = options.protocols
const peerId = connection.remotePeer
this.identify(connection, peerId).catch(log.error)
})
/**
* @property {PeerId}
*/
this.peerId = libp2p.peerId
/**
* @property {AddressManager}
*/
this._libp2p = libp2p
this._protocols = protocols
this.handleMessage = this.handleMessage.bind(this) this.handleMessage = this.handleMessage.bind(this)
} }
@ -93,7 +110,7 @@ class IdentifyService {
await pipe( await pipe(
[{ [{
listenAddrs: this._libp2p.multiaddrs.map((ma) => ma.buffer), listenAddrs: this.peerInfo.multiaddrs.toArray().map((ma) => ma.buffer),
protocols: Array.from(this._protocols.keys()) protocols: Array.from(this._protocols.keys())
}], }],
pb.encode(Message), pb.encode(Message),
@ -117,7 +134,7 @@ class IdentifyService {
const connections = [] const connections = []
let connection let connection
for (const peer of peerStore.peers.values()) { for (const peer of peerStore.peers.values()) {
if (peer.protocols.includes(MULTICODEC_IDENTIFY_PUSH) && (connection = this.connectionManager.get(peer.id))) { if (peer.protocols.has(MULTICODEC_IDENTIFY_PUSH) && (connection = this.registrar.getConnection(peer))) {
connections.push(connection) connections.push(connection)
} }
} }
@ -164,7 +181,7 @@ class IdentifyService {
} = message } = message
const id = await PeerId.createFromPubKey(publicKey) const id = await PeerId.createFromPubKey(publicKey)
const peerInfo = new PeerInfo(id)
if (connection.remotePeer.toB58String() !== id.toB58String()) { if (connection.remotePeer.toB58String() !== id.toB58String()) {
throw errCode(new Error('identified peer does not match the expected peer'), codes.ERR_INVALID_PEER) throw errCode(new Error('identified peer does not match the expected peer'), codes.ERR_INVALID_PEER)
} }
@ -172,10 +189,11 @@ class IdentifyService {
// Get the observedAddr if there is one // Get the observedAddr if there is one
observedAddr = IdentifyService.getCleanMultiaddr(observedAddr) observedAddr = IdentifyService.getCleanMultiaddr(observedAddr)
// Update peers data in PeerStore // Copy the listenAddrs and protocols
this.peerStore.addressBook.set(id, listenAddrs.map((addr) => multiaddr(addr))) IdentifyService.updatePeerAddresses(peerInfo, listenAddrs)
this.peerStore.protoBook.set(id, protocols) IdentifyService.updatePeerProtocols(peerInfo, protocols)
this.registrar.peerStore.replace(peerInfo)
// TODO: Track our observed address so that we can score it // TODO: Track our observed address so that we can score it
log('received observed address of %s', observedAddr) log('received observed address of %s', observedAddr)
} }
@ -210,15 +228,15 @@ class IdentifyService {
*/ */
_handleIdentify ({ connection, stream }) { _handleIdentify ({ connection, stream }) {
let publicKey = Buffer.alloc(0) let publicKey = Buffer.alloc(0)
if (this.peerId.pubKey) { if (this.peerInfo.id.pubKey) {
publicKey = this.peerId.pubKey.bytes publicKey = this.peerInfo.id.pubKey.bytes
} }
const message = Message.encode({ const message = Message.encode({
protocolVersion: PROTOCOL_VERSION, protocolVersion: PROTOCOL_VERSION,
agentVersion: AGENT_VERSION, agentVersion: AGENT_VERSION,
publicKey, publicKey,
listenAddrs: this._libp2p.multiaddrs.map((ma) => ma.buffer), listenAddrs: this.peerInfo.multiaddrs.toArray().map((ma) => ma.buffer),
observedAddr: connection.remoteAddr.buffer, observedAddr: connection.remoteAddr.buffer,
protocols: Array.from(this._protocols.keys()) protocols: Array.from(this._protocols.keys())
}) })
@ -255,16 +273,20 @@ class IdentifyService {
return log.error('received invalid message', err) return log.error('received invalid message', err)
} }
// Update peers data in PeerStore // Update the listen addresses
const id = connection.remotePeer const peerInfo = new PeerInfo(connection.remotePeer)
try { try {
this.peerStore.addressBook.set(id, message.listenAddrs.map((addr) => multiaddr(addr))) IdentifyService.updatePeerAddresses(peerInfo, message.listenAddrs)
} catch (err) { } catch (err) {
return log.error('received invalid listen addrs', err) return log.error('received invalid listen addrs', err)
} }
// Update the protocols // Update the protocols
this.peerStore.protoBook.set(id, message.protocols) IdentifyService.updatePeerProtocols(peerInfo, message.protocols)
// Update the peer in the PeerStore
this.registrar.peerStore.replace(peerInfo)
} }
} }

View File

@ -2,30 +2,25 @@
const { EventEmitter } = require('events') const { EventEmitter } = require('events')
const debug = require('debug') const debug = require('debug')
const globalThis = require('ipfs-utils/src/globalthis')
const log = debug('libp2p') const log = debug('libp2p')
log.error = debug('libp2p:error') log.error = debug('libp2p:error')
const errCode = require('err-code') const PeerInfo = require('peer-info')
const PeerId = require('peer-id')
const peerRouting = require('./peer-routing') const peerRouting = require('./peer-routing')
const contentRouting = require('./content-routing') const contentRouting = require('./content-routing')
const pubsub = require('./pubsub') const pubsub = require('./pubsub')
const getPeer = require('./get-peer') const { getPeerInfo } = require('./get-peer-info')
const { validate: validateConfig } = require('./config') const { validate: validateConfig } = require('./config')
const { codes, messages } = require('./errors') const { codes } = require('./errors')
const AddressManager = require('./address-manager')
const ConnectionManager = require('./connection-manager') const ConnectionManager = require('./connection-manager')
const Circuit = require('./circuit') const Circuit = require('./circuit')
const Dialer = require('./dialer') const Dialer = require('./dialer')
const Keychain = require('./keychain')
const Metrics = require('./metrics') const Metrics = require('./metrics')
const TransportManager = require('./transport-manager') const TransportManager = require('./transport-manager')
const Upgrader = require('./upgrader') const Upgrader = require('./upgrader')
const PeerStore = require('./peer-store') const PeerStore = require('./peer-store')
const PersistentPeerStore = require('./peer-store/persistent')
const Registrar = require('./registrar') const Registrar = require('./registrar')
const ping = require('./ping') const ping = require('./ping')
const { const {
@ -46,83 +41,69 @@ class Libp2p extends EventEmitter {
// and add default values where appropriate // and add default values where appropriate
this._options = validateConfig(_options) this._options = validateConfig(_options)
this.peerId = this._options.peerId
this.datastore = this._options.datastore this.datastore = this._options.datastore
this.peerInfo = this._options.peerInfo
this.peerStore = (this.datastore && this._options.peerStore.persistence) this.peerStore = new PeerStore()
? new PersistentPeerStore({
datastore: this.datastore,
...this._options.peerStore
})
: new PeerStore()
// Addresses {listen, announce, noAnnounce}
this.addresses = this._options.addresses
this.addressManager = new AddressManager(this._options.addresses)
this._modules = this._options.modules this._modules = this._options.modules
this._config = this._options.config this._config = this._options.config
this._transport = [] // Transport instances/references this._transport = [] // Transport instances/references
this._discovery = new Map() // Discovery service instances/references this._discovery = new Map() // Discovery service instances/references
// Create the Connection Manager
this.connectionManager = new ConnectionManager(this, this._options.connectionManager)
// Create Metrics
if (this._options.metrics.enabled) { if (this._options.metrics.enabled) {
this.metrics = new Metrics({ this.metrics = new Metrics(this._options.metrics)
...this._options.metrics,
connectionManager: this.connectionManager
})
}
// Create keychain
if (this._options.keychain && this._options.keychain.pass && this._options.keychain.datastore) {
log('creating keychain')
const keychainOpts = Keychain.generateOptions()
this.keychain = new Keychain(this._options.keychain.datastore, {
passPhrase: this._options.keychain.pass,
...keychainOpts,
...this._options.keychain
})
log('keychain constructed')
} }
// Setup the Upgrader // Setup the Upgrader
this.upgrader = new Upgrader({ this.upgrader = new Upgrader({
localPeer: this.peerId, localPeer: this.peerInfo.id,
metrics: this.metrics, metrics: this.metrics,
onConnection: (connection) => this.connectionManager.onConnect(connection), onConnection: (connection) => {
onConnectionEnd: (connection) => this.connectionManager.onDisconnect(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)
// Run identify for every connection
if (this.identifyService) {
this.identifyService.identify(connection, connection.remotePeer)
.catch(log.error)
}
},
onConnectionEnd: (connection) => {
const peerInfo = Dialer.getDialable(connection.remotePeer)
this.registrar.onDisconnect(peerInfo, connection)
this.connectionManager.onDisconnect(connection)
// 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)
}
}
}) })
// Create the Registrar
this.registrar = new Registrar({ peerStore: this.peerStore })
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 // Setup the transport manager
this.transportManager = new TransportManager({ this.transportManager = new TransportManager({
libp2p: this, libp2p: this,
upgrader: this.upgrader, upgrader: this.upgrader
faultTolerance: this._options.transportManager.faultTolerance
}) })
// Create the Registrar
this.registrar = new Registrar({
peerStore: this.peerStore,
connectionManager: this.connectionManager
})
this.handle = this.handle.bind(this)
this.registrar.handle = this.handle
// Attach crypto channels // Attach crypto channels
if (!this._modules.connEncryption || !this._modules.connEncryption.length) { if (this._modules.connEncryption) {
throw errCode(new Error(messages.CONN_ENCRYPTION_REQUIRED), codes.CONN_ENCRYPTION_REQUIRED) const cryptos = this._modules.connEncryption
cryptos.forEach((crypto) => {
this.upgrader.cryptos.set(crypto.protocol, crypto)
})
} }
const cryptos = this._modules.connEncryption
cryptos.forEach((crypto) => {
this.upgrader.cryptos.set(crypto.protocol, crypto)
})
this.dialer = new Dialer({ this.dialer = new Dialer({
transportManager: this.transportManager, transportManager: this.transportManager,
@ -151,7 +132,8 @@ class Libp2p extends EventEmitter {
// Add the identify service since we can multiplex // Add the identify service since we can multiplex
this.identifyService = new IdentifyService({ this.identifyService = new IdentifyService({
libp2p: this, registrar: this.registrar,
peerInfo: this.peerInfo,
protocols: this.upgrader.protocols protocols: this.upgrader.protocols
}) })
this.handle(Object.values(IDENTIFY_PROTOCOLS), this.identifyService.handleMessage) this.handle(Object.values(IDENTIFY_PROTOCOLS), this.identifyService.handleMessage)
@ -160,7 +142,7 @@ class Libp2p extends EventEmitter {
// Attach private network protector // Attach private network protector
if (this._modules.connProtector) { if (this._modules.connProtector) {
this.upgrader.protector = this._modules.connProtector this.upgrader.protector = this._modules.connProtector
} else if (globalThis.process !== undefined && globalThis.process.env && globalThis.process.env.LIBP2P_FORCE_PNET) { } else if (process.env.LIBP2P_FORCE_PNET) {
throw new Error('Private network is enforced, but no protector was provided') throw new Error('Private network is enforced, but no protector was provided')
} }
@ -169,7 +151,7 @@ class Libp2p extends EventEmitter {
const DHT = this._modules.dht const DHT = this._modules.dht
this._dht = new DHT({ this._dht = new DHT({
dialer: this.dialer, dialer: this.dialer,
peerId: this.peerId, peerInfo: this.peerInfo,
peerStore: this.peerStore, peerStore: this.peerStore,
registrar: this.registrar, registrar: this.registrar,
datastore: this.datastore, datastore: this.datastore,
@ -215,7 +197,6 @@ class Libp2p extends EventEmitter {
*/ */
async start () { async start () {
log('libp2p is starting') log('libp2p is starting')
try { try {
await this._onStarting() await this._onStarting()
await this._onDidStart() await this._onDidStart()
@ -243,10 +224,7 @@ class Libp2p extends EventEmitter {
await Promise.all(Array.from(this._discovery.values(), s => s.stop())) await Promise.all(Array.from(this._discovery.values(), s => s.stop()))
this._discovery = new Map() this.connectionManager.stop()
await this.peerStore.stop()
await this.connectionManager.stop()
await Promise.all([ await Promise.all([
this.pubsub && this.pubsub.stop(), this.pubsub && this.pubsub.stop(),
@ -255,6 +233,7 @@ class Libp2p extends EventEmitter {
]) ])
await this.transportManager.close() await this.transportManager.close()
await this.registrar.close()
ping.unmount(this) ping.unmount(this)
this.dialer.destroy() this.dialer.destroy()
@ -268,20 +247,6 @@ class Libp2p extends EventEmitter {
log('libp2p has stopped') log('libp2p has stopped')
} }
/**
* Load keychain keys from the datastore.
* Imports the private key as 'self', if needed.
* @async
* @returns {void}
*/
async loadKeychain () {
try {
await this.keychain.findKeyByName('self')
} catch (err) {
await this.keychain.importPeer('self', this.peerId)
}
}
isStarted () { isStarted () {
return this._isStarted return this._isStarted
} }
@ -292,13 +257,14 @@ class Libp2p extends EventEmitter {
* @returns {Map<string, Connection[]>} * @returns {Map<string, Connection[]>}
*/ */
get connections () { get connections () {
return this.connectionManager.connections return this.registrar.connections
} }
/** /**
* Dials to the provided peer. If successful, the known metadata of the * Dials to the provided peer. If successful, the `PeerInfo` of the
* peer will be added to the nodes `peerStore` * peer will be added to the nodes `peerStore`
* @param {PeerId|Multiaddr|string} peer The peer to dial *
* @param {PeerInfo|PeerId|Multiaddr|string} peer The peer to dial
* @param {object} options * @param {object} options
* @param {AbortSignal} [options.signal] * @param {AbortSignal} [options.signal]
* @returns {Promise<Connection>} * @returns {Promise<Connection>}
@ -309,23 +275,26 @@ class Libp2p extends EventEmitter {
/** /**
* Dials to the provided peer and handshakes with the given protocol. * Dials to the provided peer and handshakes with the given protocol.
* If successful, the known metadata of the peer will be added to the nodes `peerStore`, * If successful, the `PeerInfo` of the peer will be added to the nodes `peerStore`,
* and the `Connection` will be returned * and the `Connection` will be sent in the callback
*
* @async * @async
* @param {PeerId|Multiaddr|string} peer The peer to dial * @param {PeerInfo|PeerId|Multiaddr|string} peer The peer to dial
* @param {string[]|string} protocols * @param {string[]|string} protocols
* @param {object} options * @param {object} options
* @param {AbortSignal} [options.signal] * @param {AbortSignal} [options.signal]
* @returns {Promise<Connection|*>} * @returns {Promise<Connection|*>}
*/ */
async dialProtocol (peer, protocols, options) { async dialProtocol (peer, protocols, options) {
const { id, multiaddrs } = getPeer(peer, this.peerStore) const dialable = Dialer.getDialable(peer)
let connection = this.connectionManager.get(id) let connection
if (PeerInfo.isPeerInfo(dialable)) {
this.peerStore.put(dialable, { silent: true })
connection = this.registrar.getConnection(dialable)
}
if (!connection) { if (!connection) {
connection = await this.dialer.connectToPeer(peer, options) connection = await this.dialer.connectToPeer(dialable, options)
} else if (multiaddrs) {
this.peerStore.addressBook.add(id, multiaddrs)
} }
// If a protocol was provided, create a new stream // If a protocol was provided, create a new stream
@ -336,64 +305,30 @@ class Libp2p extends EventEmitter {
return connection return connection
} }
/**
* Get peer advertising multiaddrs by concating the addresses used
* by transports to listen with the announce addresses.
* Duplicated addresses and noAnnounce addresses are filtered out.
* @return {Array<Multiaddr>}
*/
get multiaddrs () {
// Filter noAnnounce multiaddrs
const filterMa = this.addressManager.getNoAnnounceAddrs()
// Create advertising list
return this.transportManager.getAddrs()
.concat(this.addressManager.getAnnounceAddrs())
.filter((ma, index, array) => {
// Filter out if repeated
if (array.findIndex((otherMa) => otherMa.equals(ma)) !== index) {
return false
}
// Filter out if in noAnnounceMultiaddrs
if (filterMa.find((fm) => fm.equals(ma))) {
return false
}
return true
})
}
/** /**
* Disconnects all connections to the given `peer` * Disconnects all connections to the given `peer`
* @param {PeerId|multiaddr|string} peer the peer to close connections to *
* @param {PeerInfo|PeerId|multiaddr|string} peer the peer to close connections to
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async hangUp (peer) { hangUp (peer) {
const { id } = getPeer(peer) const peerInfo = getPeerInfo(peer, this.peerStore)
return Promise.all(
const connections = this.connectionManager.connections.get(id.toB58String()) this.registrar.connections.get(peerInfo.id.toB58String()).map(connection => {
if (!connections) {
return
}
await Promise.all(
connections.map(connection => {
return connection.close() return connection.close()
}) })
) )
} }
/** /**
* Pings the given peer in order to obtain the operation latency. * Pings the given peer
* @param {PeerId|Multiaddr|string} peer The peer to ping * @param {PeerInfo|PeerId|Multiaddr|string} peer The peer to ping
* @returns {Promise<number>} * @returns {Promise<number>}
*/ */
ping (peer) { async ping (peer) {
const { id } = getPeer(peer) const peerInfo = await getPeerInfo(peer, this.peerStore)
return ping(this, id) return ping(this, peerInfo)
} }
/** /**
@ -431,11 +366,17 @@ class Libp2p extends EventEmitter {
} }
async _onStarting () { async _onStarting () {
// Listen on the provided transports // Listen on the addresses supplied in the peerInfo
await this.transportManager.listen() const multiaddrs = this.peerInfo.multiaddrs.toArray()
// Start PeerStore await this.transportManager.listen(multiaddrs)
await this.peerStore.start()
// 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)
}
if (this._config.pubsub.enabled) { if (this._config.pubsub.enabled) {
this.pubsub && this.pubsub.start() this.pubsub && this.pubsub.start()
@ -458,57 +399,55 @@ class Libp2p extends EventEmitter {
* Called when libp2p has started and before it returns * Called when libp2p has started and before it returns
* @private * @private
*/ */
async _onDidStart () { _onDidStart () {
this._isStarted = true this._isStarted = true
this.connectionManager.start() this.connectionManager.start()
this.peerStore.on('peer', peerId => { this.peerStore.on('peer', peerInfo => {
this.emit('peer:discovery', peerId) this.emit('peer:discovery', peerInfo)
this._maybeConnect(peerId) this._maybeConnect(peerInfo)
}) })
// Once we start, emit and dial any peers we may have already discovered
for (const peer of this.peerStore.peers.values()) {
this.emit('peer:discovery', peer.id)
this._maybeConnect(peer.id)
}
// Peer discovery // Peer discovery
await this._setupPeerDiscovery() 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. * Called whenever peer discovery services emit `peer` events.
* Known peers may be emitted. * Known peers may be emitted.
* @private * @private
* @param {{ id: PeerId, multiaddrs: Array<Multiaddr>, protocols: Array<string> }} peer * @param {PeerInfo} peerInfo
*/ */
_onDiscoveryPeer (peer) { _onDiscoveryPeer (peerInfo) {
if (peer.id.toB58String() === this.peerId.toB58String()) { if (peerInfo.id.toB58String() === this.peerInfo.id.toB58String()) {
log.error(new Error(codes.ERR_DISCOVERED_SELF)) log.error(new Error(codes.ERR_DISCOVERED_SELF))
return return
} }
this.peerStore.put(peerInfo)
peer.multiaddrs && this.peerStore.addressBook.add(peer.id, peer.multiaddrs)
peer.protocols && this.peerStore.protoBook.set(peer.id, peer.protocols)
} }
/** /**
* Will dial to the given `peerId` if the current number of * Will dial to the given `peerInfo` if the current number of
* connected peers is less than the configured `ConnectionManager` * connected peers is less than the configured `ConnectionManager`
* minPeers. * minPeers.
* @private * @private
* @param {PeerId} peerId * @param {PeerInfo} peerInfo
*/ */
async _maybeConnect (peerId) { async _maybeConnect (peerInfo) {
// If auto dialing is on and we have no connection to the peer, check if we should dial // 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.connectionManager.get(peerId)) { if (this._config.peerDiscovery.autoDial === true && !this.registrar.getConnection(peerInfo)) {
const minPeers = this._options.connectionManager.minPeers || 0 const minPeers = this._options.connectionManager.minPeers || 0
if (minPeers > this.connectionManager.size) { if (minPeers > this.connectionManager._connections.size) {
log('connecting to discovered peer %s', peerId.toB58String()) log('connecting to discovered peer %s', peerInfo.id.toB58String())
try { try {
await this.dialer.connectToPeer(peerId) await this.dialer.connectToPeer(peerInfo)
} catch (err) { } catch (err) {
log.error('could not connect to discovered peer', err) log.error('could not connect to discovered peer', err)
} }
@ -519,10 +458,10 @@ class Libp2p extends EventEmitter {
/** /**
* Initializes and starts peer discovery services * Initializes and starts peer discovery services
* *
* @async
* @private * @private
* @returns {Promise<void>}
*/ */
async _setupPeerDiscovery () { _setupPeerDiscovery () {
const setupService = (DiscoveryService) => { const setupService = (DiscoveryService) => {
let config = { let config = {
enabled: true // on by default enabled: true // on by default
@ -539,10 +478,7 @@ class Libp2p extends EventEmitter {
let discoveryService let discoveryService
if (typeof DiscoveryService === 'function') { if (typeof DiscoveryService === 'function') {
discoveryService = new DiscoveryService(Object.assign({}, config, { discoveryService = new DiscoveryService(Object.assign({}, config, { peerInfo: this.peerInfo }))
peerId: this.peerId,
libp2p: this
}))
} else { } else {
discoveryService = DiscoveryService discoveryService = DiscoveryService
} }
@ -564,25 +500,24 @@ class Libp2p extends EventEmitter {
} }
} }
await Promise.all(Array.from(this._discovery.values(), d => d.start())) return Promise.all(Array.from(this._discovery.values(), d => d.start()))
} }
} }
module.exports = Libp2p
/** /**
* Like `new Libp2p(options)` except it will create a `PeerId` * Like `new Libp2p(options)` except it will create a `PeerInfo`
* instance if one is not provided in options. * instance if one is not provided in options.
* @param {object} options Libp2p configuration options * @param {object} options Libp2p configuration options
* @returns {Libp2p} * @returns {Libp2p}
*/ */
Libp2p.create = async function create (options = {}) { module.exports.create = async (options = {}) => {
if (options.peerId) { if (options.peerInfo) {
return new Libp2p(options) return new Libp2p(options)
} }
const peerId = await PeerId.create() const peerInfo = await PeerInfo.create()
options.peerId = peerId options.peerInfo = peerInfo
return new Libp2p(options) return new Libp2p(options)
} }
module.exports = Libp2p

View File

@ -1,55 +0,0 @@
# js-libp2p-keychain
> A secure key chain for libp2p in JavaScript
## Features
- Manages the lifecycle of a key
- Keys are encrypted at rest
- Enforces the use of safe key names
- Uses encrypted PKCS 8 for key storage
- Uses PBKDF2 for a "stetched" key encryption key
- Enforces NIST SP 800-131A and NIST SP 800-132
- Uses PKCS 7: CMS (aka RFC 5652) to provide cryptographically protected messages
- Delays reporting errors to slow down brute force attacks
### KeyInfo
The key management and naming service API all return a `KeyInfo` object. The `id` is a universally unique identifier for the key. The `name` is local to the key chain.
```js
{
name: 'rsa-key',
id: 'QmYWYSUZ4PV6MRFYpdtEDJBiGs4UrmE6g8wmAWSePekXVW'
}
```
The **key id** is the SHA-256 [multihash](https://github.com/multiformats/multihash) of its public key. The *public key* is a [protobuf encoding](https://github.com/libp2p/js-libp2p-crypto/blob/master/src/keys/keys.proto.js) containing a type and the [DER encoding](https://en.wikipedia.org/wiki/X.690) of the PKCS [SubjectPublicKeyInfo](https://www.ietf.org/rfc/rfc3279.txt).
### Private key storage
A private key is stored as an encrypted PKCS 8 structure in the PEM format. It is protected by a key generated from the key chain's *passPhrase* using **PBKDF2**.
The default options for generating the derived encryption key are in the `dek` object. This, along with the passPhrase, is the input to a `PBKDF2` function.
```js
const defaultOptions = {
//See https://cryptosense.com/parameter-choice-for-pbkdf2/
dek: {
keyLength: 512 / 8,
iterationCount: 1000,
salt: 'at least 16 characters long',
hash: 'sha2-512'
}
}
```
![key storage](./doc/private-key.png?raw=true)
### Physical storage
The actual physical storage of an encrypted key is left to implementations of [interface-datastore](https://github.com/ipfs/interface-datastore/). A key benifit is that now the key chain can be used in browser with the [js-datastore-level](https://github.com/ipfs/js-datastore-level) implementation.
### Cryptographic Message Syntax (CMS)
CMS, aka [PKCS #7](https://en.wikipedia.org/wiki/PKCS) and [RFC 5652](https://tools.ietf.org/html/rfc5652), describes an encapsulation syntax for data protection. It is used to digitally sign, digest, authenticate, or encrypt arbitrary message content. Basically, `cms.encrypt` creates a DER message that can be only be read by someone holding the private key.

View File

@ -1,122 +0,0 @@
'use strict'
require('node-forge/lib/pkcs7')
require('node-forge/lib/pbe')
const forge = require('node-forge/lib/forge')
const { certificateForKey, findAsync } = require('./util')
const errcode = require('err-code')
/**
* Cryptographic Message Syntax (aka PKCS #7)
*
* CMS describes an encapsulation syntax for data protection. It
* is used to digitally sign, digest, authenticate, or encrypt
* arbitrary message content.
*
* See RFC 5652 for all the details.
*/
class CMS {
/**
* Creates a new instance with a keychain
*
* @param {Keychain} keychain - the available keys
*/
constructor (keychain) {
if (!keychain) {
throw errcode(new Error('keychain is required'), 'ERR_KEYCHAIN_REQUIRED')
}
this.keychain = keychain
}
/**
* Creates some protected data.
*
* The output Buffer contains the PKCS #7 message in DER.
*
* @param {string} name - The local key name.
* @param {Buffer} plain - The data to encrypt.
* @returns {undefined}
*/
async encrypt (name, plain) {
if (!Buffer.isBuffer(plain)) {
throw errcode(new Error('Plain data must be a Buffer'), 'ERR_INVALID_PARAMS')
}
const key = await this.keychain.findKeyByName(name)
const pem = await this.keychain._getPrivateKey(name)
const privateKey = forge.pki.decryptRsaPrivateKey(pem, this.keychain._())
const certificate = await certificateForKey(key, privateKey)
// create a p7 enveloped message
const p7 = forge.pkcs7.createEnvelopedData()
p7.addRecipient(certificate)
p7.content = forge.util.createBuffer(plain)
p7.encrypt()
// convert message to DER
const der = forge.asn1.toDer(p7.toAsn1()).getBytes()
return Buffer.from(der, 'binary')
}
/**
* Reads some protected data.
*
* The keychain must contain one of the keys used to encrypt the data. If none of the keys
* exists, an Error is returned with the property 'missingKeys'. It is array of key ids.
*
* @param {Buffer} cmsData - The CMS encrypted data to decrypt.
* @returns {undefined}
*/
async decrypt (cmsData) {
if (!Buffer.isBuffer(cmsData)) {
throw errcode(new Error('CMS data is required'), 'ERR_INVALID_PARAMS')
}
let cms
try {
const buf = forge.util.createBuffer(cmsData.toString('binary'))
const obj = forge.asn1.fromDer(buf)
cms = forge.pkcs7.messageFromAsn1(obj)
} catch (err) {
throw errcode(new Error('Invalid CMS: ' + err.message), 'ERR_INVALID_CMS')
}
// Find a recipient whose key we hold. We only deal with recipient certs
// issued by ipfs (O=ipfs).
const recipients = cms.recipients
.filter(r => r.issuer.find(a => a.shortName === 'O' && a.value === 'ipfs'))
.filter(r => r.issuer.find(a => a.shortName === 'CN'))
.map(r => {
return {
recipient: r,
keyId: r.issuer.find(a => a.shortName === 'CN').value
}
})
const r = await findAsync(recipients, async (recipient) => {
try {
const key = await this.keychain.findKeyById(recipient.keyId)
if (key) return true
} catch (err) {
return false
}
return false
})
if (!r) {
const missingKeys = recipients.map(r => r.keyId)
throw errcode(new Error('Decryption needs one of the key(s): ' + missingKeys.join(', ')), 'ERR_MISSING_KEYS', {
missingKeys
})
}
const key = await this.keychain.findKeyById(r.keyId)
const pem = await this.keychain._getPrivateKey(key.name)
const privateKey = forge.pki.decryptRsaPrivateKey(pem, this.keychain._())
cms.decrypt(r.recipient, privateKey)
return Buffer.from(cms.content.getBytes(), 'binary')
}
}
module.exports = CMS

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@ -1 +0,0 @@
<mxfile userAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36" version="7.8.2" editor="www.draw.io"><diagram id="a8b2919f-aefc-d24c-c550-ea0bf34e92af" name="Page-1">7VlNb6MwEP01HLfCGBJ6bNJ2V9pdqVIP2x4dcMAKYGScJumvXxNsvkw+SmgSVe2hMs9mbL839swQA07j9U+G0vAv9XFkWKa/NuC9YVmua4n/ObApAOjCAggY8QsIVMAzeccSNCW6JD7OGgM5pREnaRP0aJJgjzcwxBhdNYfNadScNUUB1oBnD0U6+o/4PJTbssYV/guTIFQzg9Ft0TND3iJgdJnI+QwLzrd/RXeMlC250SxEPl3VIPhgwCmjlBeteD3FUU6toq1473FHb7luhhN+zAtSpzcULeXWU5RluYmQoQzLRfKNIobjtbA7CXkcCQCIZsYZXeApjSgTSEITMXIyJ1HUglBEgkQ8emJlWOCTN8w4EZTfyY6Y+H4+zWQVEo6fU+Tlc66EfwlsSynOF22KJ7loYQCvd24clHQKL8U0xpxtxBDlolIA6aBgJJ9Xldy2hMKa0ko3JB0sKA1XJIuG5Lmbc6hx/jT5ff9oaWQL50jzZsqoh4Uq3dTUtBiAF9AmxtaJAVYHM6MBmLE1Zny8EABNOaFJ9nW9sfQryfr4fN7oaJxrNOPEv8sv1ZyvSFwPxGuSLjbJNi85GzcmGCvgdQvAUQk8YUbE8nK6a7xhX7uKD7JWo8XpoEVhDEeIk7em+S6u5AxPlIiJq6PQEgWMraaJjC6Zh+Vb9Uu2bUiFw12GOGIB5pqhrXTlto9SczSomk5Dyw9IJsL1dku1C+9SKpYHR5Fvmj1VhE1D2ukbTkX3WlQsuGmErbqw4KLnE5oHBDlWWbt10K22i+xQVgiANrVhaT4g271g22xfKI3kTDQKi33d5rY7fB4Mmgxn5B3NtgNy/5D7EKOdieHcfyhcRmiGo0mZBauwW+XBe+KlzOblSoxSz7pjunvj6A8RgcpaY9Mw3tfZ1BA6n2f41IOt6puaRAucrz/AiSbUNaR/Fjxj+geAxk668PJqRLiPexX8QPuS/OjVmo84yjhleqV2CXac9o18Vnb06uEm3e01PvWW8XZfh4iZFdn+n9mQTLWSCQhcjanRntB5ElF6yl9cQl++zGpfbo7unp9VZgE9M2dJoFFdbRmc5cRarRMLLd0P3S5KnAEoGWuUaHwcTHPXhL/U2q/NjPdF+k6tIHV6J8AqeF9PBtzyZxu2HLVvaQPdlqHhShswaG0zmLQdVWsRbb+lPV5avf44Qdpm2Vo/67JLnfb+oo86RDeNKxLdHkr0208TXcXGz/pW0S066C+61SG6/S36x0TXC7VTRP9SH43VLahyzHZpc/xHY7DfUG85xWP1A2MxvPoRFz78Bw==</diagram></mxfile>

View File

@ -1,469 +0,0 @@
/* eslint max-nested-callbacks: ["error", 5] */
'use strict'
const sanitize = require('sanitize-filename')
const mergeOptions = require('merge-options')
const crypto = require('libp2p-crypto')
const DS = require('interface-datastore')
const CMS = require('./cms')
const errcode = require('err-code')
const keyPrefix = '/pkcs8/'
const infoPrefix = '/info/'
// NIST SP 800-132
const NIST = {
minKeyLength: 112 / 8,
minSaltLength: 128 / 8,
minIterationCount: 1000
}
const defaultOptions = {
// See https://cryptosense.com/parametesr-choice-for-pbkdf2/
dek: {
keyLength: 512 / 8,
iterationCount: 10000,
salt: 'you should override this value with a crypto secure random number',
hash: 'sha2-512'
}
}
function validateKeyName (name) {
if (!name) return false
if (typeof name !== 'string') return false
return name === sanitize(name.trim())
}
/**
* Throws an error after a delay
*
* This assumes than an error indicates that the keychain is under attack. Delay returning an
* error to make brute force attacks harder.
*
* @param {string | Error} err - The error
* @private
*/
async function throwDelayed (err) {
const min = 200
const max = 1000
const delay = Math.random() * (max - min) + min
await new Promise(resolve => setTimeout(resolve, delay))
throw err
}
/**
* Converts a key name into a datastore name.
*
* @param {string} name
* @returns {DS.Key}
* @private
*/
function DsName (name) {
return new DS.Key(keyPrefix + name)
}
/**
* Converts a key name into a datastore info name.
*
* @param {string} name
* @returns {DS.Key}
* @private
*/
function DsInfoName (name) {
return new DS.Key(infoPrefix + name)
}
/**
* Information about a key.
*
* @typedef {Object} KeyInfo
*
* @property {string} id - The universally unique key id.
* @property {string} name - The local key name.
*/
/**
* Manages the lifecycle of a key. Keys are encrypted at rest using PKCS #8.
*
* A key in the store has two entries
* - '/info/*key-name*', contains the KeyInfo for the key
* - '/pkcs8/*key-name*', contains the PKCS #8 for the key
*
*/
class Keychain {
/**
* Creates a new instance of a key chain.
*
* @param {DS} store - where the key are.
* @param {object} options - ???
*/
constructor (store, options) {
if (!store) {
throw new Error('store is required')
}
this.store = store
this.opts = mergeOptions(defaultOptions, options)
// Enforce NIST SP 800-132
if (!this.opts.passPhrase || this.opts.passPhrase.length < 20) {
throw new Error('passPhrase must be least 20 characters')
}
if (this.opts.dek.keyLength < NIST.minKeyLength) {
throw new Error(`dek.keyLength must be least ${NIST.minKeyLength} bytes`)
}
if (this.opts.dek.salt.length < NIST.minSaltLength) {
throw new Error(`dek.saltLength must be least ${NIST.minSaltLength} bytes`)
}
if (this.opts.dek.iterationCount < NIST.minIterationCount) {
throw new Error(`dek.iterationCount must be least ${NIST.minIterationCount}`)
}
// Create the derived encrypting key
const dek = crypto.pbkdf2(
this.opts.passPhrase,
this.opts.dek.salt,
this.opts.dek.iterationCount,
this.opts.dek.keyLength,
this.opts.dek.hash)
Object.defineProperty(this, '_', { value: () => dek })
}
/**
* Gets an object that can encrypt/decrypt protected data
* using the Cryptographic Message Syntax (CMS).
*
* CMS describes an encapsulation syntax for data protection. It
* is used to digitally sign, digest, authenticate, or encrypt
* arbitrary message content.
*
* @returns {CMS}
*/
get cms () {
return new CMS(this)
}
/**
* Generates the options for a keychain. A random salt is produced.
*
* @returns {object}
*/
static generateOptions () {
const options = Object.assign({}, defaultOptions)
const saltLength = Math.ceil(NIST.minSaltLength / 3) * 3 // no base64 padding
options.dek.salt = crypto.randomBytes(saltLength).toString('base64')
return options
}
/**
* Gets an object that can encrypt/decrypt protected data.
* The default options for a keychain.
*
* @returns {object}
*/
static get options () {
return defaultOptions
}
/**
* Create a new key.
*
* @param {string} name - The local key name; cannot already exist.
* @param {string} type - One of the key types; 'rsa'.
* @param {int} size - The key size in bits.
* @returns {KeyInfo}
*/
async createKey (name, type, size) {
const self = this
if (!validateKeyName(name) || name === 'self') {
return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
}
if (typeof type !== 'string') {
return throwDelayed(errcode(new Error(`Invalid key type '${type}'`), 'ERR_INVALID_KEY_TYPE'))
}
if (!Number.isSafeInteger(size)) {
return throwDelayed(errcode(new Error(`Invalid key size '${size}'`), 'ERR_INVALID_KEY_SIZE'))
}
const dsname = DsName(name)
const exists = await self.store.has(dsname)
if (exists) return throwDelayed(errcode(new Error(`Key '${name}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
switch (type.toLowerCase()) {
case 'rsa':
if (size < 2048) {
return throwDelayed(errcode(new Error(`Invalid RSA key size ${size}`), 'ERR_INVALID_KEY_SIZE'))
}
break
default:
break
}
let keyInfo
try {
const keypair = await crypto.keys.generateKeyPair(type, size)
const kid = await keypair.id()
const pem = await keypair.export(this._())
keyInfo = {
name: name,
id: kid
}
const batch = self.store.batch()
batch.put(dsname, pem)
batch.put(DsInfoName(name), JSON.stringify(keyInfo))
await batch.commit()
} catch (err) {
return throwDelayed(err)
}
return keyInfo
}
/**
* List all the keys.
*
* @returns {KeyInfo[]}
*/
async listKeys () {
const self = this
const query = {
prefix: infoPrefix
}
const info = []
for await (const value of self.store.query(query)) {
info.push(JSON.parse(value.value))
}
return info
}
/**
* Find a key by it's id.
*
* @param {string} id - The universally unique key identifier.
* @returns {KeyInfo}
*/
async findKeyById (id) {
try {
const keys = await this.listKeys()
return keys.find((k) => k.id === id)
} catch (err) {
return throwDelayed(err)
}
}
/**
* Find a key by it's name.
*
* @param {string} name - The local key name.
* @returns {KeyInfo}
*/
async findKeyByName (name) {
if (!validateKeyName(name)) {
return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
}
const dsname = DsInfoName(name)
try {
const res = await this.store.get(dsname)
return JSON.parse(res.toString())
} catch (err) {
return throwDelayed(errcode(new Error(`Key '${name}' does not exist. ${err.message}`), 'ERR_KEY_NOT_FOUND'))
}
}
/**
* Remove an existing key.
*
* @param {string} name - The local key name; must already exist.
* @returns {KeyInfo}
*/
async removeKey (name) {
const self = this
if (!validateKeyName(name) || name === 'self') {
return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
}
const dsname = DsName(name)
const keyInfo = await self.findKeyByName(name)
const batch = self.store.batch()
batch.delete(dsname)
batch.delete(DsInfoName(name))
await batch.commit()
return keyInfo
}
/**
* Rename a key
*
* @param {string} oldName - The old local key name; must already exist.
* @param {string} newName - The new local key name; must not already exist.
* @returns {KeyInfo}
*/
async renameKey (oldName, newName) {
const self = this
if (!validateKeyName(oldName) || oldName === 'self') {
return throwDelayed(errcode(new Error(`Invalid old key name '${oldName}'`), 'ERR_OLD_KEY_NAME_INVALID'))
}
if (!validateKeyName(newName) || newName === 'self') {
return throwDelayed(errcode(new Error(`Invalid new key name '${newName}'`), 'ERR_NEW_KEY_NAME_INVALID'))
}
const oldDsname = DsName(oldName)
const newDsname = DsName(newName)
const oldInfoName = DsInfoName(oldName)
const newInfoName = DsInfoName(newName)
const exists = await self.store.has(newDsname)
if (exists) return throwDelayed(errcode(new Error(`Key '${newName}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
try {
let res = await this.store.get(oldDsname)
const pem = res.toString()
res = await self.store.get(oldInfoName)
const keyInfo = JSON.parse(res.toString())
keyInfo.name = newName
const batch = self.store.batch()
batch.put(newDsname, pem)
batch.put(newInfoName, JSON.stringify(keyInfo))
batch.delete(oldDsname)
batch.delete(oldInfoName)
await batch.commit()
return keyInfo
} catch (err) {
return throwDelayed(err)
}
}
/**
* Export an existing key as a PEM encrypted PKCS #8 string
*
* @param {string} name - The local key name; must already exist.
* @param {string} password - The password
* @returns {string}
*/
async exportKey (name, password) {
if (!validateKeyName(name)) {
return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
}
if (!password) {
return throwDelayed(errcode(new Error('Password is required'), 'ERR_PASSWORD_REQUIRED'))
}
const dsname = DsName(name)
try {
const res = await this.store.get(dsname)
const pem = res.toString()
const privateKey = await crypto.keys.import(pem, this._())
return privateKey.export(password)
} catch (err) {
return throwDelayed(err)
}
}
/**
* Import a new key from a PEM encoded PKCS #8 string
*
* @param {string} name - The local key name; must not already exist.
* @param {string} pem - The PEM encoded PKCS #8 string
* @param {string} password - The password.
* @returns {KeyInfo}
*/
async importKey (name, pem, password) {
const self = this
if (!validateKeyName(name) || name === 'self') {
return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
}
if (!pem) {
return throwDelayed(errcode(new Error('PEM encoded key is required'), 'ERR_PEM_REQUIRED'))
}
const dsname = DsName(name)
const exists = await self.store.has(dsname)
if (exists) return throwDelayed(errcode(new Error(`Key '${name}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
let privateKey
try {
privateKey = await crypto.keys.import(pem, password)
} catch (err) {
return throwDelayed(errcode(new Error('Cannot read the key, most likely the password is wrong'), 'ERR_CANNOT_READ_KEY'))
}
let kid
try {
kid = await privateKey.id()
pem = await privateKey.export(this._())
} catch (err) {
return throwDelayed(err)
}
const keyInfo = {
name: name,
id: kid
}
const batch = self.store.batch()
batch.put(dsname, pem)
batch.put(DsInfoName(name), JSON.stringify(keyInfo))
await batch.commit()
return keyInfo
}
async importPeer (name, peer) {
const self = this
if (!validateKeyName(name)) {
return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
}
if (!peer || !peer.privKey) {
return throwDelayed(errcode(new Error('Peer.privKey is required'), 'ERR_MISSING_PRIVATE_KEY'))
}
const privateKey = peer.privKey
const dsname = DsName(name)
const exists = await self.store.has(dsname)
if (exists) return throwDelayed(errcode(new Error(`Key '${name}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
try {
const kid = await privateKey.id()
const pem = await privateKey.export(this._())
const keyInfo = {
name: name,
id: kid
}
const batch = self.store.batch()
batch.put(dsname, pem)
batch.put(DsInfoName(name), JSON.stringify(keyInfo))
await batch.commit()
return keyInfo
} catch (err) {
return throwDelayed(err)
}
}
/**
* Gets the private key as PEM encoded PKCS #8 string.
*
* @param {string} name
* @returns {string}
* @private
*/
async _getPrivateKey (name) {
if (!validateKeyName(name)) {
return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
}
try {
const dsname = DsName(name)
const res = await this.store.get(dsname)
return res.toString()
} catch (err) {
return throwDelayed(errcode(new Error(`Key '${name}' does not exist. ${err.message}`), 'ERR_KEY_NOT_FOUND'))
}
}
}
module.exports = Keychain

View File

@ -1,89 +0,0 @@
'use strict'
require('node-forge/lib/x509')
const forge = require('node-forge/lib/forge')
const pki = forge.pki
exports = module.exports
/**
* Gets a self-signed X.509 certificate for the key.
*
* The output Buffer contains the PKCS #7 message in DER.
*
* TODO: move to libp2p-crypto package
*
* @param {KeyInfo} key - The id and name of the key
* @param {RsaPrivateKey} privateKey - The naked key
* @returns {undefined}
*/
exports.certificateForKey = (key, privateKey) => {
const publicKey = pki.setRsaPublicKey(privateKey.n, privateKey.e)
const cert = pki.createCertificate()
cert.publicKey = publicKey
cert.serialNumber = '01'
cert.validity.notBefore = new Date()
cert.validity.notAfter = new Date()
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 10)
const attrs = [{
name: 'organizationName',
value: 'ipfs'
}, {
shortName: 'OU',
value: 'keystore'
}, {
name: 'commonName',
value: key.id
}]
cert.setSubject(attrs)
cert.setIssuer(attrs)
cert.setExtensions([{
name: 'basicConstraints',
cA: true
}, {
name: 'keyUsage',
keyCertSign: true,
digitalSignature: true,
nonRepudiation: true,
keyEncipherment: true,
dataEncipherment: true
}, {
name: 'extKeyUsage',
serverAuth: true,
clientAuth: true,
codeSigning: true,
emailProtection: true,
timeStamping: true
}, {
name: 'nsCertType',
client: true,
server: true,
email: true,
objsign: true,
sslCA: true,
emailCA: true,
objCA: true
}])
// self-sign certificate
cert.sign(privateKey)
return cert
}
/**
* Finds the first item in a collection that is matched in the
* `asyncCompare` function.
*
* `asyncCompare` is an async function that must
* resolve to either `true` or `false`.
*
* @param {Array} array
* @param {function(*)} asyncCompare An async function that returns a boolean
*/
async function findAsync (array, asyncCompare) {
const promises = array.map(asyncCompare)
const results = await Promise.all(promises)
const index = results.findIndex(result => result)
return array[index]
}
module.exports.findAsync = findAsync

View File

@ -21,7 +21,6 @@ class Metrics {
/** /**
* *
* @param {object} options * @param {object} options
* @param {ConnectionManager} options.connectionManager
* @param {number} options.computeThrottleMaxQueueSize * @param {number} options.computeThrottleMaxQueueSize
* @param {number} options.computeThrottleTimeout * @param {number} options.computeThrottleTimeout
* @param {Array<number>} options.movingAverageIntervals * @param {Array<number>} options.movingAverageIntervals
@ -35,10 +34,6 @@ class Metrics {
this._oldPeers = oldPeerLRU(this._options.maxOldPeersRetention) this._oldPeers = oldPeerLRU(this._options.maxOldPeersRetention)
this._running = false this._running = false
this._onMessage = this._onMessage.bind(this) this._onMessage = this._onMessage.bind(this)
this._connectionManager = options.connectionManager
this._connectionManager.on('peer:disconnect', (connection) => {
this.onPeerDisconnected(connection.remotePeer)
})
} }
/** /**

View File

@ -18,7 +18,7 @@ module.exports = (node) => {
* @param {String} id The id of the peer to find * @param {String} id The id of the peer to find
* @param {object} [options] * @param {object} [options]
* @param {number} [options.timeout] How long the query should run * @param {number} [options.timeout] How long the query should run
* @returns {Promise<{ id: PeerId, multiaddrs: Multiaddr[] }>} * @returns {Promise<PeerInfo>}
*/ */
findPeer: async (id, options) => { // eslint-disable-line require-await findPeer: async (id, options) => { // eslint-disable-line require-await
if (!routers.length) { if (!routers.length) {

View File

@ -1,145 +1,3 @@
# PeerStore # Peerstore
Libp2p's PeerStore is responsible for keeping an updated register with the relevant information of the known peers. It should be the single source of truth for all peer data, where a subsystem can learn about peers' data and where someone can listen for updates. The PeerStore comprises four main components: `addressBook`, `keyBook`, `protocolBook` and `metadataBook`. WIP
The PeerStore manages the high level operations on its inner books. Moreover, the PeerStore should be responsible for notifying interested parties of relevant events, through its Event Emitter.
## Submitting records to the PeerStore
Several libp2p subsystems will perform operations that might gather relevant information about peers.
### Identify
- The Identify protocol automatically runs on every connection when multiplexing is enabled. The protocol will put the multiaddrs and protocols provided by the peer to the PeerStore.
- In the background, the Identify Service is also waiting for protocol change notifications of peers via the IdentifyPush protocol. Peers may leverage the `identify-push` message to communicate protocol changes to all connected peers, so that their PeerStore can be updated with the updated protocols.
- While it is currently not supported in js-libp2p, future iterations may also support the [IdentifyDelta protocol](https://github.com/libp2p/specs/pull/176).
- Taking into account that the Identify protocol records are directly from the peer, they should be considered the source of truth and weighted accordingly.
### Peer Discovery
- Libp2p discovery protocols aim to discover new peers in the network. In a typical discovery protocol, addresses of the peer are discovered along with its peer id. Once this happens, a libp2p discovery protocol should emit a `peer` event with the information of the discovered peer and this information will be added to the PeerStore by libp2p.
### Dialer
- Libp2p API supports dialing a peer given a `multiaddr`, and no prior knowledge of the peer. If the node is able to establish a connection with the peer, it and its multiaddr is added to the PeerStore.
- When a connection is being upgraded, more precisely after its encryption, or even in a discovery protocol, a libp2p node can get to know other parties public keys. In this scenario, libp2p will add the peer's public key to its `KeyBook`.
### DHT
- On some DHT operations, such as finding providers for a given CID, nodes may exchange peer data as part of the query. This passive peer discovery should result in the DHT emitting the `peer` event in the same way [Peer Discovery](#peerdiscovery) does.
## Retrieving records from the PeerStore
When data in the PeerStore is updated the PeerStore will emit events based on the changes, to allow applications and other subsystems to take action on those changes. Any subsystem interested in these notifications should subscribe the [`PeerStore events`][peer-store-events].
### Peer
- Each time a new peer is discovered, the PeerStore should emit a [`peer` event][peer-store-events], so that interested parties can leverage this peer and establish a connection with it.
### Protocols
- When the known protocols of a peer change, the PeerStore emits a [`change:protocols` event][peer-store-events].
### Multiaddrs
- When the known listening `multiaddrs` of a peer change, the PeerStore emits a [`change:multiaddrs` event][peer-store-events].
## PeerStore implementation
The PeerStore wraps four main components: `addressBook`, `keyBook`, `protocolBook` and `metadataBook`. Moreover, it provides a high level API for those components, as well as data events.
### Components
#### Address Book
The `addressBook` keeps the known multiaddrs of a peer. The multiaddrs of each peer may change over time and the Address Book must account for this.
`Map<string, Address>`
A `peerId.toB58String()` identifier mapping to a `Address` object, which should have the following structure:
```js
{
multiaddr: <Multiaddr>
}
```
#### Key Book
The `keyBook` tracks the public keys of the peers by keeping their [`PeerId`][peer-id].
`Map<string, PeerId`
A `peerId.toB58String()` identifier mapping to a `PeerId` of the peer. This instance contains the peer public key.
#### Protocol Book
The `protoBook` holds the identifiers of the protocols supported by each peer. The protocols supported by each peer are dynamic and will change over time.
`Map<string, Set<string>>`
A `peerId.toB58String()` identifier mapping to a `Set` of protocol identifier strings.
#### Metadata Book
The `metadataBook` keeps track of the known metadata of a peer. Its metadata is stored in a key value fashion, where a key identifier (`string`) represents a metadata value (`Buffer`).
`Map<string, Map<string, Buffer>>`
A `peerId.toB58String()` identifier mapping to the peer metadata Map.
### API
For the complete API documentation, you should check the [API.md](../../doc/API.md).
Access to its underlying books:
- `peerStore.addressBook.*`
- `peerStore.keyBook.*`
- `peerStore.metadataBook.*`
- `peerStore.protoBook.*`
### Events
- `peer` - emitted when a new peer is added.
- `change:multiaadrs` - emitted when a known peer has a different set of multiaddrs.
- `change:protocols` - emitted when a known peer supports a different set of protocols.
- `change:pubkey` - emitted when a peer's public key is known.
- `change:metadata` - emitted when known metadata of a peer changes.
## Data Persistence
The data stored in the PeerStore can be persisted if configured appropriately. Keeping a record of the peers already discovered by the peer, as well as their known data aims to improve the efficiency of peers joining the network after being offline.
The libp2p node will need to receive a [datastore](https://github.com/ipfs/interface-datastore), in order to persist this data across restarts. A [datastore](https://github.com/ipfs/interface-datastore) stores its data in a key-value fashion. As a result, we need coherent keys so that we do not overwrite data.
The PeerStore should not continuously update the datastore whenever data is changed. Instead, it should only store new data after reaching a certain threshold of "dirty" peers, as well as when the node is stopped, in order to batch writes to the datastore.
The peer id will be appended to the datastore key for each data namespace. The namespaces were defined as follows:
**AddressBook**
All the known peer addresses are stored with a key pattern as follows:
`/peers/addrs/<b32 peer id no padding>`
**ProtoBook**
All the known peer protocols are stored with a key pattern as follows:
`/peers/protos/<b32 peer id no padding>`
**KeyBook**
All public keys are stored under the following pattern:
` /peers/keys/<b32 peer id no padding>`
**MetadataBook**
Metadata is stored under the following key pattern:
`/peers/metadata/<b32 peer id no padding>/<key>`
## Future Considerations
- If multiaddr TTLs are added, the PeerStore may schedule jobs to delete all addresses that exceed the TTL to prevent AddressBook bloating
- Further API methods will probably need to be added in the context of multiaddr validity and confidence.
- When improving libp2p configuration for specific runtimes, we should take into account the PeerStore recommended datastore.
- When improving libp2p configuration, we should think about a possible way of allowing the configuration of Bootstrap to be influenced by the persisted peers, as a way to decrease the load on Bootstrap nodes.
[peer-id]: https://github.com/libp2p/js-peer-id
[peer-store-events]: ../../doc/API.md#libp2ppeerstore

View File

@ -1,196 +0,0 @@
'use strict'
const errcode = require('err-code')
const debug = require('debug')
const log = debug('libp2p:peer-store:address-book')
log.error = debug('libp2p:peer-store:address-book:error')
const multiaddr = require('multiaddr')
const PeerId = require('peer-id')
const Book = require('./book')
const {
codes: { ERR_INVALID_PARAMETERS }
} = require('../errors')
/**
* The AddressBook is responsible for keeping the known multiaddrs
* of a peer.
*/
class AddressBook extends Book {
/**
* Address object
* @typedef {Object} Address
* @property {Multiaddr} multiaddr peer multiaddr.
*/
/**
* @constructor
* @param {PeerStore} peerStore
*/
constructor (peerStore) {
/**
* PeerStore Event emitter, used by the AddressBook to emit:
* "peer" - emitted when a peer is discovered by the node.
* "change:multiaddrs" - emitted when the known multiaddrs of a peer change.
*/
super({
peerStore,
eventName: 'change:multiaddrs',
eventProperty: 'multiaddrs',
eventTransformer: (data) => data.map((address) => address.multiaddr)
})
/**
* Map known peers to their known Addresses.
* @type {Map<string, Array<Address>>}
*/
this.data = new Map()
}
/**
* Set known multiaddrs of a provided peer.
* @override
* @param {PeerId} peerId
* @param {Array<Multiaddr>} multiaddrs
* @returns {AddressBook}
*/
set (peerId, multiaddrs) {
if (!PeerId.isPeerId(peerId)) {
log.error('peerId must be an instance of peer-id to store data')
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
const addresses = this._toAddresses(multiaddrs)
const id = peerId.toB58String()
const rec = this.data.get(id)
// Not replace multiaddrs
if (!addresses.length) {
return this
}
// Already knows the peer
if (rec && rec.length === addresses.length) {
const intersection = rec.filter((mi) => addresses.some((newMi) => mi.multiaddr.equals(newMi.multiaddr)))
// Are new addresses equal to the old ones?
// If yes, no changes needed!
if (intersection.length === rec.length) {
log(`the addresses provided to store are equal to the already stored for ${id}`)
return this
}
}
this._setData(peerId, addresses)
log(`stored provided multiaddrs for ${id}`)
// Notify the existance of a new peer
if (!rec) {
this._ps.emit('peer', peerId)
}
return this
}
/**
* Add known addresses of a provided peer.
* If the peer is not known, it is set with the given addresses.
* @param {PeerId} peerId
* @param {Array<Multiaddr>} multiaddrs
* @returns {AddressBook}
*/
add (peerId, multiaddrs) {
if (!PeerId.isPeerId(peerId)) {
log.error('peerId must be an instance of peer-id to store data')
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
const addresses = this._toAddresses(multiaddrs)
const id = peerId.toB58String()
const rec = this.data.get(id)
// Add recorded uniquely to the new array (Union)
rec && rec.forEach((mi) => {
if (!addresses.find(r => r.multiaddr.equals(mi.multiaddr))) {
addresses.push(mi)
}
})
// If the recorded length is equal to the new after the unique union
// The content is the same, no need to update.
if (rec && rec.length === addresses.length) {
log(`the addresses provided to store are already stored for ${id}`)
return this
}
this._setData(peerId, addresses)
log(`added provided multiaddrs for ${id}`)
// Notify the existance of a new peer
if (!rec) {
this._ps.emit('peer', peerId)
}
return this
}
/**
* Transforms received multiaddrs into Address.
* @private
* @param {Array<Multiaddr>} multiaddrs
* @returns {Array<Address>}
*/
_toAddresses (multiaddrs) {
if (!multiaddrs) {
log.error('multiaddrs must be provided to store data')
throw errcode(new Error('multiaddrs must be provided'), ERR_INVALID_PARAMETERS)
}
// create Address for each address
const addresses = []
multiaddrs.forEach((addr) => {
if (!multiaddr.isMultiaddr(addr)) {
log.error(`multiaddr ${addr} must be an instance of multiaddr`)
throw errcode(new Error(`multiaddr ${addr} must be an instance of multiaddr`), ERR_INVALID_PARAMETERS)
}
addresses.push({
multiaddr: addr
})
})
return addresses
}
/**
* Get the known multiaddrs for a given peer. All returned multiaddrs
* will include the encapsulated `PeerId` of the peer.
* @param {PeerId} peerId
* @returns {Array<Multiaddr>}
*/
getMultiaddrsForPeer (peerId) {
if (!PeerId.isPeerId(peerId)) {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
const record = this.data.get(peerId.toB58String())
if (!record) {
return undefined
}
return record.map((address) => {
const multiaddr = address.multiaddr
const idString = multiaddr.getPeerId()
if (idString && idString === peerId.toB58String()) return multiaddr
return multiaddr.encapsulate(`/p2p/${peerId.toB58String()}`)
})
}
}
module.exports = AddressBook

View File

@ -1,113 +0,0 @@
'use strict'
const errcode = require('err-code')
const PeerId = require('peer-id')
const {
codes: { ERR_INVALID_PARAMETERS }
} = require('../errors')
const passthrough = data => data
/**
* The Book is the skeleton for the PeerStore books.
*/
class Book {
/**
* @constructor
* @param {Object} properties
* @param {PeerStore} properties.peerStore PeerStore instance.
* @param {string} properties.eventName Name of the event to emit by the PeerStore.
* @param {string} properties.eventProperty Name of the property to emit by the PeerStore.
* @param {function} [properties.eventTransformer] Transformer function of the provided data for being emitted.
*/
constructor ({ peerStore, eventName, eventProperty, eventTransformer = passthrough }) {
this._ps = peerStore
this.eventName = eventName
this.eventProperty = eventProperty
this.eventTransformer = eventTransformer
/**
* Map known peers to their data.
* @type {Map<string, Array<Data>}
*/
this.data = new Map()
}
/**
* Set known data of a provided peer.
* @param {PeerId} peerId
* @param {Array<Data>|Data} data
*/
set (peerId, data) {
throw errcode(new Error('set must be implemented by the subclass'), 'ERR_NOT_IMPLEMENTED')
}
/**
* Set data into the datastructure, persistence and emit it using the provided transformers.
* @private
* @param {PeerId} peerId peerId of the data to store
* @param {*} data data to store.
* @param {Object} [options] storing options.
* @param {boolean} [options.emit = true] emit the provided data.
* @return {void}
*/
_setData (peerId, data, { emit = true } = {}) {
const b58key = peerId.toB58String()
// Store data in memory
this.data.set(b58key, data)
// Emit event
emit && this._emit(peerId, data)
}
/**
* Emit data.
* @private
* @param {PeerId} peerId
* @param {*} data
*/
_emit (peerId, data) {
this._ps.emit(this.eventName, {
peerId,
[this.eventProperty]: this.eventTransformer(data)
})
}
/**
* Get the known data of a provided peer.
* @param {PeerId} peerId
* @returns {Array<Data>}
*/
get (peerId) {
if (!PeerId.isPeerId(peerId)) {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
const rec = this.data.get(peerId.toB58String())
return rec ? [...rec] : undefined
}
/**
* Deletes the provided peer from the book.
* @param {PeerId} peerId
* @returns {boolean}
*/
delete (peerId) {
if (!PeerId.isPeerId(peerId)) {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
if (!this.data.delete(peerId.toB58String())) {
return false
}
this._emit(peerId, [])
return true
}
}
module.exports = Book

View File

@ -1,135 +1,232 @@
'use strict' 'use strict'
const errcode = require('err-code') const assert = require('assert')
const debug = require('debug') const debug = require('debug')
const log = debug('libp2p:peer-store') const log = debug('libp2p:peer-store')
log.error = debug('libp2p:peer-store:error') log.error = debug('libp2p:peer-store:error')
const { EventEmitter } = require('events') const { EventEmitter } = require('events')
const PeerId = require('peer-id') const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const AddressBook = require('./address-book')
const KeyBook = require('./key-book')
const MetadataBook = require('./metadata-book')
const ProtoBook = require('./proto-book')
const {
ERR_INVALID_PARAMETERS
} = require('../errors')
/** /**
* Responsible for managing known peers, as well as their addresses, protocols and metadata. * Responsible for managing known peers, as well as their addresses and metadata
* @fires PeerStore#peer Emitted when a new peer is added. * @fires PeerStore#peer Emitted when a peer is connected to this node
* @fires PeerStore#change:protocols Emitted when a known peer supports a different set of protocols. * @fires PeerStore#change:protocols
* @fires PeerStore#change:multiaddrs Emitted when a known peer has a different set of multiaddrs. * @fires PeerStore#change:multiaddrs
* @fires PeerStore#change:pubkey Emitted emitted when a peer's public key is known.
* @fires PeerStore#change:metadata Emitted when the known metadata of a peer change.
*/ */
class PeerStore extends EventEmitter { class PeerStore extends EventEmitter {
/**
* Peer object
* @typedef {Object} Peer
* @property {PeerId} id peer's peer-id instance.
* @property {Array<Address>} addresses peer's addresses containing its multiaddrs and metadata.
* @property {Array<string>} protocols peer's supported protocols.
*/
/**
* @constructor
*/
constructor () { constructor () {
super() super()
/** /**
* AddressBook containing a map of peerIdStr to Address. * Map of peers
*
* @type {Map<string, PeerInfo>}
*/ */
this.addressBook = new AddressBook(this) this.peers = new Map()
/** // TODO: Track ourselves. We should split `peerInfo` up into its pieces so we get better
* KeyBook containing a map of peerIdStr to their PeerId with public keys. // control and observability. This will be the initial step for removing PeerInfo
*/ // https://github.com/libp2p/go-libp2p-core/blob/master/peerstore/peerstore.go
this.keyBook = new KeyBook(this) // this.addressBook = new Map()
// this.protoBook = new Map()
/**
* MetadataBook containing a map of peerIdStr to their metadata Map.
*/
this.metadataBook = new MetadataBook(this)
/**
* ProtoBook containing a map of peerIdStr to supported protocols.
*/
this.protoBook = new ProtoBook(this)
} }
/** /**
* Start the PeerStore. * 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.
* @param {PeerInfo} peerInfo
* @param {object} [options]
* @param {boolean} [options.silent] (Default=false)
* @return {PeerInfo}
*/ */
start () {} put (peerInfo, options = { silent: false }) {
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)
} else {
peer = this.add(peerInfo)
// Emit the peer if silent = false
!options.silent && this.emit('peer', peerInfo)
}
return peer
}
/** /**
* Stop the PeerStore. * Add a new peer to the store.
* @param {PeerInfo} peerInfo
* @return {PeerInfo}
*/ */
stop () {} add (peerInfo) {
assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
/** // Create new instance and add values to it
* Get all the stored information of every peer. const newPeerInfo = new PeerInfo(peerInfo.id)
* @returns {Map<string, Peer>}
*/
get peers () {
const storedPeers = new Set([
...this.addressBook.data.keys(),
...this.keyBook.data.keys(),
...this.protoBook.data.keys(),
...this.metadataBook.data.keys()
])
const peersData = new Map() peerInfo.multiaddrs.forEach((ma) => newPeerInfo.multiaddrs.add(ma))
storedPeers.forEach((idStr) => { peerInfo.protocols.forEach((p) => newPeerInfo.protocols.add(p))
peersData.set(idStr, this.get(PeerId.createFromCID(idStr)))
const connectedMa = peerInfo.isConnected()
connectedMa && newPeerInfo.connect(connectedMa)
const peerProxy = new Proxy(newPeerInfo, {
set: (obj, prop, value) => {
if (prop === 'multiaddrs') {
this.emit('change:multiaddrs', {
peerInfo: obj,
multiaddrs: value.toArray()
})
} else if (prop === 'protocols') {
this.emit('change:protocols', {
peerInfo: obj,
protocols: Array.from(value)
})
}
return Reflect.set(...arguments)
}
}) })
return peersData this.peers.set(peerInfo.id.toB58String(), peerProxy)
return peerProxy
} }
/** /**
* Delete the information of the given peer in every book. * Updates an already known peer.
* @param {PeerId} peerId * @param {PeerInfo} peerInfo
* @returns {boolean} true if found and removed * @return {PeerInfo}
*/ */
delete (peerId) { update (peerInfo) {
const addressesDeleted = this.addressBook.delete(peerId) assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
const keyDeleted = this.keyBook.delete(peerId) const id = peerInfo.id.toB58String()
const protocolsDeleted = this.protoBook.delete(peerId) const recorded = this.peers.get(id)
const metadataDeleted = this.metadataBook.delete(peerId)
return addressesDeleted || keyDeleted || protocolsDeleted || metadataDeleted // pass active connection state
const ma = peerInfo.isConnected()
if (ma) {
recorded.connect(ma)
}
// Verify new multiaddrs
// TODO: better track added and removed multiaddrs
const multiaddrsIntersection = [
...recorded.multiaddrs.toArray()
].filter((m) => peerInfo.multiaddrs.has(m))
if (multiaddrsIntersection.length !== peerInfo.multiaddrs.size ||
multiaddrsIntersection.length !== recorded.multiaddrs.size) {
for (const ma of peerInfo.multiaddrs.toArray()) {
recorded.multiaddrs.add(ma)
}
this.emit('change:multiaddrs', {
peerInfo: recorded,
multiaddrs: recorded.multiaddrs.toArray()
})
}
let isProtocolsChanged = false
for (const protocol of peerInfo.protocols) {
if (!recorded.protocols.has(protocol)) {
isProtocolsChanged = true
recorded.protocols.add(protocol)
}
}
if (isProtocolsChanged) {
this.emit('change:protocols', {
peerInfo: recorded,
protocols: Array.from(recorded.protocols)
})
}
// Add the public key if missing
if (!recorded.id.pubKey && peerInfo.id.pubKey) {
recorded.id.pubKey = peerInfo.id.pubKey
}
return recorded
} }
/** /**
* Get the stored information of a given peer. * Get the info to the given id.
* @param {PeerId} peerId * @param {PeerId|string} peerId b58str id
* @returns {Peer} * @returns {PeerInfo}
*/ */
get (peerId) { get (peerId) {
if (!PeerId.isPeerId(peerId)) { // TODO: deprecate this and just accept `PeerId` instances
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) if (PeerId.isPeerId(peerId)) {
peerId = peerId.toB58String()
} }
const id = this.keyBook.data.get(peerId.toB58String()) return this.peers.get(peerId)
const addresses = this.addressBook.get(peerId) }
const metadata = this.metadataBook.get(peerId)
const protocols = this.protoBook.get(peerId)
if (!id && !addresses && !metadata && !protocols) { /**
return undefined * 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 { return this.peers.has(peerId)
id: id || peerId, }
addresses: addresses || [],
protocols: protocols || [], /**
metadata: metadata * Removes the Peer with the matching `peerId` from the PeerStore
* @param {PeerId|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)
}
/**
* Completely replaces the existing peers metadata with the given `peerInfo`
* @param {PeerInfo} peerInfo
* @returns {void}
*/
replace (peerInfo) {
assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
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

@ -1,85 +0,0 @@
'use strict'
const errcode = require('err-code')
const debug = require('debug')
const log = debug('libp2p:peer-store:key-book')
log.error = debug('libp2p:peer-store:key-book:error')
const PeerId = require('peer-id')
const Book = require('./book')
const {
codes: { ERR_INVALID_PARAMETERS }
} = require('../errors')
/**
* The KeyBook is responsible for keeping the known public keys of a peer.
*/
class KeyBook extends Book {
/**
* @constructor
* @param {PeerStore} peerStore
*/
constructor (peerStore) {
super({
peerStore,
eventName: 'change:pubkey',
eventProperty: 'pubkey',
eventTransformer: (data) => data.pubKey
})
/**
* Map known peers to their known Public Key.
* @type {Map<string, PeerId>}
*/
this.data = new Map()
}
/**
* Set the Peer public key.
* @override
* @param {PeerId} peerId
* @param {RsaPublicKey|Ed25519PublicKey|Secp256k1PublicKey} publicKey
* @return {KeyBook}
*/
set (peerId, publicKey) {
if (!PeerId.isPeerId(peerId)) {
log.error('peerId must be an instance of peer-id to store data')
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
const id = peerId.toB58String()
const recPeerId = this.data.get(id)
// If no record available, and this is valid
if (!recPeerId && publicKey) {
// This might be unecessary, but we want to store the PeerId
// to avoid an async operation when reconstructing the PeerId
peerId.pubKey = publicKey
this._setData(peerId, peerId)
log(`stored provided public key for ${id}`)
}
return this
}
/**
* Get Public key of the given PeerId, if stored.
* @override
* @param {PeerId} peerId
* @return {RsaPublicKey|Ed25519PublicKey|Secp256k1PublicKey}
*/
get (peerId) {
if (!PeerId.isPeerId(peerId)) {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
const rec = this.data.get(peerId.toB58String())
return rec ? rec.pubKey : undefined
}
}
module.exports = KeyBook

View File

@ -1,161 +0,0 @@
'use strict'
const errcode = require('err-code')
const debug = require('debug')
const log = debug('libp2p:peer-store:proto-book')
log.error = debug('libp2p:peer-store:proto-book:error')
const { Buffer } = require('buffer')
const PeerId = require('peer-id')
const Book = require('./book')
const {
codes: { ERR_INVALID_PARAMETERS }
} = require('../errors')
/**
* The MetadataBook is responsible for keeping the known supported
* protocols of a peer.
* @fires MetadataBook#change:metadata
*/
class MetadataBook extends Book {
/**
* @constructor
* @param {PeerStore} peerStore
*/
constructor (peerStore) {
/**
* PeerStore Event emitter, used by the MetadataBook to emit:
* "change:metadata" - emitted when the known metadata of a peer change.
*/
super({
peerStore,
eventName: 'change:metadata',
eventProperty: 'metadata'
})
/**
* Map known peers to their known protocols.
* @type {Map<string, Map<string, Buffer>>}
*/
this.data = new Map()
}
/**
* Set metadata key and value of a provided peer.
* @override
* @param {PeerId} peerId
* @param {string} key metadata key
* @param {Buffer} value metadata value
* @returns {ProtoBook}
*/
set (peerId, key, value) {
if (!PeerId.isPeerId(peerId)) {
log.error('peerId must be an instance of peer-id to store data')
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
if (typeof key !== 'string' || !Buffer.isBuffer(value)) {
log.error('valid key and value must be provided to store data')
throw errcode(new Error('valid key and value must be provided'), ERR_INVALID_PARAMETERS)
}
this._setValue(peerId, key, value)
return this
}
/**
* Set data into the datastructure
* @override
*/
_setValue (peerId, key, value, { emit = true } = {}) {
const id = peerId.toB58String()
const rec = this.data.get(id) || new Map()
const recMap = rec.get(key)
// Already exists and is equal
if (recMap && value.equals(recMap)) {
log(`the metadata provided to store is equal to the already stored for ${id} on ${key}`)
return
}
rec.set(key, value)
this.data.set(id, rec)
emit && this._emit(peerId, key)
}
/**
* Get the known data of a provided peer.
* @param {PeerId} peerId
* @returns {Map<string, Buffer>}
*/
get (peerId) {
if (!PeerId.isPeerId(peerId)) {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
return this.data.get(peerId.toB58String())
}
/**
* Get specific metadata value, if it exists
* @param {PeerId} peerId
* @param {string} key
* @returns {Buffer}
*/
getValue (peerId, key) {
if (!PeerId.isPeerId(peerId)) {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
const rec = this.data.get(peerId.toB58String())
return rec && rec.get(key)
}
/**
* Deletes the provided peer from the book.
* @param {PeerId} peerId
* @returns {boolean}
*/
delete (peerId) {
if (!PeerId.isPeerId(peerId)) {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
if (!this.data.delete(peerId.toB58String())) {
return false
}
this._emit(peerId)
return true
}
/**
* Deletes the provided peer metadata key from the book.
* @param {PeerId} peerId
* @param {string} key
* @returns {boolean}
*/
deleteValue (peerId, key) {
if (!PeerId.isPeerId(peerId)) {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
const rec = this.data.get(peerId.toB58String())
if (!rec || !rec.delete(key)) {
return false
}
this._emit(peerId, key)
return true
}
}
module.exports = MetadataBook

View File

@ -1,15 +0,0 @@
'use strict'
module.exports.NAMESPACE_COMMON = '/peers/'
// /peers/protos/<b32 peer id no padding>
module.exports.NAMESPACE_ADDRESS = '/peers/addrs/'
// /peers/keys/<b32 peer id no padding>
module.exports.NAMESPACE_KEYS = '/peers/keys/'
// /peers/metadata/<b32 peer id no padding>/<key>
module.exports.NAMESPACE_METADATA = '/peers/metadata/'
// /peers/addrs/<b32 peer id no padding>
module.exports.NAMESPACE_PROTOCOL = '/peers/protos/'

View File

@ -1,336 +0,0 @@
'use strict'
const debug = require('debug')
const log = debug('libp2p:persistent-peer-store')
log.error = debug('libp2p:persistent-peer-store:error')
const { Key } = require('interface-datastore')
const multiaddr = require('multiaddr')
const PeerId = require('peer-id')
const PeerStore = require('..')
const {
NAMESPACE_ADDRESS,
NAMESPACE_COMMON,
NAMESPACE_KEYS,
NAMESPACE_METADATA,
NAMESPACE_PROTOCOL
} = require('./consts')
const Addresses = require('./pb/address-book.proto')
const Protocols = require('./pb/proto-book.proto')
/**
* Responsible for managing the persistence of data in the PeerStore.
*/
class PersistentPeerStore extends PeerStore {
/**
* @constructor
* @param {Object} properties
* @param {Datastore} properties.datastore Datastore to persist data.
* @param {number} [properties.threshold = 5] Number of dirty peers allowed before commit data.
*/
constructor ({ datastore, threshold = 5 }) {
super()
/**
* Backend datastore used to persist data.
*/
this._datastore = datastore
/**
* Peers modified after the latest data persisted.
*/
this._dirtyPeers = new Set()
/**
* Peers metadata changed mapping peer identifers to metadata changed.
* @type {Map<string, Set<string>>}
*/
this._dirtyMetadata = new Map()
this.threshold = threshold
this._addDirtyPeer = this._addDirtyPeer.bind(this)
}
/**
* Start Persistent PeerStore.
* @return {Promise<void>}
*/
async start () {
log('PeerStore is starting')
// Handlers for dirty peers
this.on('change:protocols', this._addDirtyPeer)
this.on('change:multiaddrs', this._addDirtyPeer)
this.on('change:pubkey', this._addDirtyPeer)
this.on('change:metadata', this._addDirtyPeerMetadata)
// Load data
for await (const entry of this._datastore.query({ prefix: NAMESPACE_COMMON })) {
await this._processDatastoreEntry(entry)
}
log('PeerStore started')
}
async stop () {
log('PeerStore is stopping')
this.removeAllListeners()
await this._commitData()
log('PeerStore stopped')
}
/**
* Add modified peer to the dirty set
* @private
* @param {Object} params
* @param {PeerId} params.peerId
*/
_addDirtyPeer ({ peerId }) {
const peerIdstr = peerId.toB58String()
log('add dirty peer', peerIdstr)
this._dirtyPeers.add(peerIdstr)
if (this._dirtyPeers.size >= this.threshold) {
// Commit current data
this._commitData().catch(err => {
log.error('error committing data', err)
})
}
}
/**
* Add modified metadata peer to the set.
* @private
* @param {Object} params
* @param {PeerId} params.peerId
* @param {string} params.metadata
*/
_addDirtyPeerMetadata ({ peerId, metadata }) {
const peerIdstr = peerId.toB58String()
log('add dirty metadata peer', peerIdstr)
this._dirtyPeers.add(peerIdstr)
// Add dirty metadata key
const mData = this._dirtyMetadata.get(peerIdstr) || new Set()
mData.add(metadata)
this._dirtyMetadata.set(peerIdstr, mData)
if (this._dirtyPeers.size >= this.threshold) {
// Commit current data
this._commitData().catch(err => {
log.error('error committing data', err)
})
}
}
/**
* Add all the peers current data to a datastore batch and commit it.
* @private
* @param {Array<string>} peers
* @return {Promise<void>}
*/
async _commitData () {
const commitPeers = Array.from(this._dirtyPeers)
if (!commitPeers.length) {
return
}
// Clear Dirty Peers set
this._dirtyPeers.clear()
log('create batch commit')
const batch = this._datastore.batch()
for (const peerIdStr of commitPeers) {
// PeerId
const peerId = this.keyBook.data.get(peerIdStr) || PeerId.createFromCID(peerIdStr)
// Address Book
this._batchAddressBook(peerId, batch)
// Key Book
this._batchKeyBook(peerId, batch)
// Metadata Book
this._batchMetadataBook(peerId, batch)
// Proto Book
this._batchProtoBook(peerId, batch)
}
await batch.commit()
log('batch committed')
}
/**
* Add address book data of the peer to the batch.
* @private
* @param {PeerId} peerId
* @param {Object} batch
*/
_batchAddressBook (peerId, batch) {
const b32key = peerId.toString()
const key = new Key(`${NAMESPACE_ADDRESS}${b32key}`)
const addresses = this.addressBook.get(peerId)
try {
// Deleted from the book
if (!addresses) {
batch.delete(key)
return
}
const encodedData = Addresses.encode({
addrs: addresses.map((address) => ({
multiaddr: address.multiaddr.buffer
}))
})
batch.put(key, encodedData)
} catch (err) {
log.error(err)
}
}
/**
* Add Key book data of the peer to the batch.
* @private
* @param {PeerId} peerId
* @param {Object} batch
*/
_batchKeyBook (peerId, batch) {
const b32key = peerId.toString()
const key = new Key(`${NAMESPACE_KEYS}${b32key}`)
try {
// Deleted from the book
if (!peerId.pubKey) {
batch.delete(key)
return
}
const encodedData = peerId.marshalPubKey()
batch.put(key, encodedData)
} catch (err) {
log.error(err)
}
}
/**
* Add metadata book data of the peer to the batch.
* @private
* @param {PeerId} peerId
* @param {Object} batch
*/
_batchMetadataBook (peerId, batch) {
const b32key = peerId.toString()
const dirtyMetada = this._dirtyMetadata.get(peerId.toB58String()) || []
try {
dirtyMetada.forEach((dirtyKey) => {
const key = new Key(`${NAMESPACE_METADATA}${b32key}/${dirtyKey}`)
const dirtyValue = this.metadataBook.getValue(peerId, dirtyKey)
if (dirtyValue) {
batch.put(key, dirtyValue)
} else {
batch.delete(key)
}
})
} catch (err) {
log.error(err)
}
}
/**
* Add proto book data of the peer to the batch.
* @private
* @param {PeerId} peerId
* @param {Object} batch
*/
_batchProtoBook (peerId, batch) {
const b32key = peerId.toString()
const key = new Key(`${NAMESPACE_PROTOCOL}${b32key}`)
const protocols = this.protoBook.get(peerId)
try {
// Deleted from the book
if (!protocols) {
batch.delete(key)
return
}
const encodedData = Protocols.encode({ protocols })
batch.put(key, encodedData)
} catch (err) {
log.error(err)
}
}
/**
* Process datastore entry and add its data to the correct book.
* @private
* @param {Object} params
* @param {Key} params.key datastore key
* @param {Buffer} params.value datastore value stored
* @return {Promise<void>}
*/
async _processDatastoreEntry ({ key, value }) {
try {
const keyParts = key.toString().split('/')
const peerId = PeerId.createFromCID(keyParts[3])
let decoded
switch (keyParts[2]) {
case 'addrs':
decoded = Addresses.decode(value)
this.addressBook._setData(
peerId,
decoded.addrs.map((address) => ({
multiaddr: multiaddr(address.multiaddr)
})),
{ emit: false })
break
case 'keys':
decoded = await PeerId.createFromPubKey(value)
this.keyBook._setData(
decoded,
decoded,
{ emit: false })
break
case 'metadata':
this.metadataBook._setValue(
peerId,
keyParts[4],
value,
{ emit: false })
break
case 'protos':
decoded = Protocols.decode(value)
this.protoBook._setData(
peerId,
new Set(decoded.protocols),
{ emit: false })
break
default:
log('invalid data persisted for: ', key.toString())
}
} catch (err) {
log.error(err)
}
}
}
module.exports = PersistentPeerStore

View File

@ -1,15 +0,0 @@
'use strict'
const protons = require('protons')
const message = `
message Addresses {
message Address {
required bytes multiaddr = 1;
}
repeated Address addrs = 1;
}
`
module.exports = protons(message).Addresses

View File

@ -1,12 +0,0 @@
'use strict'
const protons = require('protons')
/* eslint-disable no-tabs */
const message = `
message Protocols {
repeated string protocols = 1;
}
`
module.exports = protons(message).Protocols

View File

@ -1,120 +0,0 @@
'use strict'
const errcode = require('err-code')
const debug = require('debug')
const log = debug('libp2p:peer-store:proto-book')
log.error = debug('libp2p:peer-store:proto-book:error')
const PeerId = require('peer-id')
const Book = require('./book')
const {
codes: { ERR_INVALID_PARAMETERS }
} = require('../errors')
/**
* The ProtoBook is responsible for keeping the known supported
* protocols of a peer.
* @fires ProtoBook#change:protocols
*/
class ProtoBook extends Book {
/**
* @constructor
* @param {PeerStore} peerStore
*/
constructor (peerStore) {
/**
* PeerStore Event emitter, used by the ProtoBook to emit:
* "change:protocols" - emitted when the known protocols of a peer change.
*/
super({
peerStore,
eventName: 'change:protocols',
eventProperty: 'protocols',
eventTransformer: (data) => Array.from(data)
})
/**
* Map known peers to their known protocols.
* @type {Map<string, Set<string>>}
*/
this.data = new Map()
}
/**
* Set known protocols of a provided peer.
* If the peer was not known before, it will be added.
* @override
* @param {PeerId} peerId
* @param {Array<string>} protocols
* @returns {ProtoBook}
*/
set (peerId, protocols) {
if (!PeerId.isPeerId(peerId)) {
log.error('peerId must be an instance of peer-id to store data')
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
if (!protocols) {
log.error('protocols must be provided to store data')
throw errcode(new Error('protocols must be provided'), ERR_INVALID_PARAMETERS)
}
const id = peerId.toB58String()
const recSet = this.data.get(id)
const newSet = new Set(protocols)
const isSetEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value))
// Already knows the peer and the recorded protocols are the same?
// If yes, no changes needed!
if (recSet && isSetEqual(recSet, newSet)) {
log(`the protocols provided to store are equal to the already stored for ${id}`)
return this
}
this._setData(peerId, newSet)
log(`stored provided protocols for ${id}`)
return this
}
/**
* Adds known protocols of a provided peer.
* If the peer was not known before, it will be added.
* @param {PeerId} peerId
* @param {Array<string>} protocols
* @returns {ProtoBook}
*/
add (peerId, protocols) {
if (!PeerId.isPeerId(peerId)) {
log.error('peerId must be an instance of peer-id to store data')
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
if (!protocols) {
log.error('protocols must be provided to store data')
throw errcode(new Error('protocols must be provided'), ERR_INVALID_PARAMETERS)
}
const id = peerId.toB58String()
const recSet = this.data.get(id) || new Set()
const newSet = new Set([...recSet, ...protocols]) // Set Union
// Any new protocol added?
if (recSet.size === newSet.size) {
log(`the protocols provided to store are already stored for ${id}`)
return this
}
protocols = [...newSet]
this._setData(peerId, newSet)
log(`added provided protocols for ${id}`)
return this
}
}
module.exports = ProtoBook

View File

@ -15,11 +15,11 @@ const { PROTOCOL, PING_LENGTH } = require('./constants')
/** /**
* Ping a given peer and wait for its response, getting the operation latency. * Ping a given peer and wait for its response, getting the operation latency.
* @param {Libp2p} node * @param {Libp2p} node
* @param {PeerId} peer * @param {PeerInfo} peer
* @returns {Promise<Number>} * @returns {Promise<Number>}
*/ */
async function ping (node, peer) { async function ping (node, peer) {
log('dialing %s to %s', PROTOCOL, peer.toB58String()) log('dialing %s to %s', PROTOCOL, peer.id.toB58String())
const { stream } = await node.dialProtocol(peer, PROTOCOL) const { stream } = await node.dialProtocol(peer, PROTOCOL)

View File

@ -1,6 +1,5 @@
'use strict' 'use strict'
const { Buffer } = require('buffer')
const debug = require('debug') const debug = require('debug')
const Errors = require('./errors') const Errors = require('./errors')
const xsalsa20 = require('xsalsa20') const xsalsa20 = require('xsalsa20')

View File

@ -1,13 +1,10 @@
'use strict' 'use strict'
const pipe = require('it-pipe') const pipe = require('it-pipe')
const errcode = require('err-code') const assert = require('assert')
const duplexPair = require('it-pair/duplex') const duplexPair = require('it-pair/duplex')
const crypto = require('libp2p-crypto') const crypto = require('libp2p-crypto')
const Errors = require('./errors') const Errors = require('./errors')
const {
ERR_INVALID_PARAMETERS
} = require('../errors')
const { const {
createBoxStream, createBoxStream,
createUnboxStream, createUnboxStream,
@ -43,9 +40,7 @@ class Protector {
* @returns {*} A protected duplex iterable * @returns {*} A protected duplex iterable
*/ */
async protect (connection) { async protect (connection) {
if (!connection) { assert(connection, Errors.NO_HANDSHAKE_CONNECTION)
throw errcode(new Error(Errors.NO_HANDSHAKE_CONNECTION), ERR_INVALID_PARAMETERS)
}
// Exchange nonces // Exchange nonces
log('protecting the connection') log('protecting the connection')

View File

@ -1,6 +1,6 @@
'use strict' 'use strict'
const crypto = require('libp2p-crypto') const crypto = require('crypto')
const KEY_LENGTH = 32 const KEY_LENGTH = 32
/** /**

View File

@ -1,11 +1,10 @@
'use strict' 'use strict'
const { Buffer } = require('buffer')
const errCode = require('err-code') const errCode = require('err-code')
const { messages, codes } = require('./errors') const { messages, codes } = require('./errors')
module.exports = (node, Pubsub, config) => { module.exports = (node, Pubsub, config) => {
const pubsub = new Pubsub(node.peerId, node.registrar, config) const pubsub = new Pubsub(node.peerInfo, node.registrar, config)
return { return {
/** /**

View File

@ -1,14 +1,13 @@
'use strict' 'use strict'
const assert = require('assert')
const debug = require('debug') const debug = require('debug')
const errcode = require('err-code')
const log = debug('libp2p:peer-store') const log = debug('libp2p:peer-store')
log.error = debug('libp2p:peer-store:error') log.error = debug('libp2p:peer-store:error')
const {
ERR_INVALID_PARAMETERS
} = require('./errors')
const Topology = require('libp2p-interfaces/src/topology') const Topology = require('libp2p-interfaces/src/topology')
const { Connection } = require('libp2p-interfaces/src/connection')
const PeerInfo = require('peer-info')
/** /**
* Responsible for notifying registered protocols of events in the network. * Responsible for notifying registered protocols of events in the network.
@ -17,14 +16,17 @@ class Registrar {
/** /**
* @param {Object} props * @param {Object} props
* @param {PeerStore} props.peerStore * @param {PeerStore} props.peerStore
* @param {connectionManager} props.connectionManager
* @constructor * @constructor
*/ */
constructor ({ peerStore, connectionManager }) { constructor ({ peerStore }) {
// Used on topology to listen for protocol changes
this.peerStore = peerStore this.peerStore = peerStore
this.connectionManager = connectionManager /**
* Map of connections per peer
* TODO: this should be handled by connectionManager
* @type {Map<string, Array<conn>>}
*/
this.connections = new Map()
/** /**
* Map of topologies * Map of topologies
@ -34,9 +36,6 @@ class Registrar {
this.topologies = new Map() this.topologies = new Map()
this._handle = undefined this._handle = undefined
this._onDisconnect = this._onDisconnect.bind(this)
this.connectionManager.on('peer:disconnect', this._onDisconnect)
} }
get handle () { get handle () {
@ -47,13 +46,84 @@ class Registrar {
this._handle = handle 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
* @param {PeerInfo} peerInfo
* @param {Connection} conn
* @returns {void}
*/
onConnect (peerInfo, conn) {
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.toB58String()
const storedConn = this.connections.get(id)
if (storedConn) {
storedConn.push(conn)
} else {
this.connections.set(id, [conn])
}
}
/**
* Remove a disconnected peer from the record
* TODO: this should live in the ConnectionManager
* @param {PeerInfo} peerInfo
* @param {Connection} connection
* @param {Error} [error]
* @returns {void}
*/
onDisconnect (peerInfo, connection, error) {
assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
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.toB58String())
}
}
/** /**
* Get a connection with a peer. * Get a connection with a peer.
* @param {PeerId} peerId * @param {PeerInfo} peerInfo
* @returns {Connection} * @returns {Connection}
*/ */
getConnection (peerId) { getConnection (peerInfo) {
return this.connectionManager.get(peerId) assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
const connections = this.connections.get(peerInfo.id.toB58String())
// Return the first, open connection
if (connections) {
return connections.find(connection => connection.stat.status === 'open')
}
return null
} }
/** /**
@ -62,9 +132,9 @@ class Registrar {
* @return {string} registrar identifier * @return {string} registrar identifier
*/ */
register (topology) { register (topology) {
if (!Topology.isTopology(topology)) { assert(
throw errcode(new Error('topology must be an instance of interfaces/topology'), ERR_INVALID_PARAMETERS) Topology.isTopology(topology),
} 'topology must be an instance of interfaces/topology')
// Create topology // Create topology
const id = (parseInt(Math.random() * 1e9)).toString(36) + Date.now() const id = (parseInt(Math.random() * 1e9)).toString(36) + Date.now()
@ -85,18 +155,6 @@ class Registrar {
unregister (id) { unregister (id) {
return this.topologies.delete(id) return this.topologies.delete(id)
} }
/**
* Remove a disconnected peer from the record
* @param {Connection} connection
* @param {Error} [error]
* @returns {void}
*/
_onDisconnect (connection, error) {
for (const [, topology] of this.topologies) {
topology.disconnect(connection.remotePeer, error)
}
}
} }
module.exports = Registrar module.exports = Registrar

View File

@ -13,14 +13,12 @@ class TransportManager {
* @param {object} options * @param {object} options
* @param {Libp2p} options.libp2p The Libp2p instance. It will be passed to the transports. * @param {Libp2p} options.libp2p The Libp2p instance. It will be passed to the transports.
* @param {Upgrader} options.upgrader The upgrader to provide to the transports * @param {Upgrader} options.upgrader The upgrader to provide to the transports
* @param {boolean} [options.faultTolerance = FAULT_TOLERANCE.FATAL_ALL] Address listen error tolerance.
*/ */
constructor ({ libp2p, upgrader, faultTolerance = FAULT_TOLERANCE.FATAL_ALL }) { constructor ({ libp2p, upgrader }) {
this.libp2p = libp2p this.libp2p = libp2p
this.upgrader = upgrader this.upgrader = upgrader
this._transports = new Map() this._transports = new Map()
this._listeners = new Map() this._listeners = new Map()
this.faultTolerance = faultTolerance
} }
/** /**
@ -129,12 +127,11 @@ class TransportManager {
} }
/** /**
* Starts listeners for each listen Multiaddr. * Starts listeners for each given Multiaddr.
* @async * @async
* @param {Multiaddr[]} addrs
*/ */
async listen () { async listen (addrs) {
const addrs = this.libp2p.addressManager.getListenAddrs()
if (addrs.length === 0) { if (addrs.length === 0) {
log('no addresses were provided for listening, this node is dial only') log('no addresses were provided for listening, this node is dial only')
return return
@ -175,11 +172,7 @@ class TransportManager {
// If no transports were able to listen, throw an error. This likely // If no transports were able to listen, throw an error. This likely
// means we were given addresses we do not have transports for // means we were given addresses we do not have transports for
if (couldNotListen.length === this._transports.size) { if (couldNotListen.length === this._transports.size) {
const message = `no valid addresses were provided for transports [${couldNotListen}]` throw errCode(new Error(`no valid addresses were provided for transports [${couldNotListen}]`), codes.ERR_NO_VALID_ADDRESSES)
if (this.faultTolerance === FAULT_TOLERANCE.FATAL_ALL) {
throw errCode(new Error(message), codes.ERR_NO_VALID_ADDRESSES)
}
log(`libp2p in dial mode only: ${message}`)
} }
} }
@ -218,18 +211,4 @@ class TransportManager {
} }
} }
/**
* Enum Transport Manager Fault Tolerance values.
* FATAL_ALL should be used for failing in any listen circumstance.
* NO_FATAL should be used for not failing when not listening.
* @readonly
* @enum {number}
*/
const FAULT_TOLERANCE = {
FATAL_ALL: 0,
NO_FATAL: 1
}
TransportManager.FaultTolerance = FAULT_TOLERANCE
module.exports = TransportManager module.exports = TransportManager

View File

@ -232,7 +232,7 @@ class Upgrader {
log('%s: incoming stream opened on %s', direction, protocol) log('%s: incoming stream opened on %s', direction, protocol)
if (this.metrics) this.metrics.trackStream({ stream, remotePeer, protocol }) if (this.metrics) this.metrics.trackStream({ stream, remotePeer, protocol })
connection.addStream(muxedStream, { protocol }) connection.addStream(muxedStream, { protocol })
this._onStream({ connection, stream: { ...muxedStream, ...stream }, protocol }) this._onStream({ connection, stream, protocol })
} catch (err) { } catch (err) {
log.error(err) log.error(err)
} }
@ -317,7 +317,7 @@ class Upgrader {
* Attempts to encrypt the incoming `connection` with the provided `cryptos`. * Attempts to encrypt the incoming `connection` with the provided `cryptos`.
* @private * @private
* @async * @async
* @param {PeerId} localPeer The initiators PeerId * @param {PeerId} localPeer The initiators PeerInfo
* @param {*} connection * @param {*} connection
* @param {Map<string, Crypto>} cryptos * @param {Map<string, Crypto>} cryptos
* @returns {CryptoResult} An encrypted connection, remote peer `PeerId` and the protocol of the `Crypto` used * @returns {CryptoResult} An encrypted connection, remote peer `PeerId` and the protocol of the `Crypto` used
@ -346,7 +346,7 @@ class Upgrader {
* The first `Crypto` module to succeed will be used * The first `Crypto` module to succeed will be used
* @private * @private
* @async * @async
* @param {PeerId} localPeer The initiators PeerId * @param {PeerId} localPeer The initiators PeerInfo
* @param {*} connection * @param {*} connection
* @param {PeerId} remotePeerId * @param {PeerId} remotePeerId
* @param {Map<string, Crypto>} cryptos * @param {Map<string, Crypto>} cryptos

View File

@ -1,93 +0,0 @@
'use strict'
/* eslint-env mocha */
const chai = require('chai')
chai.use(require('dirty-chai'))
chai.use(require('chai-as-promised'))
const { expect } = chai
const multiaddr = require('multiaddr')
const AddressManager = require('../../src/address-manager')
const peerUtils = require('../utils/creators/peer')
const listenAddresses = ['/ip4/127.0.0.1/tcp/15006/ws', '/ip4/127.0.0.1/tcp/15008/ws']
const announceAddreses = ['/dns4/peer.io']
describe('Address Manager', () => {
it('should not need any addresses', () => {
const am = new AddressManager()
expect(am.listen.size).to.equal(0)
expect(am.announce.size).to.equal(0)
expect(am.noAnnounce.size).to.equal(0)
})
it('should return listen multiaddrs on get', () => {
const am = new AddressManager({
listen: listenAddresses
})
expect(am.listen.size).to.equal(listenAddresses.length)
expect(am.announce.size).to.equal(0)
expect(am.noAnnounce.size).to.equal(0)
const listenMultiaddrs = am.getListenAddrs()
expect(listenMultiaddrs.length).to.equal(2)
expect(listenMultiaddrs[0].equals(multiaddr(listenAddresses[0]))).to.equal(true)
expect(listenMultiaddrs[1].equals(multiaddr(listenAddresses[1]))).to.equal(true)
})
it('should return announce multiaddrs on get', () => {
const am = new AddressManager({
listen: listenAddresses,
announce: announceAddreses
})
expect(am.listen.size).to.equal(listenAddresses.length)
expect(am.announce.size).to.equal(announceAddreses.length)
expect(am.noAnnounce.size).to.equal(0)
const announceMultiaddrs = am.getAnnounceAddrs()
expect(announceMultiaddrs.length).to.equal(1)
expect(announceMultiaddrs[0].equals(multiaddr(announceAddreses[0]))).to.equal(true)
})
it('should return noAnnounce multiaddrs on get', () => {
const am = new AddressManager({
listen: listenAddresses,
noAnnounce: listenAddresses
})
expect(am.listen.size).to.equal(listenAddresses.length)
expect(am.announce.size).to.equal(0)
expect(am.noAnnounce.size).to.equal(listenAddresses.length)
const noAnnounceMultiaddrs = am.getNoAnnounceAddrs()
expect(noAnnounceMultiaddrs.length).to.equal(2)
expect(noAnnounceMultiaddrs[0].equals(multiaddr(listenAddresses[0]))).to.equal(true)
expect(noAnnounceMultiaddrs[1].equals(multiaddr(listenAddresses[1]))).to.equal(true)
})
})
describe('libp2p.addressManager', () => {
let libp2p
afterEach(() => libp2p && libp2p.stop())
it('should populate the AddressManager from the config', async () => {
[libp2p] = await peerUtils.createPeer({
started: false,
config: {
addresses: {
listen: listenAddresses,
announce: announceAddreses,
noAnnounce: listenAddresses
}
}
})
expect(libp2p.addressManager.listen.size).to.equal(listenAddresses.length)
expect(libp2p.addressManager.announce.size).to.equal(announceAddreses.length)
expect(libp2p.addressManager.noAnnounce.size).to.equal(listenAddresses.length)
})
})

View File

@ -1,126 +0,0 @@
'use strict'
/* eslint-env mocha */
const chai = require('chai')
chai.use(require('dirty-chai'))
chai.use(require('chai-as-promised'))
const { expect } = chai
const sinon = require('sinon')
const { AddressesOptions } = require('./utils')
const peerUtils = require('../utils/creators/peer')
const listenAddresses = ['/ip4/127.0.0.1/tcp/0', '/ip4/127.0.0.1/tcp/8000/ws']
const announceAddreses = ['/dns4/peer.io']
describe('libp2p.multiaddrs', () => {
let libp2p
afterEach(() => libp2p && libp2p.stop())
it('should keep listen addresses after start, even if changed', async () => {
[libp2p] = await peerUtils.createPeer({
started: false,
config: {
...AddressesOptions,
addresses: {
listen: listenAddresses,
announce: announceAddreses
}
}
})
let listenAddrs = libp2p.addressManager.listen
expect(listenAddrs.size).to.equal(listenAddresses.length)
expect(listenAddrs.has(listenAddresses[0])).to.equal(true)
expect(listenAddrs.has(listenAddresses[1])).to.equal(true)
// Should not replace listen addresses after transport listen
// Only transportManager has visibility of the port used
await libp2p.start()
listenAddrs = libp2p.addressManager.listen
expect(listenAddrs.size).to.equal(listenAddresses.length)
expect(listenAddrs.has(listenAddresses[0])).to.equal(true)
expect(listenAddrs.has(listenAddresses[1])).to.equal(true)
})
it('should advertise all addresses if noAnnounce addresses are not provided, but with correct ports', async () => {
[libp2p] = await peerUtils.createPeer({
config: {
...AddressesOptions,
addresses: {
listen: listenAddresses,
announce: announceAddreses
}
}
})
const tmListen = libp2p.transportManager.getAddrs().map((ma) => ma.toString())
const spyAnnounce = sinon.spy(libp2p.addressManager, 'getAnnounceAddrs')
const spyNoAnnounce = sinon.spy(libp2p.addressManager, 'getNoAnnounceAddrs')
const spyListen = sinon.spy(libp2p.addressManager, 'getListenAddrs')
const spyTranspMgr = sinon.spy(libp2p.transportManager, 'getAddrs')
const advertiseMultiaddrs = libp2p.multiaddrs.map((ma) => ma.toString())
expect(spyAnnounce).to.have.property('callCount', 1)
expect(spyNoAnnounce).to.have.property('callCount', 1)
expect(spyListen).to.have.property('callCount', 0) // Listen addr should not be used
expect(spyTranspMgr).to.have.property('callCount', 1)
// Announce 2 listen (transport) + 1 announce
expect(advertiseMultiaddrs.length).to.equal(3)
tmListen.forEach((m) => {
expect(advertiseMultiaddrs).to.include(m)
})
announceAddreses.forEach((m) => {
expect(advertiseMultiaddrs).to.include(m)
})
expect(advertiseMultiaddrs).to.not.include(listenAddresses[0]) // Random Port switch
})
it('should remove replicated addresses', async () => {
[libp2p] = await peerUtils.createPeer({
config: {
...AddressesOptions,
addresses: {
listen: listenAddresses,
announce: [listenAddresses[1]]
}
}
})
const advertiseMultiaddrs = libp2p.multiaddrs.map((ma) => ma.toString())
// Announce 2 listen (transport), ignoring duplicated in announce
expect(advertiseMultiaddrs.length).to.equal(2)
})
it('should not advertise noAnnounce addresses', async () => {
const noAnnounce = [listenAddresses[1]]
;[libp2p] = await peerUtils.createPeer({
config: {
...AddressesOptions,
addresses: {
listen: listenAddresses,
announce: announceAddreses,
noAnnounce
}
}
})
const advertiseMultiaddrs = libp2p.multiaddrs.map((ma) => ma.toString())
// Announce 1 listen (transport) not in the noAnnounce and the announce
expect(advertiseMultiaddrs.length).to.equal(2)
announceAddreses.forEach((m) => {
expect(advertiseMultiaddrs).to.include(m)
})
noAnnounce.forEach((m) => {
expect(advertiseMultiaddrs).to.not.include(m)
})
})
})

View File

@ -1,16 +0,0 @@
'use strict'
const Transport1 = require('libp2p-tcp')
const Transport2 = require('libp2p-websockets')
const mergeOptions = require('merge-options')
const baseOptions = require('../utils/base-options')
module.exports.baseOptions = baseOptions
const AddressesOptions = mergeOptions(baseOptions, {
modules: {
transport: [Transport1, Transport2]
}
})
module.exports.AddressesOptions = AddressesOptions

View File

@ -1,115 +0,0 @@
'use strict'
/* eslint-env mocha */
const chai = require('chai')
chai.use(require('dirty-chai'))
chai.use(require('chai-as-promised'))
const { expect } = chai
const sinon = require('sinon')
const peerUtils = require('../utils/creators/peer')
const mockConnection = require('../utils/mockConnection')
const baseOptions = require('../utils/base-options.browser')
const listenMultiaddr = '/ip4/127.0.0.1/tcp/15002/ws'
describe('Connection Manager', () => {
let libp2p
beforeEach(async () => {
[libp2p] = await peerUtils.createPeer({
config: {
addresses: {
listen: [listenMultiaddr]
},
modules: baseOptions.modules
}
})
})
afterEach(() => libp2p.stop())
it('should filter connections on disconnect, removing the closed one', async () => {
const [localPeer, remotePeer] = await peerUtils.createPeerId({ number: 2 })
const conn1 = await mockConnection({ localPeer, remotePeer })
const conn2 = await mockConnection({ localPeer, remotePeer })
const id = remotePeer.toB58String()
// Add connection to the connectionManager
libp2p.connectionManager.onConnect(conn1)
libp2p.connectionManager.onConnect(conn2)
expect(libp2p.connectionManager.connections.get(id).length).to.eql(2)
conn2._stat.status = 'closed'
libp2p.connectionManager.onDisconnect(conn2)
const peerConnections = libp2p.connectionManager.connections.get(id)
expect(peerConnections.length).to.eql(1)
expect(peerConnections[0]._stat.status).to.eql('open')
})
it('should add connection on dial and remove on node stop', async () => {
const [remoteLibp2p] = await peerUtils.createPeer({
config: {
addresses: {
listen: ['/ip4/127.0.0.1/tcp/15003/ws']
},
modules: baseOptions.modules
}
})
// Spy on emit for easy verification
sinon.spy(libp2p.connectionManager, 'emit')
sinon.spy(remoteLibp2p.connectionManager, 'emit')
libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs)
await libp2p.dial(remoteLibp2p.peerId)
// check connect event
expect(libp2p.connectionManager.emit.callCount).to.equal(1)
const [event, connection] = libp2p.connectionManager.emit.getCall(0).args
expect(event).to.equal('peer:connect')
expect(connection.remotePeer.isEqual(remoteLibp2p.peerId)).to.equal(true)
const libp2pConn = libp2p.connectionManager.get(remoteLibp2p.peerId)
expect(libp2pConn).to.exist()
const remoteConn = remoteLibp2p.connectionManager.get(libp2p.peerId)
expect(remoteConn).to.exist()
await remoteLibp2p.stop()
expect(remoteLibp2p.connectionManager.size).to.eql(0)
})
})
describe('libp2p.connections', () => {
it('libp2p.connections gets the connectionManager conns', async () => {
const [libp2p] = await peerUtils.createPeer({
config: {
addresses: {
listen: ['/ip4/127.0.0.1/tcp/15003/ws']
},
modules: baseOptions.modules
}
})
const [remoteLibp2p] = await peerUtils.createPeer({
config: {
addresses: {
listen: ['/ip4/127.0.0.1/tcp/15004/ws']
},
modules: baseOptions.modules
}
})
libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs)
await libp2p.dial(remoteLibp2p.peerId)
expect(libp2p.connections.size).to.eql(1)
await libp2p.stop()
await remoteLibp2p.stop()
})
})

View File

@ -7,7 +7,7 @@ chai.use(require('chai-as-promised'))
const { expect } = chai const { expect } = chai
const sinon = require('sinon') const sinon = require('sinon')
const peerUtils = require('../utils/creators/peer') const { createPeer } = require('../utils/creators/peer')
const mockConnection = require('../utils/mockConnection') const mockConnection = require('../utils/mockConnection')
const baseOptions = require('../utils/base-options.browser') const baseOptions = require('../utils/base-options.browser')
@ -20,7 +20,7 @@ describe('Connection Manager', () => {
}) })
it('should be able to create without metrics', async () => { it('should be able to create without metrics', async () => {
[libp2p] = await peerUtils.createPeer({ [libp2p] = await createPeer({
config: { config: {
modules: baseOptions.modules modules: baseOptions.modules
}, },
@ -35,7 +35,7 @@ describe('Connection Manager', () => {
}) })
it('should be able to create with metrics', async () => { it('should be able to create with metrics', async () => {
[libp2p] = await peerUtils.createPeer({ [libp2p] = await createPeer({
config: { config: {
modules: baseOptions.modules, modules: baseOptions.modules,
metrics: { metrics: {
@ -49,12 +49,12 @@ describe('Connection Manager', () => {
await libp2p.start() await libp2p.start()
expect(spy).to.have.property('callCount', 1) expect(spy).to.have.property('callCount', 1)
expect(libp2p.connectionManager._libp2p.metrics).to.exist() expect(libp2p.connectionManager._metrics).to.exist()
}) })
it('should close lowest value peer connection when the maximum has been reached', async () => { it('should close lowest value peer connection when the maximum has been reached', async () => {
const max = 5 const max = 5
;[libp2p] = await peerUtils.createPeer({ ;[libp2p] = await createPeer({
config: { config: {
modules: baseOptions.modules, modules: baseOptions.modules,
connectionManager: { connectionManager: {
@ -92,7 +92,7 @@ describe('Connection Manager', () => {
it('should close connection when the maximum has been reached even without peer values', async () => { it('should close connection when the maximum has been reached even without peer values', async () => {
const max = 5 const max = 5
;[libp2p] = await peerUtils.createPeer({ ;[libp2p] = await createPeer({
config: { config: {
modules: baseOptions.modules, modules: baseOptions.modules,
connectionManager: { connectionManager: {
@ -110,7 +110,7 @@ describe('Connection Manager', () => {
const spy = sinon.spy() const spy = sinon.spy()
await Promise.all([...new Array(max + 1)].map(async () => { await Promise.all([...new Array(max + 1)].map(async () => {
const connection = await mockConnection() const connection = await mockConnection()
sinon.stub(connection, 'close').callsFake(() => spy()) // eslint-disable-line sinon.stub(connection, 'close').callsFake(() => spy())
libp2p.connectionManager.onConnect(connection) libp2p.connectionManager.onConnect(connection)
})) }))
@ -119,7 +119,7 @@ describe('Connection Manager', () => {
}) })
it('should fail if the connection manager has mismatched connection limit options', async () => { it('should fail if the connection manager has mismatched connection limit options', async () => {
await expect(peerUtils.createPeer({ await expect(createPeer({
config: { config: {
modules: baseOptions.modules, modules: baseOptions.modules,
connectionManager: { connectionManager: {

View File

@ -56,7 +56,7 @@ describe('content-routing', () => {
// Ring dial // Ring dial
await Promise.all( await Promise.all(
nodes.map((peer, i) => peer.dial(nodes[(i + 1) % number].peerId)) nodes.map((peer, i) => peer.dial(nodes[(i + 1) % number].peerInfo))
) )
}) })
@ -96,9 +96,9 @@ describe('content-routing', () => {
let delegate let delegate
beforeEach(async () => { beforeEach(async () => {
const [peerId] = await peerUtils.createPeerId({ fixture: false }) const [peerInfo] = await peerUtils.createPeerInfo({ fixture: false })
delegate = new DelegatedContentRouter(peerId, { delegate = new DelegatedContentRouter(peerInfo.id, {
host: '0.0.0.0', host: '0.0.0.0',
protocol: 'http', protocol: 'http',
port: 60197 port: 60197
@ -155,7 +155,10 @@ describe('content-routing', () => {
const mockApi = nock('http://0.0.0.0:60197') const mockApi = nock('http://0.0.0.0:60197')
// mock the refs call // mock the refs call
.post('/api/v0/refs') .post('/api/v0/refs')
.query(true) .query({
recursive: false,
arg: cid.toBaseEncodedString()
})
.reply(200, null, [ .reply(200, null, [
'Content-Type', 'application/json', 'Content-Type', 'application/json',
'X-Chunked-Output', '1' 'X-Chunked-Output', '1'
@ -171,7 +174,10 @@ describe('content-routing', () => {
const mockApi = nock('http://0.0.0.0:60197') const mockApi = nock('http://0.0.0.0:60197')
// mock the refs call // mock the refs call
.post('/api/v0/refs') .post('/api/v0/refs')
.query(true) .query({
recursive: false,
arg: cid.toBaseEncodedString()
})
.reply(502, 'Bad Gateway', ['Content-Type', 'application/json']) .reply(502, 'Bad Gateway', ['Content-Type', 'application/json'])
await expect(node.contentRouting.provide(cid)) await expect(node.contentRouting.provide(cid))
@ -186,7 +192,9 @@ describe('content-routing', () => {
const mockApi = nock('http://0.0.0.0:60197') const mockApi = nock('http://0.0.0.0:60197')
.post('/api/v0/dht/findprovs') .post('/api/v0/dht/findprovs')
.query(true) .query({
arg: cid.toBaseEncodedString()
})
.reply(200, `{"Extra":"","ID":"QmWKqWXCtRXEeCQTo3FoZ7g4AfnGiauYYiczvNxFCHicbB","Responses":[{"Addrs":["/ip4/0.0.0.0/tcp/0"],"ID":"${provider}"}],"Type":4}\n`, [ .reply(200, `{"Extra":"","ID":"QmWKqWXCtRXEeCQTo3FoZ7g4AfnGiauYYiczvNxFCHicbB","Responses":[{"Addrs":["/ip4/0.0.0.0/tcp/0"],"ID":"${provider}"}],"Type":4}\n`, [
'Content-Type', 'application/json', 'Content-Type', 'application/json',
'X-Chunked-Output', '1' 'X-Chunked-Output', '1'
@ -206,7 +214,9 @@ describe('content-routing', () => {
const cid = new CID('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB') const cid = new CID('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB')
const mockApi = nock('http://0.0.0.0:60197') const mockApi = nock('http://0.0.0.0:60197')
.post('/api/v0/dht/findprovs') .post('/api/v0/dht/findprovs')
.query(true) .query({
arg: cid.toBaseEncodedString()
})
.reply(502, 'Bad Gateway', [ .reply(502, 'Bad Gateway', [
'X-Chunked-Output', '1' 'X-Chunked-Output', '1'
]) ])
@ -227,9 +237,9 @@ describe('content-routing', () => {
let delegate let delegate
beforeEach(async () => { beforeEach(async () => {
const [peerId] = await peerUtils.createPeerId({ fixture: false }) const [peerInfo] = await peerUtils.createPeerInfo({ fixture: false })
delegate = new DelegatedContentRouter(peerId, { delegate = new DelegatedContentRouter(peerInfo.id, {
host: '0.0.0.0', host: '0.0.0.0',
protocol: 'http', protocol: 'http',
port: 60197 port: 60197

View File

@ -6,12 +6,13 @@ chai.use(require('dirty-chai'))
const { expect } = chai const { expect } = chai
const mergeOptions = require('merge-options') const mergeOptions = require('merge-options')
const multiaddr = require('multiaddr')
const { create } = require('../../../src') const { create } = require('../../../src')
const { baseOptions, subsystemOptions } = require('./utils') const { baseOptions, subsystemOptions } = require('./utils')
const peerUtils = require('../../utils/creators/peer') const peerUtils = require('../../utils/creators/peer')
const listenAddr = '/ip4/127.0.0.1/tcp/0' const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0')
describe('DHT subsystem is configurable', () => { describe('DHT subsystem is configurable', () => {
let libp2p let libp2p
@ -31,13 +32,11 @@ describe('DHT subsystem is configurable', () => {
}) })
it('should start and stop by default once libp2p starts', async () => { it('should start and stop by default once libp2p starts', async () => {
const [peerId] = await peerUtils.createPeerId(1) const [peerInfo] = await peerUtils.createPeerInfo(1)
peerInfo.multiaddrs.add(listenAddr)
const customOptions = mergeOptions(subsystemOptions, { const customOptions = mergeOptions(subsystemOptions, {
peerId, peerInfo
addresses: {
listen: [listenAddr]
}
}) })
libp2p = await create(customOptions) libp2p = await create(customOptions)
@ -51,13 +50,11 @@ describe('DHT subsystem is configurable', () => {
}) })
it('should not start if disabled once libp2p starts', async () => { it('should not start if disabled once libp2p starts', async () => {
const [peerId] = await peerUtils.createPeerId(1) const [peerInfo] = await peerUtils.createPeerInfo(1)
peerInfo.multiaddrs.add(listenAddr)
const customOptions = mergeOptions(subsystemOptions, { const customOptions = mergeOptions(subsystemOptions, {
peerId, peerInfo,
addresses: {
listen: [listenAddr]
},
config: { config: {
dht: { dht: {
enabled: false enabled: false
@ -73,13 +70,11 @@ describe('DHT subsystem is configurable', () => {
}) })
it('should allow a manual start', async () => { it('should allow a manual start', async () => {
const [peerId] = await peerUtils.createPeerId(1) const [peerInfo] = await peerUtils.createPeerInfo(1)
peerInfo.multiaddrs.add(listenAddr)
const customOptions = mergeOptions(subsystemOptions, { const customOptions = mergeOptions(subsystemOptions, {
peerId, peerInfo,
addresses: {
listen: [listenAddr]
},
config: { config: {
dht: { dht: {
enabled: false enabled: false

View File

@ -1,14 +1,13 @@
'use strict' 'use strict'
/* eslint-env mocha */ /* eslint-env mocha */
const { Buffer } = require('buffer')
const chai = require('chai') const chai = require('chai')
chai.use(require('dirty-chai')) chai.use(require('dirty-chai'))
const { expect } = chai const { expect } = chai
const multiaddr = require('multiaddr')
const pWaitFor = require('p-wait-for') const pWaitFor = require('p-wait-for')
const mergeOptions = require('merge-options') const mergeOptions = require('merge-options')
const multiaddr = require('multiaddr')
const { create } = require('../../../src') const { create } = require('../../../src')
const { subsystemOptions, subsystemMulticodecs } = require('./utils') const { subsystemOptions, subsystemMulticodecs } = require('./utils')
@ -18,28 +17,25 @@ const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/8000')
const remoteListenAddr = multiaddr('/ip4/127.0.0.1/tcp/8001') const remoteListenAddr = multiaddr('/ip4/127.0.0.1/tcp/8001')
describe('DHT subsystem operates correctly', () => { describe('DHT subsystem operates correctly', () => {
let peerId, remotePeerId let peerInfo, remotePeerInfo
let libp2p, remoteLibp2p let libp2p, remoteLibp2p
let remAddr let remAddr
beforeEach(async () => { beforeEach(async () => {
[peerId, remotePeerId] = await peerUtils.createPeerId({ number: 2 }) [peerInfo, remotePeerInfo] = await peerUtils.createPeerInfo({ number: 2 })
peerInfo.multiaddrs.add(listenAddr)
remotePeerInfo.multiaddrs.add(remoteListenAddr)
}) })
describe('dht started before connect', () => { describe('dht started before connect', () => {
beforeEach(async () => { beforeEach(async () => {
libp2p = await create(mergeOptions(subsystemOptions, { libp2p = await create(mergeOptions(subsystemOptions, {
peerId, peerInfo
addresses: {
listen: [listenAddr]
}
})) }))
remoteLibp2p = await create(mergeOptions(subsystemOptions, { remoteLibp2p = await create(mergeOptions(subsystemOptions, {
peerId: remotePeerId, peerInfo: remotePeerInfo
addresses: {
listen: [remoteListenAddr]
}
})) }))
await Promise.all([ await Promise.all([
@ -47,8 +43,7 @@ describe('DHT subsystem operates correctly', () => {
remoteLibp2p.start() remoteLibp2p.start()
]) ])
libp2p.peerStore.addressBook.set(remotePeerId, [remoteListenAddr]) remAddr = remoteLibp2p.transportManager.getAddrs()[0]
remAddr = libp2p.peerStore.addressBook.getMultiaddrsForPeer(remotePeerId)[0]
}) })
afterEach(() => Promise.all([ afterEach(() => Promise.all([
@ -72,14 +67,15 @@ describe('DHT subsystem operates correctly', () => {
const value = Buffer.from('world') const value = Buffer.from('world')
await libp2p.dialProtocol(remAddr, subsystemMulticodecs) await libp2p.dialProtocol(remAddr, subsystemMulticodecs)
await Promise.all([ await Promise.all([
pWaitFor(() => libp2p._dht.routingTable.size === 1), pWaitFor(() => libp2p._dht.routingTable.size === 1),
pWaitFor(() => remoteLibp2p._dht.routingTable.size === 1) pWaitFor(() => remoteLibp2p._dht.routingTable.size === 1)
]) ])
await libp2p.contentRouting.put(key, value) await libp2p.contentRouting.put(key, value)
const fetchedValue = await remoteLibp2p.contentRouting.get(key)
const fetchedValue = await remoteLibp2p.contentRouting.get(key)
expect(fetchedValue).to.eql(value) expect(fetchedValue).to.eql(value)
}) })
}) })
@ -87,17 +83,11 @@ describe('DHT subsystem operates correctly', () => {
describe('dht started after connect', () => { describe('dht started after connect', () => {
beforeEach(async () => { beforeEach(async () => {
libp2p = await create(mergeOptions(subsystemOptions, { libp2p = await create(mergeOptions(subsystemOptions, {
peerId, peerInfo
addresses: {
listen: [listenAddr]
}
})) }))
remoteLibp2p = await create(mergeOptions(subsystemOptions, { remoteLibp2p = await create(mergeOptions(subsystemOptions, {
peerId: remotePeerId, peerInfo: remotePeerInfo,
addresses: {
listen: [remoteListenAddr]
},
config: { config: {
dht: { dht: {
enabled: false enabled: false
@ -108,8 +98,7 @@ describe('DHT subsystem operates correctly', () => {
await libp2p.start() await libp2p.start()
await remoteLibp2p.start() await remoteLibp2p.start()
libp2p.peerStore.addressBook.set(remotePeerId, [remoteListenAddr]) remAddr = remoteLibp2p.transportManager.getAddrs()[0]
remAddr = libp2p.peerStore.addressBook.getMultiaddrsForPeer(remotePeerId)[0]
}) })
afterEach(() => Promise.all([ afterEach(() => Promise.all([

View File

@ -1,57 +0,0 @@
'use strict'
/* eslint-env mocha */
const chai = require('chai')
chai.use(require('dirty-chai'))
chai.use(require('chai-as-promised'))
const { expect } = chai
const Transport = require('libp2p-websockets')
const { NOISE: Crypto } = require('libp2p-noise')
const Libp2p = require('../../src')
const { codes: ErrorCodes } = require('../../src/errors')
const { createPeerId } = require('../utils/creators/peer')
describe('Connection encryption configuration', () => {
let peerId
before(async () => {
[peerId] = await createPeerId()
})
it('is required', async () => {
const config = {
peerId,
modules: {
transport: [Transport]
}
}
await expect(Libp2p.create(config)).to.eventually.be.rejected()
.and.to.have.property('code', ErrorCodes.CONN_ENCRYPTION_REQUIRED)
})
it('is required and needs at least one module', async () => {
const config = {
peerId,
modules: {
transport: [Transport],
connEncryption: []
}
}
await expect(Libp2p.create(config)).to.eventually.be.rejected()
.and.to.have.property('code', ErrorCodes.CONN_ENCRYPTION_REQUIRED)
})
it('can be created', async () => {
const config = {
peerId,
modules: {
transport: [Transport],
connEncryption: [Crypto]
}
}
await Libp2p.create(config)
})
})

View File

@ -5,20 +5,21 @@ const chai = require('chai')
chai.use(require('dirty-chai')) chai.use(require('dirty-chai'))
const { expect } = chai const { expect } = chai
const multiaddr = require('multiaddr')
const Transport = require('libp2p-tcp') const Transport = require('libp2p-tcp')
const { NOISE: Crypto } = require('libp2p-noise')
const { create } = require('../../src') const { create } = require('../../src')
const peerUtils = require('../utils/creators/peer') const peerUtils = require('../utils/creators/peer')
const listenAddr = '/ip4/0.0.0.0/tcp/0' const listenAddr = multiaddr('/ip4/0.0.0.0/tcp/0')
describe('Listening', () => { describe('Listening', () => {
let peerId let peerInfo
let libp2p let libp2p
before(async () => { before(async () => {
[peerId] = await peerUtils.createPeerId() [peerInfo] = await peerUtils.createPeerInfo()
peerInfo.multiaddrs.add(listenAddr)
}) })
after(async () => { after(async () => {
@ -27,19 +28,15 @@ describe('Listening', () => {
it('should replace wildcard host and port with actual host and port on startup', async () => { it('should replace wildcard host and port with actual host and port on startup', async () => {
libp2p = await create({ libp2p = await create({
peerId, peerInfo,
addresses: {
listen: [listenAddr]
},
modules: { modules: {
transport: [Transport], transport: [Transport]
connEncryption: [Crypto]
} }
}) })
await libp2p.start() await libp2p.start()
const addrs = libp2p.transportManager.getAddrs() const addrs = libp2p.peerInfo.multiaddrs.toArray()
// Should get something like: // Should get something like:
// /ip4/127.0.0.1/tcp/50866 // /ip4/127.0.0.1/tcp/50866

View File

@ -20,19 +20,16 @@ describe('ping', () => {
number: 2, number: 2,
config: baseOptions config: baseOptions
}) })
nodes[0].peerStore.addressBook.set(nodes[1].peerId, nodes[1].multiaddrs)
nodes[1].peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs)
}) })
it('ping once from peer0 to peer1', async () => { it('ping once from peer0 to peer1', async () => {
const latency = await nodes[0].ping(nodes[1].peerId) const latency = await nodes[0].ping(nodes[1].peerInfo)
expect(latency).to.be.a('Number') expect(latency).to.be.a('Number')
}) })
it('ping several times for getting an average', async () => { it('ping several times for getting an average', async () => {
const latencies = await pTimes(5, () => nodes[1].ping(nodes[0].peerId)) const latencies = await pTimes(5, () => nodes[1].ping(nodes[0].peerInfo))
const averageLatency = latencies.reduce((p, c) => p + c, 0) / latencies.length const averageLatency = latencies.reduce((p, c) => p + c, 0) / latencies.length
expect(averageLatency).to.be.a('Number') expect(averageLatency).to.be.a('Number')
@ -69,7 +66,7 @@ describe('ping', () => {
) )
}) })
const latency = await nodes[0].ping(nodes[1].peerId) const latency = await nodes[0].ping(nodes[1].peerInfo)
expect(latency).to.be.a('Number') expect(latency).to.be.a('Number')
}) })

View File

@ -1,7 +1,6 @@
'use strict' 'use strict'
/* eslint-env mocha */ /* eslint-env mocha */
const { Buffer } = require('buffer')
const chai = require('chai') const chai = require('chai')
chai.use(require('dirty-chai')) chai.use(require('dirty-chai'))
chai.use(require('chai-as-promised')) chai.use(require('chai-as-promised'))
@ -9,9 +8,10 @@ const { expect } = chai
const sinon = require('sinon') const sinon = require('sinon')
const Transport = require('libp2p-tcp') const Transport = require('libp2p-tcp')
const Muxer = require('libp2p-mplex') const Muxer = require('libp2p-mplex')
const { NOISE: Crypto } = require('libp2p-noise') const Crypto = require('libp2p-secio')
const multiaddr = require('multiaddr') const multiaddr = require('multiaddr')
const PeerId = require('peer-id') const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const delay = require('delay') const delay = require('delay')
const pDefer = require('p-defer') const pDefer = require('p-defer')
const pSettle = require('p-settle') const pSettle = require('p-settle')
@ -22,7 +22,6 @@ const { AbortError } = require('libp2p-interfaces/src/transport/errors')
const Libp2p = require('../../src') const Libp2p = require('../../src')
const Dialer = require('../../src/dialer') const Dialer = require('../../src/dialer')
const AddressManager = require('../../src/address-manager')
const PeerStore = require('../../src/peer-store') const PeerStore = require('../../src/peer-store')
const TransportManager = require('../../src/transport-manager') const TransportManager = require('../../src/transport-manager')
const { codes: ErrorCodes } = require('../../src/errors') const { codes: ErrorCodes } = require('../../src/errors')
@ -32,30 +31,23 @@ const swarmKeyBuffer = Buffer.from(require('../fixtures/swarm.key'))
const mockUpgrader = require('../utils/mockUpgrader') const mockUpgrader = require('../utils/mockUpgrader')
const createMockConnection = require('../utils/mockConnection') const createMockConnection = require('../utils/mockConnection')
const Peers = require('../fixtures/peers') const Peers = require('../fixtures/peers')
const { createPeerId } = require('../utils/creators/peer') const { createPeerInfo } = require('../utils/creators/peer')
const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0')
const unsupportedAddr = multiaddr('/ip4/127.0.0.1/tcp/9999/ws/p2p/QmckxVrJw1Yo8LqvmDJNUmdAsKtSbiKWmrXJFyKmUraBoN') const unsupportedAddr = multiaddr('/ip4/127.0.0.1/tcp/9999/ws')
describe('Dialing (direct, TCP)', () => { describe('Dialing (direct, TCP)', () => {
let remoteTM let remoteTM
let localTM let localTM
let peerStore
let remoteAddr let remoteAddr
before(async () => { before(async () => {
const [remotePeerId] = await Promise.all([
PeerId.createFromJSON(Peers[0])
])
remoteTM = new TransportManager({ remoteTM = new TransportManager({
libp2p: { libp2p: {},
addressManager: new AddressManager({ listen: [listenAddr] })
},
upgrader: mockUpgrader upgrader: mockUpgrader
}) })
remoteTM.add(Transport.prototype[Symbol.toStringTag], Transport) remoteTM.add(Transport.prototype[Symbol.toStringTag], Transport)
peerStore = new PeerStore()
localTM = new TransportManager({ localTM = new TransportManager({
libp2p: {}, libp2p: {},
upgrader: mockUpgrader upgrader: mockUpgrader
@ -64,7 +56,7 @@ describe('Dialing (direct, TCP)', () => {
await remoteTM.listen([listenAddr]) await remoteTM.listen([listenAddr])
remoteAddr = remoteTM.getAddrs()[0].encapsulate(`/p2p/${remotePeerId.toB58String()}`) remoteAddr = remoteTM.getAddrs()[0]
}) })
after(() => remoteTM.close()) after(() => remoteTM.close())
@ -74,7 +66,7 @@ describe('Dialing (direct, TCP)', () => {
}) })
it('should be able to connect to a remote node via its multiaddr', async () => { it('should be able to connect to a remote node via its multiaddr', async () => {
const dialer = new Dialer({ transportManager: localTM, peerStore }) const dialer = new Dialer({ transportManager: localTM })
const connection = await dialer.connectToPeer(remoteAddr) const connection = await dialer.connectToPeer(remoteAddr)
expect(connection).to.exist() expect(connection).to.exist()
@ -82,20 +74,37 @@ describe('Dialing (direct, TCP)', () => {
}) })
it('should be able to connect to a remote node via its stringified multiaddr', async () => { it('should be able to connect to a remote node via its stringified multiaddr', async () => {
const dialer = new Dialer({ transportManager: localTM, peerStore }) const dialer = new Dialer({ transportManager: localTM })
const connection = await dialer.connectToPeer(remoteAddr.toString())
const dialable = Dialer.getDialable(remoteAddr.toString())
const connection = await dialer.connectToPeer(dialable)
expect(connection).to.exist() expect(connection).to.exist()
await connection.close() await connection.close()
}) })
it('should fail to connect to an unsupported multiaddr', async () => { it('should fail to connect to an unsupported multiaddr', async () => {
const dialer = new Dialer({ transportManager: localTM, peerStore }) const dialer = new Dialer({ transportManager: localTM })
await expect(dialer.connectToPeer(unsupportedAddr)) await expect(dialer.connectToPeer(unsupportedAddr))
.to.eventually.be.rejectedWith(AggregateError) .to.eventually.be.rejectedWith(AggregateError)
.and.to.have.nested.property('._errors[0].code', ErrorCodes.ERR_TRANSPORT_UNAVAILABLE) .and.to.have.nested.property('._errors[0].code', ErrorCodes.ERR_TRANSPORT_UNAVAILABLE)
}) })
it('should be able to connect to a given peer info', async () => {
const dialer = new Dialer({
transportManager: localTM,
peerStore: {
multiaddrsForPeer: () => [remoteAddr]
}
})
const peerId = await PeerId.createFromJSON(Peers[0])
const peerInfo = new PeerInfo(peerId)
const connection = await dialer.connectToPeer(peerInfo)
expect(connection).to.exist()
await connection.close()
})
it('should be able to connect to a given peer id', async () => { it('should be able to connect to a given peer id', async () => {
const peerStore = new PeerStore() const peerStore = new PeerStore()
const dialer = new Dialer({ const dialer = new Dialer({
@ -104,9 +113,11 @@ describe('Dialing (direct, TCP)', () => {
}) })
const peerId = await PeerId.createFromJSON(Peers[0]) const peerId = await PeerId.createFromJSON(Peers[0])
peerStore.addressBook.set(peerId, [remoteAddr]) const peerInfo = new PeerInfo(peerId)
peerInfo.multiaddrs.add(remoteAddr)
peerStore.put(peerInfo)
const connection = await dialer.connectToPeer(peerId) const connection = await dialer.connectToPeer(peerInfo)
expect(connection).to.exist() expect(connection).to.exist()
await connection.close() await connection.close()
}) })
@ -115,15 +126,13 @@ describe('Dialing (direct, TCP)', () => {
const dialer = new Dialer({ const dialer = new Dialer({
transportManager: localTM, transportManager: localTM,
peerStore: { peerStore: {
addressBook: { multiaddrsForPeer: () => [unsupportedAddr]
add: () => {},
getMultiaddrsForPeer: () => [unsupportedAddr]
}
} }
}) })
const peerId = await PeerId.createFromJSON(Peers[0]) const peerId = await PeerId.createFromJSON(Peers[0])
const peerInfo = new PeerInfo(peerId)
await expect(dialer.connectToPeer(peerId)) await expect(dialer.connectToPeer(peerInfo))
.to.eventually.be.rejectedWith(AggregateError) .to.eventually.be.rejectedWith(AggregateError)
.and.to.have.nested.property('._errors[0].code', ErrorCodes.ERR_TRANSPORT_UNAVAILABLE) .and.to.have.nested.property('._errors[0].code', ErrorCodes.ERR_TRANSPORT_UNAVAILABLE)
}) })
@ -131,7 +140,6 @@ describe('Dialing (direct, TCP)', () => {
it('should abort dials on queue task timeout', async () => { it('should abort dials on queue task timeout', async () => {
const dialer = new Dialer({ const dialer = new Dialer({
transportManager: localTM, transportManager: localTM,
peerStore,
timeout: 50 timeout: 50
}) })
sinon.stub(localTM, 'dial').callsFake(async (addr, options) => { sinon.stub(localTM, 'dial').callsFake(async (addr, options) => {
@ -158,10 +166,7 @@ describe('Dialing (direct, TCP)', () => {
transportManager: localTM, transportManager: localTM,
concurrency: 2, concurrency: 2,
peerStore: { peerStore: {
addressBook: { multiaddrsForPeer: () => addrs
add: () => {},
getMultiaddrsForPeer: () => addrs
}
} }
}) })
@ -170,10 +175,10 @@ describe('Dialing (direct, TCP)', () => {
const deferredDial = pDefer() const deferredDial = pDefer()
sinon.stub(localTM, 'dial').callsFake(() => deferredDial.promise) sinon.stub(localTM, 'dial').callsFake(() => deferredDial.promise)
const [peerId] = await createPeerId() const [peerInfo] = await createPeerInfo()
// Perform 3 multiaddr dials // Perform 3 multiaddr dials
dialer.connectToPeer(peerId) dialer.connectToPeer(peerInfo)
// Let the call stack run // Let the call stack run
await delay(0) await delay(0)
@ -192,32 +197,34 @@ describe('Dialing (direct, TCP)', () => {
}) })
describe('libp2p.dialer', () => { describe('libp2p.dialer', () => {
let peerId, remotePeerId let peerInfo
let remotePeerInfo
let libp2p let libp2p
let remoteLibp2p let remoteLibp2p
let remoteAddr let remoteAddr
before(async () => { before(async () => {
[peerId, remotePeerId] = await Promise.all([ const [peerId, remotePeerId] = await Promise.all([
PeerId.createFromJSON(Peers[0]), PeerId.createFromJSON(Peers[0]),
PeerId.createFromJSON(Peers[1]) PeerId.createFromJSON(Peers[1])
]) ])
peerInfo = new PeerInfo(peerId)
remotePeerInfo = new PeerInfo(remotePeerId)
remoteLibp2p = new Libp2p({ remoteLibp2p = new Libp2p({
peerId: remotePeerId, peerInfo: remotePeerInfo,
addresses: {
listen: [listenAddr]
},
modules: { modules: {
transport: [Transport], transport: [Transport],
streamMuxer: [Muxer], streamMuxer: [Muxer],
connEncryption: [Crypto] connEncryption: [Crypto]
} }
}) })
remoteLibp2p.peerInfo.multiaddrs.add(listenAddr)
remoteLibp2p.handle('/echo/1.0.0', ({ stream }) => pipe(stream, stream)) remoteLibp2p.handle('/echo/1.0.0', ({ stream }) => pipe(stream, stream))
await remoteLibp2p.start() await remoteLibp2p.start()
remoteAddr = remoteLibp2p.transportManager.getAddrs()[0].encapsulate(`/p2p/${remotePeerId.toB58String()}`) remoteAddr = remoteLibp2p.transportManager.getAddrs()[0]
}) })
afterEach(async () => { afterEach(async () => {
@ -228,31 +235,9 @@ describe('Dialing (direct, TCP)', () => {
after(() => remoteLibp2p.stop()) after(() => remoteLibp2p.stop())
it('should fail if no peer id is provided', async () => {
libp2p = new Libp2p({
peerId,
modules: {
transport: [Transport],
streamMuxer: [Muxer],
connEncryption: [Crypto]
}
})
sinon.spy(libp2p.dialer, 'connectToPeer')
try {
await libp2p.dial(remoteLibp2p.transportManager.getAddrs()[0])
} catch (err) {
expect(err).to.have.property('code', ErrorCodes.ERR_INVALID_MULTIADDR)
return
}
expect.fail('dial should have failed')
})
it('should use the dialer for connecting to a multiaddr', async () => { it('should use the dialer for connecting to a multiaddr', async () => {
libp2p = new Libp2p({ libp2p = new Libp2p({
peerId, peerInfo,
modules: { modules: {
transport: [Transport], transport: [Transport],
streamMuxer: [Muxer], streamMuxer: [Muxer],
@ -273,7 +258,7 @@ describe('Dialing (direct, TCP)', () => {
it('should use the dialer for connecting to a peer', async () => { it('should use the dialer for connecting to a peer', async () => {
libp2p = new Libp2p({ libp2p = new Libp2p({
peerId, peerInfo,
modules: { modules: {
transport: [Transport], transport: [Transport],
streamMuxer: [Muxer], streamMuxer: [Muxer],
@ -282,9 +267,10 @@ describe('Dialing (direct, TCP)', () => {
}) })
sinon.spy(libp2p.dialer, 'connectToPeer') sinon.spy(libp2p.dialer, 'connectToPeer')
libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) const remotePeer = new PeerInfo(remoteLibp2p.peerInfo.id)
remotePeer.multiaddrs.add(remoteAddr)
const connection = await libp2p.dial(remotePeerId) const connection = await libp2p.dial(remotePeer)
expect(connection).to.exist() expect(connection).to.exist()
const { stream, protocol } = await connection.newStream('/echo/1.0.0') const { stream, protocol } = await connection.newStream('/echo/1.0.0')
expect(stream).to.exist() expect(stream).to.exist()
@ -295,7 +281,7 @@ describe('Dialing (direct, TCP)', () => {
it('should be able to use hangup to close connections', async () => { it('should be able to use hangup to close connections', async () => {
libp2p = new Libp2p({ libp2p = new Libp2p({
peerId, peerInfo,
modules: { modules: {
transport: [Transport], transport: [Transport],
streamMuxer: [Muxer], streamMuxer: [Muxer],
@ -312,7 +298,7 @@ describe('Dialing (direct, TCP)', () => {
it('should be able to use hangup by address string to close connections', async () => { it('should be able to use hangup by address string to close connections', async () => {
libp2p = new Libp2p({ libp2p = new Libp2p({
peerId, peerInfo,
modules: { modules: {
transport: [Transport], transport: [Transport],
streamMuxer: [Muxer], streamMuxer: [Muxer],
@ -320,7 +306,7 @@ describe('Dialing (direct, TCP)', () => {
} }
}) })
const connection = await libp2p.dial(`${remoteAddr.toString()}`) const connection = await libp2p.dial(`${remoteAddr.toString()}/p2p/${remotePeerInfo.id.toB58String()}`)
expect(connection).to.exist() expect(connection).to.exist()
expect(connection.stat.timeline.close).to.not.exist() expect(connection.stat.timeline.close).to.not.exist()
await libp2p.hangUp(connection.remotePeer) await libp2p.hangUp(connection.remotePeer)
@ -330,7 +316,7 @@ describe('Dialing (direct, TCP)', () => {
it('should use the protectors when provided for connecting', async () => { it('should use the protectors when provided for connecting', async () => {
const protector = new Protector(swarmKeyBuffer) const protector = new Protector(swarmKeyBuffer)
libp2p = new Libp2p({ libp2p = new Libp2p({
peerId, peerInfo,
modules: { modules: {
transport: [Transport], transport: [Transport],
streamMuxer: [Muxer], streamMuxer: [Muxer],
@ -351,9 +337,9 @@ describe('Dialing (direct, TCP)', () => {
expect(libp2p.upgrader.protector.protect.callCount).to.equal(1) expect(libp2p.upgrader.protector.protect.callCount).to.equal(1)
}) })
it('should coalesce parallel dials to the same peer (id in multiaddr)', async () => { it('should coalesce parallel dials to the same peer (no id in multiaddr)', async () => {
libp2p = new Libp2p({ libp2p = new Libp2p({
peerId, peerInfo,
modules: { modules: {
transport: [Transport], transport: [Transport],
streamMuxer: [Muxer], streamMuxer: [Muxer],
@ -362,11 +348,36 @@ describe('Dialing (direct, TCP)', () => {
}) })
const dials = 10 const dials = 10
const fullAddress = remoteAddr.encapsulate(`/p2p/${remoteLibp2p.peerId.toB58String()}`)
libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs)
const dialResults = await Promise.all([...new Array(dials)].map((_, index) => { const dialResults = await Promise.all([...new Array(dials)].map((_, index) => {
if (index % 2 === 0) return libp2p.dial(remoteLibp2p.peerId) if (index % 2 === 0) return libp2p.dial(remoteLibp2p.peerInfo)
return libp2p.dial(remoteLibp2p.peerInfo.multiaddrs.toArray()[0])
}))
// All should succeed and we should have ten results
expect(dialResults).to.have.length(10)
for (const connection of dialResults) {
expect(Connection.isConnection(connection)).to.equal(true)
}
// We will have two connections, since the multiaddr dial doesn't have a peer id
expect(libp2p.connectionManager._connections.size).to.equal(2)
expect(remoteLibp2p.connectionManager._connections.size).to.equal(2)
})
it('should coalesce parallel dials to the same peer (id in multiaddr)', async () => {
libp2p = new Libp2p({
peerInfo,
modules: {
transport: [Transport],
streamMuxer: [Muxer],
connEncryption: [Crypto]
}
})
const dials = 10
const fullAddress = remoteAddr.encapsulate(`/p2p/${remoteLibp2p.peerInfo.id.toB58String()}`)
const dialResults = await Promise.all([...new Array(dials)].map((_, index) => {
if (index % 2 === 0) return libp2p.dial(remoteLibp2p.peerInfo)
return libp2p.dial(fullAddress) return libp2p.dial(fullAddress)
})) }))
@ -377,13 +388,13 @@ describe('Dialing (direct, TCP)', () => {
} }
// 1 connection, because we know the peer in the multiaddr // 1 connection, because we know the peer in the multiaddr
expect(libp2p.connectionManager.size).to.equal(1) expect(libp2p.connectionManager._connections.size).to.equal(1)
expect(remoteLibp2p.connectionManager.size).to.equal(1) expect(remoteLibp2p.connectionManager._connections.size).to.equal(1)
}) })
it('should coalesce parallel dials to the same error on failure', async () => { it('should coalesce parallel dials to the same error on failure', async () => {
libp2p = new Libp2p({ libp2p = new Libp2p({
peerId, peerInfo,
modules: { modules: {
transport: [Transport], transport: [Transport],
streamMuxer: [Muxer], streamMuxer: [Muxer],
@ -394,10 +405,10 @@ describe('Dialing (direct, TCP)', () => {
const error = new Error('Boom') const error = new Error('Boom')
sinon.stub(libp2p.transportManager, 'dial').callsFake(() => Promise.reject(error)) sinon.stub(libp2p.transportManager, 'dial').callsFake(() => Promise.reject(error))
libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) const fullAddress = remoteAddr.encapsulate(`/p2p/${remoteLibp2p.peerInfo.id.toB58String()}`)
const dialResults = await pSettle([...new Array(dials)].map((_, index) => { const dialResults = await pSettle([...new Array(dials)].map((_, index) => {
if (index % 2 === 0) return libp2p.dial(remoteLibp2p.peerId) if (index % 2 === 0) return libp2p.dial(remoteLibp2p.peerInfo)
return libp2p.dial(remoteAddr) return libp2p.dial(fullAddress)
})) }))
// All should succeed and we should have ten results // All should succeed and we should have ten results
@ -412,8 +423,8 @@ describe('Dialing (direct, TCP)', () => {
} }
// 1 connection, because we know the peer in the multiaddr // 1 connection, because we know the peer in the multiaddr
expect(libp2p.connectionManager.size).to.equal(0) expect(libp2p.connectionManager._connections.size).to.equal(0)
expect(remoteLibp2p.connectionManager.size).to.equal(0) expect(remoteLibp2p.connectionManager._connections.size).to.equal(0)
}) })
}) })
}) })

View File

@ -11,16 +11,16 @@ const pWaitFor = require('p-wait-for')
const delay = require('delay') const delay = require('delay')
const Transport = require('libp2p-websockets') const Transport = require('libp2p-websockets')
const Muxer = require('libp2p-mplex') const Muxer = require('libp2p-mplex')
const { NOISE: Crypto } = require('libp2p-noise') const Crypto = require('libp2p-secio')
const multiaddr = require('multiaddr') const multiaddr = require('multiaddr')
const PeerId = require('peer-id') const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const AggregateError = require('aggregate-error') const AggregateError = require('aggregate-error')
const { AbortError } = require('libp2p-interfaces/src/transport/errors') const { AbortError } = require('libp2p-interfaces/src/transport/errors')
const { codes: ErrorCodes } = require('../../src/errors') const { codes: ErrorCodes } = require('../../src/errors')
const Constants = require('../../src/constants') const Constants = require('../../src/constants')
const Dialer = require('../../src/dialer') const Dialer = require('../../src/dialer')
const PeerStore = require('../../src/peer-store')
const TransportManager = require('../../src/transport-manager') const TransportManager = require('../../src/transport-manager')
const Libp2p = require('../../src') const Libp2p = require('../../src')
@ -29,15 +29,13 @@ const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser')
const mockUpgrader = require('../utils/mockUpgrader') const mockUpgrader = require('../utils/mockUpgrader')
const createMockConnection = require('../utils/mockConnection') const createMockConnection = require('../utils/mockConnection')
const { createPeerId } = require('../utils/creators/peer') const { createPeerId } = require('../utils/creators/peer')
const unsupportedAddr = multiaddr('/ip4/127.0.0.1/tcp/9999/ws/p2p/QmckxVrJw1Yo8LqvmDJNUmdAsKtSbiKWmrXJFyKmUraBoN') const unsupportedAddr = multiaddr('/ip4/127.0.0.1/tcp/9999/ws')
const remoteAddr = MULTIADDRS_WEBSOCKETS[0] const remoteAddr = MULTIADDRS_WEBSOCKETS[0]
describe('Dialing (direct, WebSockets)', () => { describe('Dialing (direct, WebSockets)', () => {
let localTM let localTM
let peerStore
before(() => { before(() => {
peerStore = new PeerStore()
localTM = new TransportManager({ localTM = new TransportManager({
libp2p: {}, libp2p: {},
upgrader: mockUpgrader, upgrader: mockUpgrader,
@ -51,13 +49,13 @@ describe('Dialing (direct, WebSockets)', () => {
}) })
it('should have appropriate defaults', () => { it('should have appropriate defaults', () => {
const dialer = new Dialer({ transportManager: localTM, peerStore }) const dialer = new Dialer({ transportManager: localTM })
expect(dialer.concurrency).to.equal(Constants.MAX_PARALLEL_DIALS) expect(dialer.concurrency).to.equal(Constants.MAX_PARALLEL_DIALS)
expect(dialer.timeout).to.equal(Constants.DIAL_TIMEOUT) expect(dialer.timeout).to.equal(Constants.DIAL_TIMEOUT)
}) })
it('should limit the number of tokens it provides', () => { it('should limit the number of tokens it provides', () => {
const dialer = new Dialer({ transportManager: localTM, peerStore }) const dialer = new Dialer({ transportManager: localTM })
const maxPerPeer = Constants.MAX_PER_PEER_DIALS const maxPerPeer = Constants.MAX_PER_PEER_DIALS
expect(dialer.tokens).to.have.length(Constants.MAX_PARALLEL_DIALS) expect(dialer.tokens).to.have.length(Constants.MAX_PARALLEL_DIALS)
const tokens = dialer.getTokens(maxPerPeer + 1) const tokens = dialer.getTokens(maxPerPeer + 1)
@ -66,14 +64,14 @@ describe('Dialing (direct, WebSockets)', () => {
}) })
it('should not return tokens if non are left', () => { it('should not return tokens if non are left', () => {
const dialer = new Dialer({ transportManager: localTM, peerStore }) const dialer = new Dialer({ transportManager: localTM })
sinon.stub(dialer, 'tokens').value([]) sinon.stub(dialer, 'tokens').value([])
const tokens = dialer.getTokens(1) const tokens = dialer.getTokens(1)
expect(tokens.length).to.equal(0) expect(tokens.length).to.equal(0)
}) })
it('should NOT be able to return a token twice', () => { it('should NOT be able to return a token twice', () => {
const dialer = new Dialer({ transportManager: localTM, peerStore }) const dialer = new Dialer({ transportManager: localTM })
const tokens = dialer.getTokens(1) const tokens = dialer.getTokens(1)
expect(tokens).to.have.length(1) expect(tokens).to.have.length(1)
expect(dialer.tokens).to.have.length(Constants.MAX_PARALLEL_DIALS - 1) expect(dialer.tokens).to.have.length(Constants.MAX_PARALLEL_DIALS - 1)
@ -86,10 +84,7 @@ describe('Dialing (direct, WebSockets)', () => {
const dialer = new Dialer({ const dialer = new Dialer({
transportManager: localTM, transportManager: localTM,
peerStore: { peerStore: {
addressBook: { multiaddrsForPeer: () => [remoteAddr]
add: () => {},
getMultiaddrsForPeer: () => [remoteAddr]
}
} }
}) })
@ -102,10 +97,7 @@ describe('Dialing (direct, WebSockets)', () => {
const dialer = new Dialer({ const dialer = new Dialer({
transportManager: localTM, transportManager: localTM,
peerStore: { peerStore: {
addressBook: { multiaddrsForPeer: () => [remoteAddr]
add: () => {},
getMultiaddrsForPeer: () => [remoteAddr]
}
} }
}) })
@ -115,7 +107,7 @@ describe('Dialing (direct, WebSockets)', () => {
}) })
it('should fail to connect to an unsupported multiaddr', async () => { it('should fail to connect to an unsupported multiaddr', async () => {
const dialer = new Dialer({ transportManager: localTM, peerStore }) const dialer = new Dialer({ transportManager: localTM })
await expect(dialer.connectToPeer(unsupportedAddr)) await expect(dialer.connectToPeer(unsupportedAddr))
.to.eventually.be.rejectedWith(AggregateError) .to.eventually.be.rejectedWith(AggregateError)
@ -126,10 +118,7 @@ describe('Dialing (direct, WebSockets)', () => {
const dialer = new Dialer({ const dialer = new Dialer({
transportManager: localTM, transportManager: localTM,
peerStore: { peerStore: {
addressBook: { multiaddrsForPeer: () => [remoteAddr]
add: () => {},
getMultiaddrsForPeer: () => [remoteAddr]
}
} }
}) })
const peerId = await PeerId.createFromJSON(Peers[0]) const peerId = await PeerId.createFromJSON(Peers[0])
@ -143,10 +132,7 @@ describe('Dialing (direct, WebSockets)', () => {
const dialer = new Dialer({ const dialer = new Dialer({
transportManager: localTM, transportManager: localTM,
peerStore: { peerStore: {
addressBook: { multiaddrsForPeer: () => [unsupportedAddr]
set: () => {},
getMultiaddrsForPeer: () => [unsupportedAddr]
}
} }
}) })
const peerId = await PeerId.createFromJSON(Peers[0]) const peerId = await PeerId.createFromJSON(Peers[0])
@ -161,10 +147,7 @@ describe('Dialing (direct, WebSockets)', () => {
transportManager: localTM, transportManager: localTM,
timeout: 50, timeout: 50,
peerStore: { peerStore: {
addressBook: { multiaddrsForPeer: () => [remoteAddr]
add: () => {},
getMultiaddrsForPeer: () => [remoteAddr]
}
} }
}) })
sinon.stub(localTM, 'dial').callsFake(async (addr, options) => { sinon.stub(localTM, 'dial').callsFake(async (addr, options) => {
@ -186,10 +169,7 @@ describe('Dialing (direct, WebSockets)', () => {
transportManager: localTM, transportManager: localTM,
concurrency: 2, concurrency: 2,
peerStore: { peerStore: {
addressBook: { multiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr]
set: () => {},
getMultiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr]
}
} }
}) })
@ -225,10 +205,7 @@ describe('Dialing (direct, WebSockets)', () => {
transportManager: localTM, transportManager: localTM,
concurrency: 2, concurrency: 2,
peerStore: { peerStore: {
addressBook: { multiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr]
set: () => {},
getMultiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr]
}
} }
}) })
@ -266,12 +243,13 @@ describe('Dialing (direct, WebSockets)', () => {
}) })
describe('libp2p.dialer', () => { describe('libp2p.dialer', () => {
let peerId let peerInfo
let libp2p let libp2p
let remoteLibp2p let remoteLibp2p
before(async () => { before(async () => {
peerId = await PeerId.createFromJSON(Peers[0]) const peerId = await PeerId.createFromJSON(Peers[0])
peerInfo = new PeerInfo(peerId)
}) })
afterEach(async () => { afterEach(async () => {
@ -286,7 +264,7 @@ describe('Dialing (direct, WebSockets)', () => {
it('should create a dialer', () => { it('should create a dialer', () => {
libp2p = new Libp2p({ libp2p = new Libp2p({
peerId, peerInfo,
modules: { modules: {
transport: [Transport], transport: [Transport],
streamMuxer: [Muxer], streamMuxer: [Muxer],
@ -304,7 +282,7 @@ describe('Dialing (direct, WebSockets)', () => {
it('should be able to override dialer options', async () => { it('should be able to override dialer options', async () => {
const config = { const config = {
peerId, peerInfo,
modules: { modules: {
transport: [Transport], transport: [Transport],
streamMuxer: [Muxer], streamMuxer: [Muxer],
@ -326,7 +304,7 @@ describe('Dialing (direct, WebSockets)', () => {
it('should use the dialer for connecting', async () => { it('should use the dialer for connecting', async () => {
libp2p = new Libp2p({ libp2p = new Libp2p({
peerId, peerInfo,
modules: { modules: {
transport: [Transport], transport: [Transport],
streamMuxer: [Muxer], streamMuxer: [Muxer],
@ -335,7 +313,7 @@ describe('Dialing (direct, WebSockets)', () => {
}) })
sinon.spy(libp2p.dialer, 'connectToPeer') sinon.spy(libp2p.dialer, 'connectToPeer')
sinon.spy(libp2p.peerStore.addressBook, 'add') sinon.spy(libp2p.peerStore, 'put')
const connection = await libp2p.dial(remoteAddr) const connection = await libp2p.dial(remoteAddr)
expect(connection).to.exist() expect(connection).to.exist()
@ -344,12 +322,12 @@ describe('Dialing (direct, WebSockets)', () => {
expect(protocol).to.equal('/echo/1.0.0') expect(protocol).to.equal('/echo/1.0.0')
await connection.close() await connection.close()
expect(libp2p.dialer.connectToPeer.callCount).to.equal(1) expect(libp2p.dialer.connectToPeer.callCount).to.equal(1)
expect(libp2p.peerStore.addressBook.add.callCount).to.be.at.least(1) expect(libp2p.peerStore.put.callCount).to.be.at.least(1)
}) })
it('should run identify automatically after connecting', async () => { it('should run identify automatically after connecting', async () => {
libp2p = new Libp2p({ libp2p = new Libp2p({
peerId, peerInfo,
modules: { modules: {
transport: [Transport], transport: [Transport],
streamMuxer: [Muxer], streamMuxer: [Muxer],
@ -358,27 +336,24 @@ describe('Dialing (direct, WebSockets)', () => {
}) })
sinon.spy(libp2p.identifyService, 'identify') sinon.spy(libp2p.identifyService, 'identify')
sinon.spy(libp2p.peerStore, 'replace')
sinon.spy(libp2p.upgrader, 'onConnection') sinon.spy(libp2p.upgrader, 'onConnection')
const connection = await libp2p.dial(remoteAddr) const connection = await libp2p.dial(remoteAddr)
expect(connection).to.exist() expect(connection).to.exist()
sinon.spy(libp2p.peerStore.addressBook, 'set')
sinon.spy(libp2p.peerStore.protoBook, 'set')
// Wait for onConnection to be called // Wait for onConnection to be called
await pWaitFor(() => libp2p.upgrader.onConnection.callCount === 1) await pWaitFor(() => libp2p.upgrader.onConnection.callCount === 1)
expect(libp2p.identifyService.identify.callCount).to.equal(1) expect(libp2p.identifyService.identify.callCount).to.equal(1)
await libp2p.identifyService.identify.firstCall.returnValue await libp2p.identifyService.identify.firstCall.returnValue
expect(libp2p.peerStore.addressBook.set.callCount).to.equal(1) expect(libp2p.peerStore.replace.callCount).to.equal(1)
expect(libp2p.peerStore.protoBook.set.callCount).to.equal(1)
}) })
it('should be able to use hangup to close connections', async () => { it('should be able to use hangup to close connections', async () => {
libp2p = new Libp2p({ libp2p = new Libp2p({
peerId, peerInfo,
modules: { modules: {
transport: [Transport], transport: [Transport],
streamMuxer: [Muxer], streamMuxer: [Muxer],
@ -393,22 +368,9 @@ describe('Dialing (direct, WebSockets)', () => {
expect(connection.stat.timeline.close).to.exist() expect(connection.stat.timeline.close).to.exist()
}) })
it('should be able to use hangup when no connection exists', async () => {
libp2p = new Libp2p({
peerId,
modules: {
transport: [Transport],
streamMuxer: [Muxer],
connEncryption: [Crypto]
}
})
await libp2p.hangUp(remoteAddr)
})
it('should abort pending dials on stop', async () => { it('should abort pending dials on stop', async () => {
libp2p = new Libp2p({ libp2p = new Libp2p({
peerId, peerInfo,
modules: { modules: {
transport: [Transport], transport: [Transport],
streamMuxer: [Muxer], streamMuxer: [Muxer],

View File

@ -1,7 +1,6 @@
'use strict' 'use strict'
/* eslint-env mocha */ /* eslint-env mocha */
const { Buffer } = require('buffer')
const chai = require('chai') const chai = require('chai')
chai.use(require('dirty-chai')) chai.use(require('dirty-chai'))
chai.use(require('chai-as-promised')) chai.use(require('chai-as-promised'))
@ -12,32 +11,25 @@ const multiaddr = require('multiaddr')
const { collect } = require('streaming-iterables') const { collect } = require('streaming-iterables')
const pipe = require('it-pipe') const pipe = require('it-pipe')
const AggregateError = require('aggregate-error') const AggregateError = require('aggregate-error')
const PeerId = require('peer-id') const { createPeerInfo } = require('../utils/creators/peer')
const { createPeerId } = require('../utils/creators/peer')
const baseOptions = require('../utils/base-options') const baseOptions = require('../utils/base-options')
const Libp2p = require('../../src') const Libp2p = require('../../src')
const { codes: Errors } = require('../../src/errors') const { codes: Errors } = require('../../src/errors')
const listenAddr = '/ip4/0.0.0.0/tcp/0'
describe('Dialing (via relay, TCP)', () => { describe('Dialing (via relay, TCP)', () => {
let srcLibp2p let srcLibp2p
let relayLibp2p let relayLibp2p
let dstLibp2p let dstLibp2p
beforeEach(async () => { before(async () => {
const peerIds = await createPeerId({ number: 3 }) const peerInfos = await createPeerInfo({ number: 3 })
// Create 3 nodes, and turn HOP on for the relay // Create 3 nodes, and turn HOP on for the relay
;[srcLibp2p, relayLibp2p, dstLibp2p] = peerIds.map((peerId, index) => { ;[srcLibp2p, relayLibp2p, dstLibp2p] = peerInfos.map((peerInfo, index) => {
const opts = baseOptions const opts = baseOptions
index === 1 && (opts.config.relay.hop.enabled = true) index === 1 && (opts.config.relay.hop.enabled = true)
return new Libp2p({ return new Libp2p({
...opts, ...opts,
addresses: { peerInfo
listen: [listenAddr]
},
peerId
}) })
}) })
@ -46,7 +38,12 @@ describe('Dialing (via relay, TCP)', () => {
beforeEach(() => { beforeEach(() => {
// Start each node // Start each node
return Promise.all([srcLibp2p, relayLibp2p, dstLibp2p].map(libp2p => libp2p.start())) return Promise.all([srcLibp2p, relayLibp2p, dstLibp2p].map(libp2p => {
// Reset multiaddrs and start
libp2p.peerInfo.multiaddrs.clear()
libp2p.peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
return libp2p.start()
}))
}) })
afterEach(() => { afterEach(() => {
@ -54,37 +51,34 @@ describe('Dialing (via relay, TCP)', () => {
return Promise.all([srcLibp2p, relayLibp2p, dstLibp2p].map(async libp2p => { return Promise.all([srcLibp2p, relayLibp2p, dstLibp2p].map(async libp2p => {
await libp2p.stop() await libp2p.stop()
// Clear the peer stores // Clear the peer stores
for (const peerIdStr of libp2p.peerStore.peers.keys()) { for (const peerId of libp2p.peerStore.peers.keys()) {
const peerId = PeerId.createFromCID(peerIdStr) libp2p.peerStore.remove(peerId)
libp2p.peerStore.delete(peerId)
} }
})) }))
}) })
it('should be able to connect to a peer over a relay with active connections', async () => { it('should be able to connect to a peer over a relay with active connections', async () => {
const relayAddr = relayLibp2p.transportManager.getAddrs()[0] const relayAddr = relayLibp2p.transportManager.getAddrs()[0]
const relayIdString = relayLibp2p.peerId.toB58String() const relayIdString = relayLibp2p.peerInfo.id.toB58String()
const dialAddr = relayAddr const dialAddr = relayAddr
.encapsulate(`/p2p/${relayIdString}`) .encapsulate(`/p2p/${relayIdString}`)
.encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerId.toB58String()}`) .encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerInfo.id.toB58String()}`)
const tcpAddrs = dstLibp2p.transportManager.getAddrs() const tcpAddrs = dstLibp2p.transportManager.getAddrs()
sinon.stub(dstLibp2p.addressManager, 'listen').value([multiaddr(`/p2p-circuit${relayAddr}/p2p/${relayIdString}`)]) await dstLibp2p.transportManager.listen([multiaddr(`/p2p-circuit${relayAddr}/p2p/${relayIdString}`)])
await dstLibp2p.transportManager.listen()
expect(dstLibp2p.transportManager.getAddrs()).to.have.deep.members([...tcpAddrs, dialAddr.decapsulate('p2p')]) expect(dstLibp2p.transportManager.getAddrs()).to.have.deep.members([...tcpAddrs, dialAddr.decapsulate('p2p')])
const connection = await srcLibp2p.dial(dialAddr) const connection = await srcLibp2p.dial(dialAddr)
expect(connection).to.exist() expect(connection).to.exist()
expect(connection.remotePeer.toBytes()).to.eql(dstLibp2p.peerId.toBytes()) expect(connection.remotePeer.toBytes()).to.eql(dstLibp2p.peerInfo.id.toBytes())
expect(connection.localPeer.toBytes()).to.eql(srcLibp2p.peerId.toBytes()) expect(connection.localPeer.toBytes()).to.eql(srcLibp2p.peerInfo.id.toBytes())
expect(connection.remoteAddr).to.eql(dialAddr) expect(connection.remoteAddr).to.eql(dialAddr)
expect(connection.localAddr).to.eql( expect(connection.localAddr).to.eql(
relayAddr // the relay address relayAddr // the relay address
.encapsulate(`/p2p/${relayIdString}`) // with its peer id .encapsulate(`/p2p/${relayIdString}`) // with its peer id
.encapsulate('/p2p-circuit') // the local peer is connected over the relay .encapsulate('/p2p-circuit') // the local peer is connected over the relay
.encapsulate(`/p2p/${srcLibp2p.peerId.toB58String()}`) // and the local peer id .encapsulate(`/p2p/${srcLibp2p.peerInfo.id.toB58String()}`) // and the local peer id
) )
const { stream: echoStream } = await connection.newStream('/echo/1.0.0') const { stream: echoStream } = await connection.newStream('/echo/1.0.0')
@ -100,11 +94,11 @@ describe('Dialing (via relay, TCP)', () => {
it('should fail to connect to a peer over a relay with inactive connections', async () => { it('should fail to connect to a peer over a relay with inactive connections', async () => {
const relayAddr = relayLibp2p.transportManager.getAddrs()[0] const relayAddr = relayLibp2p.transportManager.getAddrs()[0]
const relayIdString = relayLibp2p.peerId.toB58String() const relayIdString = relayLibp2p.peerInfo.id.toB58String()
const dialAddr = relayAddr const dialAddr = relayAddr
.encapsulate(`/p2p/${relayIdString}`) .encapsulate(`/p2p/${relayIdString}`)
.encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerId.toB58String()}`) .encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerInfo.id.toB58String()}`)
await expect(srcLibp2p.dial(dialAddr)) await expect(srcLibp2p.dial(dialAddr))
.to.eventually.be.rejectedWith(AggregateError) .to.eventually.be.rejectedWith(AggregateError)
@ -113,27 +107,28 @@ describe('Dialing (via relay, TCP)', () => {
it('should not stay connected to a relay when not already connected and HOP fails', async () => { it('should not stay connected to a relay when not already connected and HOP fails', async () => {
const relayAddr = relayLibp2p.transportManager.getAddrs()[0] const relayAddr = relayLibp2p.transportManager.getAddrs()[0]
const relayIdString = relayLibp2p.peerId.toB58String() const relayIdString = relayLibp2p.peerInfo.id.toB58String()
const dialAddr = relayAddr const dialAddr = relayAddr
.encapsulate(`/p2p/${relayIdString}`) .encapsulate(`/p2p/${relayIdString}`)
.encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerId.toB58String()}`) .encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerInfo.id.toB58String()}`)
await expect(srcLibp2p.dial(dialAddr)) await expect(srcLibp2p.dial(dialAddr))
.to.eventually.be.rejectedWith(AggregateError) .to.eventually.be.rejectedWith(AggregateError)
.and.to.have.nested.property('._errors[0].code', Errors.ERR_HOP_REQUEST_FAILED) .and.to.have.nested.property('._errors[0].code', Errors.ERR_HOP_REQUEST_FAILED)
// We should not be connected to the relay, because we weren't before the dial // We should not be connected to the relay, because we weren't before the dial
const srcToRelayConn = srcLibp2p.connectionManager.get(relayLibp2p.peerId) const srcToRelayConn = srcLibp2p.registrar.getConnection(relayLibp2p.peerInfo)
expect(srcToRelayConn).to.not.exist() expect(srcToRelayConn).to.not.exist()
}) })
it('dialer should stay connected to an already connected relay on hop failure', async () => { it('dialer should stay connected to an already connected relay on hop failure', async () => {
const relayIdString = relayLibp2p.peerId.toB58String() const relayAddr = relayLibp2p.transportManager.getAddrs()[0]
const relayAddr = relayLibp2p.transportManager.getAddrs()[0].encapsulate(`/p2p/${relayIdString}`) const relayIdString = relayLibp2p.peerInfo.id.toB58String()
const dialAddr = relayAddr const dialAddr = relayAddr
.encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerId.toB58String()}`) .encapsulate(`/p2p/${relayIdString}`)
.encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerInfo.id.toB58String()}`)
await srcLibp2p.dial(relayAddr) await srcLibp2p.dial(relayAddr)
@ -141,27 +136,26 @@ describe('Dialing (via relay, TCP)', () => {
.to.eventually.be.rejectedWith(AggregateError) .to.eventually.be.rejectedWith(AggregateError)
.and.to.have.nested.property('._errors[0].code', Errors.ERR_HOP_REQUEST_FAILED) .and.to.have.nested.property('._errors[0].code', Errors.ERR_HOP_REQUEST_FAILED)
const srcToRelayConn = srcLibp2p.connectionManager.get(relayLibp2p.peerId) const srcToRelayConn = srcLibp2p.registrar.getConnection(relayLibp2p.peerInfo)
expect(srcToRelayConn).to.exist() expect(srcToRelayConn).to.exist()
expect(srcToRelayConn.stat.status).to.equal('open') expect(srcToRelayConn.stat.status).to.equal('open')
}) })
it('destination peer should stay connected to an already connected relay on hop failure', async () => { it('destination peer should stay connected to an already connected relay on hop failure', async () => {
const relayIdString = relayLibp2p.peerId.toB58String() const relayAddr = relayLibp2p.transportManager.getAddrs()[0]
const relayAddr = relayLibp2p.transportManager.getAddrs()[0].encapsulate(`/p2p/${relayIdString}`) const relayIdString = relayLibp2p.peerInfo.id.toB58String()
const dialAddr = relayAddr const dialAddr = relayAddr
.encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerId.toB58String()}`) .encapsulate(`/p2p/${relayIdString}`)
.encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerInfo.id.toB58String()}`)
// Connect the destination peer and the relay // Connect the destination peer and the relay
const tcpAddrs = dstLibp2p.transportManager.getAddrs() const tcpAddrs = dstLibp2p.transportManager.getAddrs()
sinon.stub(dstLibp2p.addressManager, 'getListenAddrs').returns([multiaddr(`${relayAddr}/p2p-circuit`)]) await dstLibp2p.transportManager.listen([multiaddr(`/p2p-circuit${relayAddr}/p2p/${relayIdString}`)])
await dstLibp2p.transportManager.listen()
expect(dstLibp2p.transportManager.getAddrs()).to.have.deep.members([...tcpAddrs, dialAddr.decapsulate('p2p')]) expect(dstLibp2p.transportManager.getAddrs()).to.have.deep.members([...tcpAddrs, dialAddr.decapsulate('p2p')])
// Tamper with the our multiaddrs for the circuit message // Tamper with the our multiaddrs for the circuit message
sinon.stub(srcLibp2p, 'multiaddrs').value([{ sinon.stub(srcLibp2p.peerInfo.multiaddrs, 'toArray').returns([{
buffer: Buffer.from('an invalid multiaddr') buffer: Buffer.from('an invalid multiaddr')
}]) }])
@ -169,7 +163,7 @@ describe('Dialing (via relay, TCP)', () => {
.to.eventually.be.rejectedWith(AggregateError) .to.eventually.be.rejectedWith(AggregateError)
.and.to.have.nested.property('._errors[0].code', Errors.ERR_HOP_REQUEST_FAILED) .and.to.have.nested.property('._errors[0].code', Errors.ERR_HOP_REQUEST_FAILED)
const dstToRelayConn = dstLibp2p.connectionManager.get(relayLibp2p.peerId) const dstToRelayConn = dstLibp2p.registrar.getConnection(relayLibp2p.peerInfo)
expect(dstToRelayConn).to.exist() expect(dstToRelayConn).to.exist()
expect(dstToRelayConn.stat.status).to.equal('open') expect(dstToRelayConn.stat.status).to.equal('open')
}) })

View File

@ -7,9 +7,9 @@ chai.use(require('chai-as-promised'))
const { expect } = chai const { expect } = chai
const sinon = require('sinon') const sinon = require('sinon')
const { EventEmitter } = require('events')
const delay = require('delay') const delay = require('delay')
const PeerId = require('peer-id') const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const duplexPair = require('it-pair/duplex') const duplexPair = require('it-pair/duplex')
const multiaddr = require('multiaddr') const multiaddr = require('multiaddr')
const pWaitFor = require('p-wait-for') const pWaitFor = require('p-wait-for')
@ -35,7 +35,7 @@ describe('Identify', () => {
[localPeer, remotePeer] = (await Promise.all([ [localPeer, remotePeer] = (await Promise.all([
PeerId.createFromJSON(Peers[0]), PeerId.createFromJSON(Peers[0]),
PeerId.createFromJSON(Peers[1]) PeerId.createFromJSON(Peers[1])
])) ])).map(id => new PeerInfo(id))
}) })
afterEach(() => { afterEach(() => {
@ -44,39 +44,27 @@ describe('Identify', () => {
it('should be able to identify another peer', async () => { it('should be able to identify another peer', async () => {
const localIdentify = new IdentifyService({ const localIdentify = new IdentifyService({
libp2p: { peerInfo: localPeer,
peerId: localPeer, protocols,
connectionManager: new EventEmitter(), registrar: {
peerStore: { peerStore: {
addressBook: { replace: () => {}
set: () => { } }
}, }
protoBook: {
set: () => { }
}
},
multiaddrs: []
},
protocols
}) })
const remoteIdentify = new IdentifyService({ const remoteIdentify = new IdentifyService({
libp2p: { peerInfo: remotePeer,
peerId: remotePeer,
connectionManager: new EventEmitter(),
multiaddrs: []
},
protocols protocols
}) })
const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234') const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234')
const localConnectionMock = { newStream: () => {}, remotePeer } const localConnectionMock = { newStream: () => {}, remotePeer: remotePeer.id }
const remoteConnectionMock = { remoteAddr: observedAddr } const remoteConnectionMock = { remoteAddr: observedAddr }
const [local, remote] = duplexPair() const [local, remote] = duplexPair()
sinon.stub(localConnectionMock, 'newStream').returns({ stream: local, protocol: multicodecs.IDENTIFY }) sinon.stub(localConnectionMock, 'newStream').returns({ stream: local, protocol: multicodecs.IDENTIFY })
sinon.spy(localIdentify.peerStore.addressBook, 'set') sinon.spy(localIdentify.registrar.peerStore, 'replace')
sinon.spy(localIdentify.peerStore.protoBook, 'set')
// Run identify // Run identify
await Promise.all([ await Promise.all([
@ -88,41 +76,29 @@ describe('Identify', () => {
}) })
]) ])
expect(localIdentify.peerStore.addressBook.set.callCount).to.equal(1) expect(localIdentify.registrar.peerStore.replace.callCount).to.equal(1)
expect(localIdentify.peerStore.protoBook.set.callCount).to.equal(1)
// Validate the remote peer gets updated in the peer store // Validate the remote peer gets updated in the peer store
const call = localIdentify.peerStore.addressBook.set.firstCall const call = localIdentify.registrar.peerStore.replace.firstCall
expect(call.args[0].id.bytes).to.equal(remotePeer.bytes) expect(call.args[0].id.bytes).to.equal(remotePeer.id.bytes)
}) })
it('should throw if identified peer is the wrong peer', async () => { it('should throw if identified peer is the wrong peer', async () => {
const localIdentify = new IdentifyService({ const localIdentify = new IdentifyService({
libp2p: { peerInfo: localPeer,
peerId: localPeer, protocols,
connectionManager: new EventEmitter(), registrar: {
peerStore: { peerStore: {
addressBook: { replace: () => {}
set: () => { } }
}, }
protoBook: {
set: () => { }
}
},
multiaddrs: []
},
protocols
}) })
const remoteIdentify = new IdentifyService({ const remoteIdentify = new IdentifyService({
libp2p: { peerInfo: remotePeer,
peerId: remotePeer,
connectionManager: new EventEmitter(),
multiaddrs: []
},
protocols protocols
}) })
const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234') const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234')
const localConnectionMock = { newStream: () => {}, remotePeer: localPeer } const localConnectionMock = { newStream: () => {}, remotePeer: localPeer.id }
const remoteConnectionMock = { remoteAddr: observedAddr } const remoteConnectionMock = { remoteAddr: observedAddr }
const [local, remote] = duplexPair() const [local, remote] = duplexPair()
@ -130,7 +106,7 @@ describe('Identify', () => {
// Run identify // Run identify
const identifyPromise = Promise.all([ const identifyPromise = Promise.all([
localIdentify.identify(localConnectionMock, localPeer), localIdentify.identify(localConnectionMock, localPeer.id),
remoteIdentify.handleMessage({ remoteIdentify.handleMessage({
connection: remoteConnectionMock, connection: remoteConnectionMock,
stream: remote, stream: remote,
@ -145,16 +121,9 @@ describe('Identify', () => {
describe('push', () => { describe('push', () => {
it('should be able to push identify updates to another peer', async () => { it('should be able to push identify updates to another peer', async () => {
const listeningAddr = multiaddr('/ip4/127.0.0.1/tcp/1234')
const connectionManager = new EventEmitter()
connectionManager.getConnection = () => {}
const localIdentify = new IdentifyService({ const localIdentify = new IdentifyService({
libp2p: { peerInfo: localPeer,
peerId: localPeer, registrar: { getConnection: () => {} },
connectionManager: new EventEmitter(),
multiaddrs: [listeningAddr]
},
protocols: new Map([ protocols: new Map([
[multicodecs.IDENTIFY], [multicodecs.IDENTIFY],
[multicodecs.IDENTIFY_PUSH], [multicodecs.IDENTIFY_PUSH],
@ -162,31 +131,30 @@ describe('Identify', () => {
]) ])
}) })
const remoteIdentify = new IdentifyService({ const remoteIdentify = new IdentifyService({
libp2p: { peerInfo: remotePeer,
peerId: remotePeer, registrar: {
connectionManager,
peerStore: { peerStore: {
addressBook: { replace: () => {}
set: () => { } }
},
protoBook: {
set: () => { }
}
},
multiaddrs: []
} }
}) })
// Setup peer protocols and multiaddrs // Setup peer protocols and multiaddrs
const localProtocols = new Set([multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH, '/echo/1.0.0']) const localProtocols = new Set([multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH, '/echo/1.0.0'])
const listeningAddr = multiaddr('/ip4/127.0.0.1/tcp/1234')
sinon.stub(localPeer.multiaddrs, 'toArray').returns([listeningAddr])
sinon.stub(localPeer, 'protocols').value(localProtocols)
sinon.stub(remotePeer, 'protocols').value(new Set([multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH]))
const localConnectionMock = { newStream: () => {} } const localConnectionMock = { newStream: () => {} }
const remoteConnectionMock = { remotePeer: localPeer } const remoteConnectionMock = { remotePeer: localPeer.id }
const [local, remote] = duplexPair() const [local, remote] = duplexPair()
sinon.stub(localConnectionMock, 'newStream').returns({ stream: local, protocol: multicodecs.IDENTIFY_PUSH }) sinon.stub(localConnectionMock, 'newStream').returns({ stream: local, protocol: multicodecs.IDENTIFY_PUSH })
sinon.spy(remoteIdentify.peerStore.addressBook, 'set') sinon.spy(IdentifyService, 'updatePeerAddresses')
sinon.spy(remoteIdentify.peerStore.protoBook, 'set') sinon.spy(IdentifyService, 'updatePeerProtocols')
sinon.spy(remoteIdentify.registrar.peerStore, 'replace')
// Run identify // Run identify
await Promise.all([ await Promise.all([
@ -198,24 +166,25 @@ describe('Identify', () => {
}) })
]) ])
expect(remoteIdentify.peerStore.addressBook.set.callCount).to.equal(1) expect(IdentifyService.updatePeerAddresses.callCount).to.equal(1)
expect(remoteIdentify.peerStore.protoBook.set.callCount).to.equal(1) expect(IdentifyService.updatePeerProtocols.callCount).to.equal(1)
const [peerId, multiaddrs] = remoteIdentify.peerStore.addressBook.set.firstCall.args
expect(peerId.bytes).to.eql(localPeer.bytes) expect(remoteIdentify.registrar.peerStore.replace.callCount).to.equal(1)
expect(multiaddrs).to.eql([listeningAddr]) const [peerInfo] = remoteIdentify.registrar.peerStore.replace.firstCall.args
const [peerId2, protocols] = remoteIdentify.peerStore.protoBook.set.firstCall.args expect(peerInfo.id.bytes).to.eql(localPeer.id.bytes)
expect(peerId2.bytes).to.eql(localPeer.bytes) expect(peerInfo.multiaddrs.toArray()).to.eql([listeningAddr])
expect(protocols).to.eql(Array.from(localProtocols)) expect(peerInfo.protocols).to.eql(localProtocols)
}) })
}) })
describe('libp2p.dialer.identifyService', () => { describe('libp2p.dialer.identifyService', () => {
let peerId let peerInfo
let libp2p let libp2p
let remoteLibp2p let remoteLibp2p
before(async () => { before(async () => {
peerId = await PeerId.createFromJSON(Peers[0]) const peerId = await PeerId.createFromJSON(Peers[0])
peerInfo = new PeerInfo(peerId)
}) })
afterEach(async () => { afterEach(async () => {
@ -231,19 +200,17 @@ describe('Identify', () => {
it('should run identify automatically after connecting', async () => { it('should run identify automatically after connecting', async () => {
libp2p = new Libp2p({ libp2p = new Libp2p({
...baseOptions, ...baseOptions,
peerId peerInfo
}) })
sinon.spy(libp2p.identifyService, 'identify') sinon.spy(libp2p.identifyService, 'identify')
const peerStoreSpySet = sinon.spy(libp2p.peerStore.addressBook, 'set') const peerStoreSpy = sinon.spy(libp2p.peerStore, 'replace')
const peerStoreSpyAdd = sinon.spy(libp2p.peerStore.addressBook, 'add')
const connection = await libp2p.dialer.connectToPeer(remoteAddr) const connection = await libp2p.dialer.connectToPeer(remoteAddr)
expect(connection).to.exist() expect(connection).to.exist()
// Wait for peer store to be updated // Wait for peer store to be updated
// Dialer._createDialTarget (add), Identify (replace) await pWaitFor(() => peerStoreSpy.callCount === 1)
await pWaitFor(() => peerStoreSpySet.callCount === 1 && peerStoreSpyAdd.callCount === 1)
expect(libp2p.identifyService.identify.callCount).to.equal(1) expect(libp2p.identifyService.identify.callCount).to.equal(1)
// The connection should have no open streams // The connection should have no open streams
@ -254,11 +221,12 @@ describe('Identify', () => {
it('should push protocol updates to an already connected peer', async () => { it('should push protocol updates to an already connected peer', async () => {
libp2p = new Libp2p({ libp2p = new Libp2p({
...baseOptions, ...baseOptions,
peerId peerInfo
}) })
sinon.spy(libp2p.identifyService, 'identify') sinon.spy(libp2p.identifyService, 'identify')
sinon.spy(libp2p.identifyService, 'push') sinon.spy(libp2p.identifyService, 'push')
sinon.spy(libp2p.peerStore, 'update')
const connection = await libp2p.dialer.connectToPeer(remoteAddr) const connection = await libp2p.dialer.connectToPeer(remoteAddr)
expect(connection).to.exist() expect(connection).to.exist()

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