Compare commits

..

47 Commits

Author SHA1 Message Date
Vasco Santos
98f6fd7157 chore: add links from getting started and readme 2020-11-09 15:17:02 +01:00
Vasco Santos
62acb72ae4 docs: discoverability and connectivity 2020-11-09 15:17:02 +01:00
Vasco Santos
8456d0e051 chore: store self protocols in protobook (#760) 2020-11-09 14:11:48 +01:00
Vasco Santos
558bcf9541 chore: improve logging for auto relay active listen 2020-11-09 14:11:48 +01:00
Vasco Santos
3bd1768b04 chore: sort relay addresses to listen for public first 2020-11-09 14:11:48 +01:00
Vasco Santos
722cacd6d2 chore: lint issues fixed 2020-11-09 14:11:48 +01:00
Vasco Santos
2746b4b025 chore: apply suggestions from code review
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-11-09 14:11:48 +01:00
Vasco Santos
29e30c2199 chore: add configuration docs for auto relay and hop service 2020-11-09 14:11:48 +01:00
Vasco Santos
3d2181f6fa chore: address review 2020-11-09 14:11:48 +01:00
Vasco Santos
e6b0134299 feat: auto relay network query for new relays 2020-11-09 14:11:48 +01:00
Vasco Santos
2530b834a1 chore: lint issue fixed 0.30 2020-11-09 14:11:48 +01:00
Vasco Santos
05e6472cce chore: address review 2020-11-09 14:11:48 +01:00
Vasco Santos
abba305bd6 chore: add identify test for multiaddr change 2020-11-09 14:11:48 +01:00
Vasco Santos
87d20ac46d chore: create signed peer record on new listen addresses in transport manager 2020-11-09 14:11:48 +01:00
Vasco Santos
ee8ee5b49b chore: use listening events to create self peer record on updates 2020-11-09 14:11:48 +01:00
Vasco Santos
971655ff27 chore: _isStarted is false when stop starts 2020-11-09 14:11:48 +01:00
Vasco Santos
8d75093dcb chore: auto relay multiaddr update push 2020-11-09 14:11:48 +01:00
Vasco Santos
25488853ef feat: auto relay (#723)
* feat: auto relay

* fix: leverage protoBook events to ask relay peers if they support hop

* chore: refactor disconnect

* chore: do not listen on a relayed conn

* chore: tweaks

* chore: improve _listenOnAvailableHopRelays logic

* chore: default value of 1 to maxListeners on auto-relay
2020-11-09 14:11:48 +01:00
Vasco Santos
fef54b2b2c chore: release version v0.29.3 2020-11-04 14:05:08 +01:00
Vasco Santos
8f29a667a1 chore: update contributors 2020-11-04 14:05:08 +01:00
Vasco Santos
093c0ea13f feat: resolve multiaddrs before dial (#782) 2020-11-04 13:54:50 +01:00
Jacob Heun
61c36f9e09 chore: release version v0.29.2 2020-10-23 15:40:54 +02:00
Jacob Heun
f82da56901 chore: update contributors 2020-10-23 15:40:53 +02:00
Jacob Heun
06f26e586f fix: cleanup open streams on conn close (#791) 2020-10-23 15:34:59 +02:00
Jacob Heun
8879634363 chore: release version v0.29.1 2020-10-22 14:33:29 +02:00
Jacob Heun
4a80afce8f chore: update contributors 2020-10-22 14:33:28 +02:00
Jacob Heun
f75ae341bb test: lock ci on node 14 2020-10-22 14:29:52 +02:00
Jacob Heun
f2d010a3ab chore: update mplex 2020-10-22 14:29:52 +02:00
Jacob Heun
e04224a1e2 fix: catch error in upgrader close call 2020-10-22 14:29:52 +02:00
Jacob Heun
4c6be91588 fix: ensure streams are closed on connection close 2020-10-22 14:29:52 +02:00
Cindy Wu
5f50054d94 docs: fix typo in transports example readme (#788) 2020-10-22 11:02:00 +02:00
Ethan Lam
d7d8439e71 docs: update transport example (#770) 2020-10-15 17:28:01 +02:00
acolytec3
4c7a89b710 doc(pubsub): add topicValidators links in API.md table of contents 2020-10-12 12:57:33 +02:00
Vasco Santos
4eabe07bde chore: update node badge 2020-10-12 12:48:48 +02:00
Vasco Santos
2fd3b0a0e5 chore: examples not using secio (#747)
* chore: examples not using secio

* chore(docs): remove unused dep

* chore(docs): remove reference of secio in setup

* chore(docs): replace circuit secio reference with noise

Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-10-07 16:16:36 +02:00
Vasco Santos
ec6f7d1cfd chore: lint issue fixed (#775) 2020-10-07 15:39:24 +02:00
Vasco Santos
a1053bdc54 chore: remove outdated events from libp2p js docs (#766) 2020-10-07 14:51:51 +02:00
Vasco Santos
0d48fc4f5a test: use ed25519 keys in tests (#669)
* chore: use ed25519 keys in tests

* fix: persisted keybook recheck keybook content for delete

* chore: only store if key not inline

* chore: update peer id

* chore: identify wait for closed streams
2020-10-07 14:50:01 +02:00
Vasco Santos
60d437f595 fix: flakey identify test firefox (#774) 2020-10-06 15:37:01 +02:00
Vasco Santos
96df4b7dc4 chore: update aegir and jsdocs for eslint changes (#773) 2020-10-06 14:59:43 +02:00
Vasco Santos
bb59b518f1 chore: complement 0.29 migration for pubsub subscribe (#755)
* chore: complement 0.29 migration for pubsub subscribe

* chore: update doc/migrations/v0.28-v0.29.md

Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-09-16 15:46:05 +02:00
Vasco Santos
fb4b2734d3 chore: update delegate deps (#753) 2020-09-15 12:47:16 +02:00
Vasco Santos
0087218194 chore: update getting started connect event (#752) 2020-09-14 13:23:28 +02:00
Vasco Santos
58b793d700 chore: add libp2p examples repo to release checklist (#746) 2020-09-08 14:47:25 +02:00
Vasco Santos
63ba2f8fa3 chore: update docs after secio deprecation announcement (#745) 2020-09-08 14:07:06 +02:00
Vasco Santos
bd26bde876 chore: remove libp2p-pubsub from package table (#743) 2020-08-31 15:19:43 +02:00
Jacob Heun
3c2a45a9d2 docs: update examples for 0.29 (#742)
* docs: update examples for 0.29

* fix: update examples/libp2p-in-the-browser/package.json

Co-authored-by: Vasco Santos <vasco.santos@moxy.studio>

Co-authored-by: Vasco Santos <vasco.santos@moxy.studio>
2020-08-27 18:13:38 +02:00
102 changed files with 2824 additions and 852 deletions

View File

@@ -7,7 +7,7 @@ stages:
node_js:
- 'lts/*'
- 'stable'
- '14'
os:
- linux

View File

@@ -1,3 +1,35 @@
<a name="0.29.3"></a>
## [0.29.3](https://github.com/libp2p/js-libp2p/compare/v0.29.2...v0.29.3) (2020-11-04)
### Features
* resolve multiaddrs before dial ([#782](https://github.com/libp2p/js-libp2p/issues/782)) ([093c0ea](https://github.com/libp2p/js-libp2p/commit/093c0ea))
<a name="0.29.2"></a>
## [0.29.2](https://github.com/libp2p/js-libp2p/compare/v0.29.1...v0.29.2) (2020-10-23)
### Bug Fixes
* cleanup open streams on conn close ([#791](https://github.com/libp2p/js-libp2p/issues/791)) ([06f26e5](https://github.com/libp2p/js-libp2p/commit/06f26e5))
<a name="0.29.1"></a>
## [0.29.1](https://github.com/libp2p/js-libp2p/compare/v0.29.0...v0.29.1) (2020-10-22)
### Bug Fixes
* catch error in upgrader close call ([e04224a](https://github.com/libp2p/js-libp2p/commit/e04224a))
* ensure streams are closed on connection close ([4c6be91](https://github.com/libp2p/js-libp2p/commit/4c6be91))
* flakey identify test firefox ([#774](https://github.com/libp2p/js-libp2p/issues/774)) ([60d437f](https://github.com/libp2p/js-libp2p/commit/60d437f))
<a name="0.29.0"></a>
# [0.29.0](https://github.com/libp2p/js-libp2p/compare/v0.28.10...v0.29.0) (2020-08-27)

View File

@@ -24,7 +24,7 @@
<a href="https://github.com/feross/standard"><img src="https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square"></a>
<a href="https://github.com/RichardLitt/standard-readme"><img src="https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square" /></a>
<a href=""><img src="https://img.shields.io/badge/npm-%3E%3D6.0.0-orange.svg?style=flat-square" /></a>
<a href=""><img src="https://img.shields.io/badge/Node.js-%3E%3D10.0.0-orange.svg?style=flat-square" /></a>
<a href=""><img src="https://img.shields.io/badge/Node.js-%3E%3D12.0.0-orange.svg?style=flat-square" /></a>
<br>
</p>
@@ -35,7 +35,7 @@ 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.
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 [GETTING_STARTED.md](./doc/GETTING_STARTED.md) guide, [Discoverability and Connectivity Readme](./DISCOVERABILITY_AND_CONNECTIVITY.md) and [examples folder](/examples).
**Want to update libp2p in your project?** Check our [migrations folder](./doc/migrations).
@@ -168,7 +168,6 @@ List of packages currently in existence for libp2p
| **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) |
| **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-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-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) |
| **extensions** |

View File

@@ -26,6 +26,7 @@
- Documentation
- [ ] Ensure that README.md is up to date
- [ ] Ensure that all the examples run
- [ ] Ensure [libp2p/js-libp2p-examples](https://github.com/libp2p/js-libp2p-examples) is updated
- [ ] Ensure that [libp2p/docs](https://github.com/libp2p/docs) is updated
- Communication
- [ ] Create the release issue

View File

@@ -2,7 +2,7 @@
* [Static Functions](#static-functions)
* [`create`](#create)
* [Instance Methods](#instance-methods)
* [Instance Methods](#libp2p-instance-methods)
* [`start`](#start)
* [`stop`](#stop)
* [`dial`](#dial)
@@ -37,6 +37,7 @@
* [`peerStore.protoBook.add`](#peerstoreprotobookadd)
* [`peerStore.protoBook.delete`](#peerstoreprotobookdelete)
* [`peerStore.protoBook.get`](#peerstoreprotobookget)
* [`peerStore.protoBook.remove`](#peerstoreprotobookremove)
* [`peerStore.protoBook.set`](#peerstoreprotobookset)
* [`peerStore.delete`](#peerstoredelete)
* [`peerStore.get`](#peerstoreget)
@@ -48,6 +49,8 @@
* [`pubsub.unsubscribe`](#pubsubunsubscribe)
* [`pubsub.on`](#pubsubon)
* [`pubsub.removeListener`](#pubsubremovelistener)
* [`pubsub.topicValidators.set`](#pubsubtopicvalidatorsset)
* [`pubsub.topicValidators.delete`](#pubsubtopicvalidatorsdelete)
* [`connectionManager.get`](#connectionmanagerget)
* [`connectionManager.setPeerValue`](#connectionmanagersetpeervalue)
* [`connectionManager.size`](#connectionmanagersize)
@@ -841,32 +844,6 @@ Consider using `addressBook.add()` if you're not sure this is what you want to d
peerStore.addressBook.add(peerId, multiaddr)
```
### peerStore.protoBook.add
Add known `protocols` of a given peer.
`peerStore.protoBook.add(peerId, protocols)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| peerId | [`PeerId`][peer-id] | peerId to set |
| protocols | `Array<string>` | protocols to add |
#### Returns
| Type | Description |
|------|-------------|
| `ProtoBook` | Returns the Proto Book component |
#### Example
```js
peerStore.protoBook.add(peerId, protocols)
```
### peerStore.keyBook.delete
Delete the provided peer from the book.
@@ -1089,6 +1066,31 @@ Set known metadata of a given `peerId`.
peerStore.metadataBook.set(peerId, 'location', uint8ArrayFromString('Berlin'))
```
### peerStore.protoBook.add
Add known `protocols` of a given peer.
`peerStore.protoBook.add(peerId, protocols)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| peerId | [`PeerId`][peer-id] | peerId to set |
| protocols | `Array<string>` | protocols to add |
#### Returns
| Type | Description |
|------|-------------|
| `ProtoBook` | Returns the Proto Book component |
#### Example
```js
peerStore.protoBook.add(peerId, protocols)
```
### peerStore.protoBook.delete
Delete the provided peer from the book.
@@ -1145,6 +1147,31 @@ peerStore.protoBook.get(peerId)
// [ '/proto/1.0.0', '/proto/1.1.0' ]
```
### peerStore.protoBook.remove
Remove given `protocols` of a given peer.
`peerStore.protoBook.remove(peerId, protocols)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| peerId | [`PeerId`][peer-id] | peerId to set |
| protocols | `Array<string>` | protocols to remove |
#### Returns
| Type | Description |
|------|-------------|
| `ProtoBook` | Returns the Proto Book component |
#### Example
```js
peerStore.protoBook.remove(peerId, protocols)
```
### peerStore.protoBook.set
Set known `protocols` of a given peer.

View File

@@ -20,6 +20,7 @@
- [Customizing DHT](#customizing-dht)
- [Setup with Content and Peer Routing](#setup-with-content-and-peer-routing)
- [Setup with Relay](#setup-with-relay)
- [Setup with Auto Relay](#setup-with-auto-relay)
- [Setup with Keychain](#setup-with-keychain)
- [Configuring Dialing](#configuring-dialing)
- [Configuring Connection Manager](#configuring-connection-manager)
@@ -98,7 +99,7 @@ If you want to know more about libp2p stream multiplexing, you should read the f
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) ⚠️ [DEPRECATED](https://blog.ipfs.io/2020-08-07-deprecating-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).
@@ -223,7 +224,7 @@ Besides the `modules` and `config`, libp2p allows other internal options and con
// Creating a libp2p node with:
// transport: websockets + tcp
// stream-muxing: mplex
// crypto-channel: secio
// crypto-channel: noise
// discovery: multicast-dns
// dht: kad-dht
// pubsub: gossipsub
@@ -232,7 +233,7 @@ const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const WS = require('libp2p-websockets')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const MulticastDNS = require('libp2p-mdns')
const DHT = require('libp2p-kad-dht')
const GossipSub = require('libp2p-gossipsub')
@@ -244,7 +245,7 @@ const node = await Libp2p.create({
new WS() // It can take instances too!
],
streamMuxer: [MPLEX],
connEncryption: [SECIO],
connEncryption: [NOISE],
peerDiscovery: [MulticastDNS],
dht: DHT,
pubsub: GossipSub
@@ -258,14 +259,14 @@ const node = await Libp2p.create({
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const MulticastDNS = require('libp2p-mdns')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO],
connEncryption: [NOISE],
peerDiscovery: [MulticastDNS]
},
config: {
@@ -291,7 +292,7 @@ const Libp2p = require('libp2p')
const WS = require('libp2p-websockets')
const WebRTCStar = require('libp2p-webrtc-star')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const node = await Libp2p.create({
modules: {
@@ -300,7 +301,7 @@ const node = await Libp2p.create({
WebRTCStar
],
streamMuxer: [MPLEX],
connEncryption: [SECIO],
connEncryption: [NOISE],
},
config: {
peerDiscovery: {
@@ -318,14 +319,14 @@ const node = await Libp2p.create({
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const GossipSub = require('libp2p-gossipsub')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO],
connEncryption: [NOISE],
pubsub: GossipSub
},
config: {
@@ -345,14 +346,14 @@ const node = await Libp2p.create({
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const DHT = require('libp2p-kad-dht')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO],
connEncryption: [NOISE],
dht: DHT
},
config: {
@@ -375,7 +376,7 @@ const node = await Libp2p.create({
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const DelegatedPeerRouter = require('libp2p-delegated-peer-routing')
const DelegatedContentRouter = require('libp2p-delegated-content-routing')
const PeerId = require('peer-id')
@@ -387,7 +388,7 @@ const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO],
connEncryption: [NOISE],
contentRouting: [
new DelegatedContentRouter(peerId)
],
@@ -401,6 +402,37 @@ const node = await Libp2p.create({
#### Setup with Relay
```js
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [NOISE]
},
config: {
relay: { // Circuit Relay options (this config is part of libp2p core configurations)
enabled: true, // Allows you to dial and accept relayed connections. Does not make you a relay.
hop: {
enabled: true, // Allows you to be a relay for other peers
active: true // You will attempt to dial destination peers if you are not connected to them
},
advertise: {
bootDelay: 15 * 60 * 1000, // Delay before HOP relay service is advertised on the network
enabled: true, // Allows you to disable the advertise of the Hop service
ttl: 30 * 60 * 1000 // Delay Between HOP relay service advertisements on the network
}
}
}
})
```
#### Setup with Auto Relay
```js
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
@@ -416,9 +448,9 @@ const node = await Libp2p.create({
config: {
relay: { // Circuit Relay options (this config is part of libp2p core configurations)
enabled: true, // Allows you to dial and accept relayed connections. Does not make you a relay.
hop: {
enabled: true, // Allows you to be a relay for other peers
active: true // You will attempt to dial destination peers if you are not connected to them
autoRelay: {
enabled: true, // Allows you to bind to relays with HOP enabled for improving node dialability
maxListeners: 2 // Configure maximum number of HOP relays to use
}
}
}
@@ -438,14 +470,14 @@ Libp2p allows you to setup a secure keychain to manage your keys. The keychain c
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const LevelStore = require('datastore-level')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
connEncryption: [NOISE]
},
keychain: {
pass: 'notsafepassword123456789',
@@ -465,6 +497,7 @@ Dialing in libp2p can be configured to limit the rate of dialing, and how long d
| 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. |
| resolvers | `object` | Dial [Resolvers](https://github.com/multiformats/js-multiaddr/blob/master/src/resolvers/index.js) for resolving multiaddrs |
The below configuration example shows how the dialer should be configured, with the current defaults:
@@ -472,18 +505,23 @@ The below configuration example shows how the dialer should be configured, with
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const { dnsaddrResolver } = require('multiaddr/src/resolvers')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
connEncryption: [NOISE]
},
dialer: {
maxParallelDials: 100,
maxDialsPerPeer: 4,
dialTimeout: 30e3
dialTimeout: 30e3,
resolvers: {
dnsaddr: dnsaddrResolver
}
}
```
@@ -495,13 +533,13 @@ The Connection Manager prunes Connections in libp2p whenever certain limits are
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
connEncryption: [NOISE]
},
connectionManager: {
maxConnections: Infinity,
@@ -526,7 +564,7 @@ The Transport Manager is responsible for managing the libp2p transports life cyc
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const { FaultTolerance } = require('libp2p/src/transport-manager')}
@@ -534,7 +572,7 @@ const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
connEncryption: [NOISE]
},
transportManager: {
faultTolerance: FaultTolerance.NO_FATAL
@@ -560,13 +598,13 @@ The below configuration example shows how the metrics should be configured. Asid
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
connEncryption: [NOISE]
},
metrics: {
enabled: true,
@@ -599,7 +637,7 @@ The below configuration example shows how the PeerStore should be configured. As
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const LevelStore = require('datastore-level')
@@ -607,7 +645,7 @@ const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
connEncryption: [NOISE]
},
datastore: new LevelStore('path/to/store'),
peerStore: {
@@ -625,7 +663,7 @@ Some Transports can be passed additional options when they are created. For exam
const Libp2p = require('libp2p')
const WebRTCStar = require('libp2p-webrtc-star')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const wrtc = require('wrtc')
const transportKey = WebRTCStar.prototype[Symbol.toStringTag]
@@ -633,7 +671,7 @@ const node = await Libp2p.create({
modules: {
transport: [WebRTCStar],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
connEncryption: [NOISE]
},
config: {
transport: {

View File

@@ -0,0 +1,99 @@
# Discoverability and Connectivity
While different p2p applications have different needs and requirements, they might also run in different environments and have different hardware capabilities. These characteristics will influence how other peers can be discovered, as well as how connections are established and kept open.
This document contains a set of guidelines to setup libp2p for the most common use cases, in the context of the typical environments where you can run a `js-libp2p` node.
## Table of Contents
[Background](#background)
- [Discovery](#discovery)
- [Connectivity](#connectivity)
[Browser](#browser)
- [Discovery](#discovery)
- [Connectivity](#connectivity)
- [Routing](#routing)
- [Overview](#overview)
[Node](#node)
## Background
### Discovery
Libp2p offers a variety of options to discover other peers on the network. They range from specifying well known peer addresses, to issue queries in a local network or exchanging peer addresses with other previously discovered peers.
To enable peer addresses exchange, peers need to specify their own announce addresses. Accordingly, announce addresses should be reachable from other peers to be valuable.
### Connectivity
A libp2p node cannot keep a unlimited number of connections over time due to hardware and network constraints. As a consequence, a node must keep the most important connections open at any moment. While certain connections will probably be important over longer periods of time, others might only be important for a smaller interval. Accordingly, libp2p needs to keep track of its open connections over time and verify if there are better connections to establish while keeping an healthy number open.
Well known peers are important for bootstrapping and getting to know other peers in the network. However, they will become less important over time since their main purpose is usually to bootstrap the network and not to provide other services. Moreover, as they will be reached by most of the network, they should be disconnected when they do not provide any more clear value to keep the network healthy.
Libp2p is able to automatically identify the importance of some connections over time, but the application layer should also flag important connections manually to improve the node's sensing of the network. For instance, libp2p will protect connections that are used in their listening addresses, in order to be reachable by other nodes, as well as connections with relevant peers for core protocols like gossipsub.
## Browser
Regarding enabling p2p applications, browsers currently have limitations that have impact on how libp2p should be setup.
### Discovery
Taking into account that a web browser does not offer any mDNS-like local discovery method to find peers on the same network and/or on the same web origin, a browser node will need to know other peers' addresses beforehand, so that it can bootstrap its network. These initial nodes should be used as a way to get to know other peers in the network and establish connections with them. Moreover, some of these peers can also advertise the browser peer to other nodes in the network, so that they can connect to it.
### Connectivity
Browser nodes do not have the ability to "listen" for incoming connections, nor a permanent address that can be dialed later for quick resume. However, Libp2p provides a set of possibilities to overcome these limitations. These solutions usually rely on other nodes to listen for connections on its behalf, as well as to advertise its information to other peers.
A browser node should start by establishing a connection with a known machine. As a result of this connection, the browser node will likely be interested to have its addresses announced to other peers in the network. Given that a browser cannot be dialed, the announced addresses of the node will be addresses that rely on this previously connection as the entry point of a dial request. For example, a circuit relay address from a connected peer. Shortly, browser nodes should have auto-relay enabled, so that they can bind to relay nodes that support HOP and become diable via them.
### Routing
DHTs are an essential building block of a p2p system to provide a lookup mechanism similar to a key-value hash table.
As browsers cannot handle large pools of open connections at the same time, as well as establish direct connections to each others, browser nodes cannot participate efficiently in DHTs. Once again, the best way to circumvent this limitation is to rely on more capable nodes in the network to handle DHT queries on their behalf. Browser nodes can rely on delegate nodes or use the DHT in client mode.
### Overview
The base connections to have a fully functional libp2p browser node are:
- nodes that can listen for incoming connections
- Relay nodes, Webrtc-star servers, ...
- nodes that can enable peer discovery and service discovery
- Webrtc-star servers, Rendezvous servers, DHT server Nodes, ...
- closest nodes
- nodes that can enable efficient routing
- DHT server nodes
- nodes from the pubsub topics mesh
- application protocol peers (as needed via `MulticodecTopology`)
While the first three points are important in any context, the last three points depend on the application use case and if the mentioned subsystems are needed.
TODO: Clearly define what libp2p handles and how it is handled
- Libp2p will protect connections used in their listening addresses like connections to a `webrtc-star` server or connections to a node acting as a relay through the `AutoRelay`, as well as nodes used for peer and service discovery
- Libp2p pubsub routers will protect the most important peer connections
- How to control and avoid excess
- Libp2p will protect connections to n DHT servers
- Libp2p will protect the n (configurable) closest peers on the network and refresh them over time, if needed
- Application protocol peers should be protected
- TODO: define how
## Node
In a Node.js context, there are less limitations that need to be considered regarding discoverability and connectivity compared to browser nodes.
The most common issue is when Libp2p nodes are behind NATs. While NAT is usually transparent for outgoing connections, listening for incoming connections might require some configuration. While its usually possible to manually configure routers, not everyone that wants to run a peer-to-peer application or other network service will have the ability to do so. Moreover, libp2p applications should run everywhere, not just in data centers or on machines with stable public IP addresses.
The best approach at the moment to circumvent this limitation is to rely on relay to communicate indirectly via an intermediary peer.
### Overview
The base connections to have a fully functional libp2p browser node are:
- nodes that can listen for incoming connections when behind a NAT
- Relay nodes, ...
- nodes that can enable service discovery
- Rendezvous servers, DHT server Nodes, ...
- closest nodes
- nodes that can enable efficient routing
- DHT server nodes
- nodes from the pubsub topics mesh
- application protocol peers (as needed via `MulticodecTopology`)

View File

@@ -112,13 +112,13 @@ npm install libp2p-mplex
```js
const Libp2p = require('libp2p')
const WebSockets = require('libp2p-websockets')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const MPLEX = require('libp2p-mplex')
const node = await Libp2p.create({
modules: {
transport: [WebSockets],
connEncryption: [SECIO],
connEncryption: [NOISE],
streamMuxer: [MPLEX]
}
})
@@ -139,7 +139,7 @@ Now that you have configured a [**Transport**][transport], [**Crypto**][crypto]
```js
const Libp2p = require('libp2p')
const WebSockets = require('libp2p-websockets')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const MPLEX = require('libp2p-mplex')
const node = await Libp2p.create({
@@ -148,7 +148,7 @@ const node = await Libp2p.create({
},
modules: {
transport: [WebSockets],
connEncryption: [SECIO],
connEncryption: [NOISE],
streamMuxer: [MPLEX]
}
})
@@ -197,21 +197,21 @@ We can provide specific configurations for each protocol within a `config.peerDi
```js
const Libp2p = require('libp2p')
const WebSockets = require('libp2p-websockets')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const MPLEX = require('libp2p-mplex')
const Bootstrap = require('libp2p-bootstrap')
// Known peers addresses
const bootstrapMultiaddrs = [
'/dns4/ams-1.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd',
'/dns4/lon-1.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3'
'/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'
]
const node = await Libp2p.create({
modules: {
transport: [WebSockets],
connEncryption: [SECIO],
connEncryption: [NOISE],
streamMuxer: [MPLEX],
peerDiscovery: [Bootstrap]
},
@@ -232,9 +232,9 @@ node.on('peer:discovery', (peer) => {
console.log('Discovered %s', peer.id.toB58String()) // Log discovered peer
})
node.on('peer:connect', (peer) => {
console.log('Connected to %s', peer.id.toB58String()) // Log connected peer
})
node.connectionManager.on('peer:connect', (connection) => {
console.log('Connected to %s', connection.remotePeer.toB58String()) // Log connected peer
})
// start libp2p
await node.start()
@@ -248,7 +248,9 @@ If you want to know more about libp2p peer discovery, you should read the follow
## What is next
There are a lot of other concepts within `libp2p`, that are not covered in this guide. For additional configuration options we recommend checking out the [Configuration Readme](./CONFIGURATION.md) and the [examples folder](../examples). If you have any problems getting started, or if anything isn't clear, please let us know by submitting an issue!
There are a lot of other concepts within `libp2p`, that are not covered in this guide. For additional configuration options we recommend checking out the [Configuration Readme](./CONFIGURATION.md) and the [examples folder](../examples).
For guidelines on how to enable discoverability and connectivity for your node environment and use case, you can check the [Discoverability and Connectivity Readme](./DISCOVERABILITY_AND_CONNECTIVITY.md).
If you have any problems getting started, or if anything isn't clear, please let us know by submitting an issue!
[transport]: https://github.com/libp2p/js-interfaces/tree/master/src/transport

View File

@@ -90,6 +90,36 @@ libp2p.pubsub.on(topic, handler)
libp2p.pubsub.subscribe(topic)
```
In the latest release, despite not being documented in `libp2p` the underlying pubsub routers supported subscribing to multiple topics at the same time. We removed that code complexity, since this is easily achieved in the application layer if needed.
**Before**
```js
const topics = ['a', 'b']
const handler = (msg) => {
// msg.data - pubsub data received
const data = msg.data.toString()
}
libp2p.pubsub.subscribe(topics, handler)
```
**After**
```js
const uint8ArrayToString = require('uint8arrays/to-string')
const topics = ['a', 'b']
const handler = (msg) => {
// msg.data - pubsub data received
const data = uint8ArrayToString(msg.data)
}
topics.forEach((topic) => {
libp2p.pubsub.on(topic, handler)
libp2p.pubsub.subscribe(topic)
})
```
#### Unsubscribe
Handlers should not be directly bound to the subscription anymore.

View File

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

View File

@@ -1,7 +1,6 @@
'use strict'
/* eslint-disable no-console */
const multaddr = require('multiaddr')
const PeerId = require('peer-id')
const Node = require('./libp2p-bundle.js')
const { stdinToStream, streamToConsole } = require('./stream')

View File

@@ -3,7 +3,6 @@
const pipe = require('it-pipe')
const lp = require('it-length-prefixed')
const uint8ArrayToString = require('uint8arrays/to-string')
function stdinToStream(stream) {
// Read utf-8 from stdin
@@ -29,7 +28,7 @@ function streamToConsole(stream) {
// For each chunk of data
for await (const msg of source) {
// Output the data as a utf8 string
console.log('> ' + uint8ArrayToString(msg).replace('\n', ''))
console.log('> ' + msg.toString().replace('\n', ''))
}
}
)

View File

@@ -4,7 +4,6 @@
const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const Bootstrap = require('libp2p-bootstrap')
@@ -29,7 +28,7 @@ const bootstrapers = [
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [NOISE, SECIO],
connEncryption: [NOISE],
peerDiscovery: [Bootstrap]
},
config: {

View File

@@ -4,7 +4,6 @@
const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const MulticastDNS = require('libp2p-mdns')
@@ -16,7 +15,7 @@ const createNode = async () => {
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [NOISE, SECIO],
connEncryption: [NOISE],
peerDiscovery: [MulticastDNS]
},
config: {

View File

@@ -8,7 +8,7 @@ These mechanisms save configuration and enable a node to operate without any exp
## 1. Bootstrap list of Peers when booting a node
For this demo, we will connect to IPFS default bootstrapper nodes and so, we will need to support the same set of features those nodes have, that are: TCP, mplex and SECIO. You can see the complete example at [1.js](./1.js).
For this demo, we will connect to IPFS default bootstrapper nodes and so, we will need to support the same set of features those nodes have, that are: TCP, mplex and NOISE. You can see the complete example at [1.js](./1.js).
First, we create our libp2p node.
@@ -20,7 +20,7 @@ const node = Libp2p.create({
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ NOISE, SECIO ],
connEncryption: [ NOISE ],
peerDiscovery: [ Bootstrap ]
},
config: {
@@ -62,7 +62,7 @@ const node = await Libp2p.create({
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ NOISE, SECIO ],
connEncryption: [ NOISE ],
peerDiscovery: [ Bootstrap ]
},
config: {
@@ -130,7 +130,7 @@ const createNode = () => {
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ NOISE, SECIO ],
connEncryption: [ NOISE ],
peerDiscovery: [ MulticastDNS ]
},
config: {

View File

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

View File

@@ -4,7 +4,6 @@ const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const pipe = require('it-pipe')
@@ -16,7 +15,7 @@ const createNode = async () => {
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [NOISE, SECIO]
connEncryption: [NOISE]
}
})

View File

@@ -8,31 +8,24 @@ A byproduct of having these encrypted communications modules is that we can auth
# 1. Set up encrypted communications
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 `libp2p-noise` module to complete it, go ahead and `npm install libp2p-noise`.
To add them to your libp2p configuration, all you have to do is:
```JavaScript
const Libp2p = require('libp2p')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const createNode = () => {
return Libp2p.create({
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
// Attach secio as the crypto channel to use
connEncryption: [ NOISE, SECIO ]
// Attach noise as the crypto channel to use
connEncryption: [ NOISE ]
}
})
}
```
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).
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

@@ -3,9 +3,8 @@ import Libp2p from 'libp2p'
import Websockets from 'libp2p-websockets'
import WebRTCStar from 'libp2p-webrtc-star'
import { NOISE } from 'libp2p-noise'
import Secio from 'libp2p-secio'
import Mplex from 'libp2p-mplex'
import Boostrap from 'libp2p-bootstrap'
import Bootstrap from 'libp2p-bootstrap'
document.addEventListener('DOMContentLoaded', async () => {
// Create our libp2p node
@@ -21,21 +20,22 @@ document.addEventListener('DOMContentLoaded', async () => {
},
modules: {
transport: [Websockets, WebRTCStar],
connEncryption: [NOISE, Secio],
connEncryption: [NOISE],
streamMuxer: [Mplex],
peerDiscovery: [Boostrap]
peerDiscovery: [Bootstrap]
},
config: {
peerDiscovery: {
bootstrap: {
// The `tag` property will be searched when creating the instance of your Peer Discovery service.
// The associated object, will be passed to the service when it is instantiated.
[Bootstrap.tag]: {
enabled: true,
list: [
'/dns4/ams-1.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd',
'/dns4/lon-1.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3',
'/dns4/sfo-3.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM',
'/dns4/sgp-1.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu',
'/dns4/nyc-1.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm',
'/dns4/nyc-2.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64'
'/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmZa1sAxajnQjVM8WjWXoMbmPd7NsWhfKsPkErzpm9wGkp',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt'
]
}
}

View File

@@ -16,12 +16,11 @@
"dependencies": {
"@babel/preset-env": "^7.8.3",
"libp2p": "../../",
"libp2p-bootstrap": "^0.11",
"libp2p-mplex": "^0.9.3",
"libp2p-noise": "^1.1.0",
"libp2p-secio": "^0.12.2",
"libp2p-webrtc-star": "^0.18.0",
"libp2p-websockets": "^0.13.2"
"libp2p-bootstrap": "^0.12.1",
"libp2p-mplex": "^0.10.0",
"libp2p-noise": "^2.0.0",
"libp2p-webrtc-star": "^0.20.0",
"libp2p-websockets": "^0.14.0"
},
"devDependencies": {
"@babel/cli": "^7.8.3",

View File

@@ -5,7 +5,6 @@ const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const KadDHT = require('libp2p-kad-dht')
const delay = require('delay')
@@ -18,7 +17,7 @@ const createNode = async () => {
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [NOISE, SECIO],
connEncryption: [NOISE],
dht: KadDHT
},
config: {

View File

@@ -4,7 +4,6 @@
const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const CID = require('cids')
const KadDHT = require('libp2p-kad-dht')
@@ -20,7 +19,7 @@ const createNode = async () => {
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [NOISE, SECIO],
connEncryption: [NOISE],
dht: KadDHT
},
config: {

View File

@@ -23,7 +23,7 @@ const node = await Libp2p.create({
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ NOISE, SECIO ],
connEncryption: [ NOISE ],
// we add the DHT module that will enable Peer and Content Routing
dht: KadDHT
},

View File

@@ -2,11 +2,7 @@
This example shows how to set up a private network of libp2p nodes.
## Setup
Install dependencies:
```
npm install
```
1. Install the modules in the libp2p root directory, `npm install`.
## Run
Running the example will cause two nodes with the same swarm key to be started and exchange basic information.

View File

@@ -3,7 +3,6 @@
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const Protector = require('libp2p/src/pnet')
@@ -24,7 +23,7 @@ const privateLibp2pNode = async (swarmKey) => {
streamMuxer: [MPLEX], // We're only using mplex muxing
// Let's make sure to use identifying crypto in our pnet since the protector doesn't
// care about node identity, and only the presence of private keys
connEncryption: [NOISE, SECIO],
connEncryption: [NOISE],
// Leave peer discovery empty, we don't want to find peers. We could omit the property, but it's
// being left in for explicit readability.
// We should explicitly dial pnet peers, or use a custom discovery service for finding nodes in our pnet

View File

@@ -1,20 +0,0 @@
{
"name": "pnet-ipfs-example",
"version": "1.0.0",
"description": "An example of private networking with IPFS",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"libp2p": "../..",
"libp2p-mplex": "^0.9.3",
"libp2p-noise": "^1.1.0",
"libp2p-secio": "^0.12.1",
"libp2p-tcp": "^0.14.2"
}
}

View File

@@ -4,7 +4,6 @@ const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const pipe = require('it-pipe')
@@ -16,7 +15,7 @@ const createNode = async () => {
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [NOISE, SECIO]
connEncryption: [NOISE]
}
})

View File

@@ -4,7 +4,6 @@ const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const pipe = require('it-pipe')
@@ -16,7 +15,7 @@ const createNode = async () => {
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [NOISE, SECIO]
connEncryption: [NOISE]
}
})

View File

@@ -5,7 +5,6 @@ const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const pipe = require('it-pipe')
@@ -17,7 +16,7 @@ const createNode = async () => {
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [NOISE, SECIO]
connEncryption: [NOISE]
}
})

View File

@@ -5,7 +5,6 @@ const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const Gossipsub = require('libp2p-gossipsub')
const uint8ArrayFromString = require('uint8arrays/from-string')
const uint8ArrayToString = require('uint8arrays/to-string')
@@ -18,7 +17,7 @@ const createNode = async () => {
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [NOISE, SECIO],
connEncryption: [NOISE],
pubsub: Gossipsub
}
})

View File

@@ -27,7 +27,7 @@ const node = await Libp2p.create({
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ NOISE, SECIO ],
connEncryption: [ NOISE ],
// we add the Pubsub module we want
pubsub: Gossipsub
}

View File

@@ -5,7 +5,6 @@ const Libp2p = require('../../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const Gossipsub = require('libp2p-gossipsub')
const uint8ArrayFromString = require('uint8arrays/from-string')
const uint8ArrayToString = require('uint8arrays/to-string')
@@ -18,7 +17,7 @@ const createNode = async () => {
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [NOISE, SECIO],
connEncryption: [NOISE],
pubsub: Gossipsub
}
})

View File

@@ -17,7 +17,7 @@ const node = await Libp2p.create({
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ NOISE, SECIO ],
connEncryption: [ NOISE ],
pubsub: Gossipsub
}
})

View File

@@ -4,18 +4,17 @@
const Libp2p = require('../..')
const TCP = require('libp2p-tcp')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const createNode = async () => {
const node = await Libp2p.create({
addresses: {
// To signall the addresses we want to be available, we use
// To signal 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: {
transport: [TCP],
connEncryption: [NOISE, SECIO]
connEncryption: [NOISE]
}
})

View File

@@ -4,7 +4,6 @@
const Libp2p = require('../..')
const TCP = require('libp2p-tcp')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const MPLEX = require('libp2p-mplex')
const pipe = require('it-pipe')
@@ -13,13 +12,13 @@ const concat = require('it-concat')
const createNode = async () => {
const node = await Libp2p.create({
addresses: {
// To signall the addresses we want to be available, we use
// To signal 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: {
transport: [TCP],
connEncryption: [NOISE, SECIO],
connEncryption: [NOISE],
streamMuxer: [MPLEX]
}
})

View File

@@ -5,7 +5,6 @@ const Libp2p = require('../..')
const TCP = require('libp2p-tcp')
const WebSockets = require('libp2p-websockets')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const MPLEX = require('libp2p-mplex')
const pipe = require('it-pipe')
@@ -17,11 +16,11 @@ const createNode = async (transports, addresses = []) => {
const node = await Libp2p.create({
addresses: {
listen: addresses.map((a) => a)
listen: addresses
},
modules: {
transport: transports,
connEncryption: [NOISE, SECIO],
connEncryption: [NOISE],
streamMuxer: [MPLEX]
}
})

View File

@@ -13,10 +13,10 @@ When using libp2p, you need properly configure it, that is, pick your set of mod
You will need 4 dependencies total, so go ahead and install all of them with:
```bash
> npm install libp2p libp2p-tcp libp2p-secio peer-info
> npm install libp2p libp2p-tcp libp2p-noise
```
Then, on your favorite text editor create a file with the `.js` extension. I've called mine `1.js`.
Then, in your favorite text editor create a file with the `.js` extension. I've called mine `1.js`.
First thing is to create our own libp2p node! Insert:
@@ -26,18 +26,17 @@ First thing is to create our own libp2p node! Insert:
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const createNode = async () => {
const node = await Libp2p.create({
addresses: {
// To signall the addresses we want to be available, we use
// To signal 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: {
transport: [ TCP ],
connEncryption: [ NOISE, SECIO ]
connEncryption: [ NOISE ]
}
})
@@ -78,20 +77,41 @@ That `QmW2cKTakTYqbQkUzBTEGXgWYFj1YEPeUndE1YWs6CBzDQ` is the PeerId that was cre
Now that we have our `createNode` function, let's create two nodes and make them dial to each other! You can find the complete solution at [2.js](./2.js).
For this step, we will need one more dependency.
For this step, we will need some more dependencies.
```bash
> npm install it-pipe it-buffer
> npm install it-pipe it-concat libp2p-mplex
```
And we also need to import the module on our .js file:
And we also need to import the modules on our .js file:
```js
const pipe = require('it-pipe')
const { toBuffer } = require('it-buffer')
const concat = require('it-concat')
const MPLEX = require('libp2p-mplex')
```
We are going to reuse the `createNode` function from step 1, but this time to make things simpler, we will create another function to print the addrs to avoid duplicating code.
We are going to reuse the `createNode` function from step 1, but this time add a stream multiplexer from `libp2p-mplex`.
```js
const createNode = async () => {
const node = await Libp2p.create({
addresses: {
// To signal 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: {
transport: [TCP],
connEncryption: [NOISE],
streamMuxer: [MPLEX] // <--- Add this line
}
})
await node.start()
return node
}
```
We will also make things simpler by creating another function to print the multiaddresses to avoid duplicating code.
```JavaScript
function printAddrs (node, number) {
@@ -100,7 +120,7 @@ function printAddrs (node, number) {
}
```
Then,
Then add,
```js
;(async () => {
@@ -112,18 +132,15 @@ Then,
printAddrs(node1, '1')
printAddrs(node2, '2')
node2.handle('/print', ({ stream }) => {
pipe(
node2.handle('/print', async ({ stream }) => {
const result = await pipe(
stream,
async function (source) {
for await (const msg of source) {
console.log(msg.toString())
}
}
concat
)
console.log(result.toString())
})
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
const { stream } = await node1.dialProtocol(node2.peerId, '/print')
await pipe(
@@ -132,8 +149,9 @@ node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
)
})();
```
For more information refer to the [docs](https://github.com/libp2p/js-libp2p/blob/master/doc/API.md).
The result should be look like:
The result should look like:
```bash
> node 2.js
@@ -148,33 +166,33 @@ Hello p2p world!
## 3. Using multiple transports
Next, we want to be available in multiple transports to increase our chances of having common transports in the network. A simple scenario, a node running in the browser only has access to HTTP, WebSockets and WebRTC since the browser doesn't let you open any other kind of transport, for this node to dial to some other node, that other node needs to share a common transport.
Next, we want nodes to have multiple transports available to increase their chances of having a common transport in the network to communicate over. A simple scenario is a node running in the browser only having access to HTTP, WebSockets and WebRTC since the browser doesn't let you open any other kind of transport. For this node to dial to some other node, that other node needs to share a common transport.
What we are going to do in this step is to create 3 nodes, one with TCP, another with TCP+WebSockets and another one with just WebSockets. The full solution can be found on [3.js](./3.js).
What we are going to do in this step is to create 3 nodes: one with TCP, another with TCP+WebSockets and another one with just WebSockets. The full solution can be found on [3.js](./3.js).
In this example, we will need to also install `libp2p-websockets`, go ahead and install:
In this example, we will need to also install `libp2p-websockets`:
```bash
> npm install libp2p-websockets
```
We want to create 3 nodes, one with TCP, one with TCP+WebSockets and one with just WebSockets. We need to update our `createNode` function to contemplate WebSockets as well. Moreover, let's upgrade our function to enable us to pick the addrs in which a node will start a listener:
We want to create 3 nodes: one with TCP, one with TCP+WebSockets and one with just WebSockets. We need to update our `createNode` function to accept WebSocket connections as well. Moreover, let's upgrade our function to enable us to pick the addresses over which a node will start a listener:
```JavaScript
// ...
const createNode = async (transports, multiaddrs = []) => {
if (!Array.isArray(multiaddrs)) {
multiaddrs = [multiaddrs]
const createNode = async (transports, addresses = []) => {
if (!Array.isArray(addresses)) {
addresses = [addresses]
}
const node = await Libp2p.create({
addresses: {
listen: multiaddrs.map((a) => multiaddr(a))
listen: addresses
},
modules: {
transport: transports,
connEncryption: [SECIO],
connEncryption: [NOISE],
streamMuxer: [MPLEX]
}
})
@@ -232,7 +250,7 @@ try {
}
```
`print` is a function created using the code from 2.js, but factored into its own function to save lines, here it is:
`print` is a function that prints each piece of data from a stream onto a new line but factored into its own function to save lines:
```JavaScript
function print ({ stream }) {
@@ -247,7 +265,7 @@ function print ({ stream }) {
}
```
If everything was set correctly, you now should see the following after you run the script:
If everything was set correctly, you now should see something similar to the following after running the script:
```Bash
> node 3.js
@@ -266,13 +284,13 @@ node 3 failed to dial to node 1 with:
Error: No transport available for address /ip4/127.0.0.1/tcp/51482
```
As expected, we created 3 nodes, node 1 with TCP, node 2 with TCP+WebSockets and node 3 with just WebSockets. node 1 -> node 2 and node 2 -> node 3 managed to dial correctly because they shared a common transport, however, node 3 -> node 1 failed because they didn't share any.
As expected, we created 3 nodes: node 1 with TCP, node 2 with TCP+WebSockets and node 3 with just WebSockets. node 1 -> node 2 and node 2 -> node 3 managed to dial correctly because they shared a common transport; however, node 3 -> node 1 failed because they didn't share any.
## 4. How to create a new libp2p transport
Today there are already several transports available and plenty to come, you can find these at [interface-transport implementations](https://github.com/libp2p/js-interfaces/tree/master/src/transport#modules-that-implement-the-interface) list.
Today there are already several transports available and plenty to come. You can find these at [interface-transport implementations](https://github.com/libp2p/js-interfaces/tree/master/src/transport#modules-that-implement-the-interface) list.
Adding more transports is done through the same way as you added TCP and WebSockets. Some transports might offer extra functionalities, but as far as libp2p is concerned, if it follows the interface defined at the [spec](https://github.com/libp2p/js-interfaces/tree/master/src/transport#api) it will be able to use it.
Adding more transports is done through the same way as you added TCP and WebSockets. Some transports might offer extra functionalities, but as far as libp2p is concerned, if it follows the interface defined in the [spec](https://github.com/libp2p/js-interfaces/tree/master/src/transport#api) it will be able to use it.
If you decide to implement a transport yourself, please consider adding to the list so that others can use it as well.

View File

@@ -51,7 +51,6 @@
["libp2p/js-peer-id", "peer-id"],
"pubsub",
["libp2p/js-libp2p-pubsub", "libp2p-pubsub"],
["libp2p/js-libp2p-floodsub", "libp2p-floodsub"],
["ChainSafe/js-libp2p-gossipsub", "libp2p-gossipsub"],

View File

@@ -1,6 +1,6 @@
{
"name": "libp2p",
"version": "0.29.0",
"version": "0.29.3",
"description": "JavaScript implementation of libp2p, a modular peer to peer network stack",
"leadMaintainer": "Jacob Heun <jacobheun@gmail.com>",
"main": "src/index.js",
@@ -45,6 +45,7 @@
"aggregate-error": "^3.0.1",
"any-signal": "^1.1.0",
"bignumber.js": "^9.0.0",
"cids": "^1.0.0",
"class-is": "^1.1.0",
"debug": "^4.1.1",
"err-code": "^2.0.0",
@@ -60,19 +61,20 @@
"it-protocol-buffers": "^0.2.0",
"libp2p-crypto": "^0.18.0",
"libp2p-interfaces": "^0.5.1",
"libp2p-utils": "^0.2.0",
"libp2p-utils": "^0.2.1",
"mafmt": "^8.0.0",
"merge-options": "^2.0.0",
"moving-average": "^1.0.0",
"multiaddr": "^8.0.0",
"multiaddr": "^8.1.0",
"multicodec": "^2.0.0",
"multihashing-async": "^2.0.1",
"multistream-select": "^1.0.0",
"mutable-proxy": "^1.0.0",
"node-forge": "^0.9.1",
"p-any": "^3.0.0",
"p-fifo": "^1.0.0",
"p-settle": "^4.0.1",
"peer-id": "^0.14.0",
"peer-id": "^0.14.2",
"protons": "^2.0.0",
"retimer": "^2.0.0",
"sanitize-filename": "^1.6.3",
@@ -84,28 +86,27 @@
"devDependencies": {
"@nodeutils/defaults-deep": "^1.1.0",
"abortable-iterator": "^3.0.0",
"aegir": "^26.0.0",
"aegir": "^27.0.0",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"chai-bytes": "^0.1.2",
"chai-string": "^1.5.0",
"cids": "^1.0.0",
"delay": "^4.3.0",
"dirty-chai": "^2.0.1",
"interop-libp2p": "^0.3.0",
"ipfs-http-client": "^46.0.0",
"ipfs-http-client": "^47.0.1",
"it-concat": "^1.0.0",
"it-pair": "^1.0.0",
"it-pushable": "^1.4.0",
"libp2p": ".",
"libp2p-bootstrap": "^0.12.0",
"libp2p-delegated-content-routing": "^0.6.0",
"libp2p-delegated-peer-routing": "^0.6.0",
"libp2p-delegated-content-routing": "^0.7.0",
"libp2p-delegated-peer-routing": "^0.7.0",
"libp2p-floodsub": "^0.23.0",
"libp2p-gossipsub": "^0.6.0",
"libp2p-kad-dht": "^0.20.0",
"libp2p-mdns": "^0.15.0",
"libp2p-mplex": "^0.10.0",
"libp2p-mplex": "^0.10.1",
"libp2p-noise": "^2.0.0",
"libp2p-secio": "^0.13.1",
"libp2p-tcp": "^0.15.1",
@@ -132,39 +133,41 @@
"Friedel Ziegelmayer <dignifiedquire@gmail.com>",
"Maciej Krüger <mkg20001@gmail.com>",
"Hugo Dias <mail@hugodias.me>",
"Volker Mische <volker.mische@gmail.com>",
"dirkmc <dirkmdev@gmail.com>",
"Volker Mische <volker.mische@gmail.com>",
"Richard Littauer <richard.littauer@gmail.com>",
"ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>",
"Andrew Nesbitt <andrewnez@gmail.com>",
"Elven <mon.samuel@qq.com>",
"Andrew Nesbitt <andrewnez@gmail.com>",
"Giovanni T. Parra <fiatjaf@gmail.com>",
"Ryan Bell <ryan@piing.net>",
"Thomas Eizinger <thomas@eizinger.io>",
"ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>",
"Didrik Nordström <didrik@betamos.se>",
"Francis Gulotta <wizard@roborooter.com>",
"Florian-Merle <florian.david.merle@gmail.com>",
"Henrique Dias <hacdias@gmail.com>",
"Fei Liu <liu.feiwood@gmail.com>",
"Irakli Gozalishvili <rfobic@gmail.com>",
"Ethan Lam <elmemphis2000@gmail.com>",
"Joel Gustafson <joelg@mit.edu>",
"Julien Bouquillon <contact@revolunet.com>",
"Kevin Kwok <antimatter15@gmail.com>",
"Felipe Martins <felipebrasil93@gmail.com>",
"Nuno Nogueira <nunofmn@gmail.com>",
"Fei Liu <liu.feiwood@gmail.com>",
"RasmusErik Voel Jensen <github@solsort.com>",
"Dmitriy Ryajov <dryajov@gmail.com>",
"RasmusErik Voel Jensen <github@solsort.com>",
"Diogo Silva <fsdiogo@gmail.com>",
"robertkiel <robert.kiel@validitylabs.org>",
"Soeren <nikorpoulsen@gmail.com>",
"Sönke Hahn <soenkehahn@gmail.com>",
"Tiago Alves <alvesjtiago@gmail.com>",
"Diogo Silva <fsdiogo@gmail.com>",
"Daijiro Wachi <daijiro.wachi@gmail.com>",
"Yusef Napora <yusef@napora.org>",
"Zane Starr <zcstarr@gmail.com>",
"Daijiro Wachi <daijiro.wachi@gmail.com>",
"Cindy Wu <ciindy.wu@gmail.com>",
"Chris Bratlien <chrisbratlien@gmail.com>",
"ebinks <elizabethjbinks@gmail.com>",
"Bernd Strehl <bernd.strehl@gmail.com>",
"isan_rivkin <isanrivkin@gmail.com>",
"Henrique Dias <hacdias@gmail.com>",
"robertkiel <robert.kiel@validitylabs.org>",
"Irakli Gozalishvili <rfobic@gmail.com>"
"Florian-Merle <florian.david.merle@gmail.com>",
"Francis Gulotta <wizard@roborooter.com>",
"Felipe Martins <felipebrasil93@gmail.com>"
]
}

View File

@@ -15,11 +15,11 @@ const multiaddr = require('multiaddr')
*/
class AddressManager {
/**
* @constructor
* @class
* @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.
* @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)
@@ -29,7 +29,8 @@ class AddressManager {
/**
* Get peer listen multiaddrs.
* @return {Array<Multiaddr>}
*
* @returns {Array<Multiaddr>}
*/
getListenAddrs () {
return Array.from(this.listen).map((a) => multiaddr(a))
@@ -37,7 +38,8 @@ class AddressManager {
/**
* Get peer announcing multiaddrs.
* @return {Array<Multiaddr>}
*
* @returns {Array<Multiaddr>}
*/
getAnnounceAddrs () {
return Array.from(this.announce).map((a) => multiaddr(a))
@@ -45,7 +47,8 @@ class AddressManager {
/**
* Get peer noAnnouncing multiaddrs.
* @return {Array<Multiaddr>}
*
* @returns {Array<Multiaddr>}
*/
getNoAnnounceAddrs () {
return Array.from(this.noAnnounce).map((a) => multiaddr(a))

View File

@@ -41,7 +41,7 @@ const multiaddr = require('multiaddr')
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const relayAddr = ...
@@ -52,7 +52,7 @@ const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
connEncryption: [NOISE]
},
config: {
relay: { // Circuit Relay options (this config is part of libp2p core configurations)

292
src/circuit/auto-relay.js Normal file
View File

@@ -0,0 +1,292 @@
'use strict'
const debug = require('debug')
const log = debug('libp2p:auto-relay')
log.error = debug('libp2p:auto-relay:error')
const isPrivate = require('libp2p-utils/src/multiaddr/is-private')
const uint8ArrayFromString = require('uint8arrays/from-string')
const uint8ArrayToString = require('uint8arrays/to-string')
const multiaddr = require('multiaddr')
const PeerId = require('peer-id')
const { relay: multicodec } = require('./multicodec')
const { canHop } = require('./circuit/hop')
const { namespaceToCid } = require('./utils')
const {
CIRCUIT_PROTO_CODE,
HOP_METADATA_KEY,
HOP_METADATA_VALUE,
RELAY_RENDEZVOUS_NS
} = require('./constants')
class AutoRelay {
/**
* Creates an instance of AutoRelay.
*
* @class
* @param {object} props
* @param {Libp2p} props.libp2p
* @param {number} [props.maxListeners = 1] - maximum number of relays to listen.
*/
constructor ({ libp2p, maxListeners = 1 }) {
this._libp2p = libp2p
this._peerId = libp2p.peerId
this._peerStore = libp2p.peerStore
this._connectionManager = libp2p.connectionManager
this._transportManager = libp2p.transportManager
this.maxListeners = maxListeners
/**
* @type {Set<string>}
*/
this._listenRelays = new Set()
this._onProtocolChange = this._onProtocolChange.bind(this)
this._onPeerDisconnected = this._onPeerDisconnected.bind(this)
this._peerStore.on('change:protocols', this._onProtocolChange)
this._connectionManager.on('peer:disconnect', this._onPeerDisconnected)
}
/**
* Check if a peer supports the relay protocol.
* If the protocol is not supported, check if it was supported before and remove it as a listen relay.
* If the protocol is supported, check if the peer supports **HOP** and add it as a listener if
* inside the threshold.
*
* @param {Object} props
* @param {PeerId} props.peerId
* @param {Array<string>} props.protocols
* @returns {Promise<void>}
*/
async _onProtocolChange ({ peerId, protocols }) {
const id = peerId.toB58String()
// Check if it has the protocol
const hasProtocol = protocols.find(protocol => protocol === multicodec)
// If no protocol, check if we were keeping the peer before as a listenRelay
if (!hasProtocol && this._listenRelays.has(id)) {
this._removeListenRelay(id)
return
} else if (!hasProtocol || this._listenRelays.has(id)) {
return
}
// If protocol, check if can hop, store info in the metadataBook and listen on it
try {
const connection = this._connectionManager.get(peerId)
// Do not hop on a relayed connection
if (connection.remoteAddr.protoCodes().includes(CIRCUIT_PROTO_CODE)) {
log(`relayed connection to ${id} will not be used to hop on`)
return
}
const supportsHop = await canHop({ connection })
if (supportsHop) {
this._peerStore.metadataBook.set(peerId, HOP_METADATA_KEY, uint8ArrayFromString(HOP_METADATA_VALUE))
await this._addListenRelay(connection, id)
}
} catch (err) {
log.error(err)
}
}
/**
* Peer disconnects.
*
* @param {Connection} connection - connection to the peer
* @returns {void}
*/
_onPeerDisconnected (connection) {
const peerId = connection.remotePeer
const id = peerId.toB58String()
// Not listening on this relay
if (!this._listenRelays.has(id)) {
return
}
this._removeListenRelay(id)
}
/**
* Attempt to listen on the given relay connection.
*
* @private
* @param {Connection} connection - connection to the peer
* @param {string} id - peer identifier string
* @returns {Promise<void>}
*/
async _addListenRelay (connection, id) {
// Check if already listening on enough relays
if (this._listenRelays.size >= this.maxListeners) {
return
}
// Create relay listen addr
let listenAddr, remoteMultiaddr, remoteAddrs
try {
// Get peer known addresses and sort them per public addresses first
remoteAddrs = this._peerStore.addressBook.get(connection.remotePeer)
// TODO: This sort should be customizable in the config (dialer addr sort)
remoteAddrs.sort(multiaddrsCompareFunction)
remoteMultiaddr = remoteAddrs.find(a => a.isCertified).multiaddr // Get first announced address certified
// TODO: HOP Relays should avoid advertising private addresses!
} catch (_) {
log.error(`${id} does not have announced certified multiaddrs`)
// Attempt first if existing
if (!remoteAddrs || !remoteAddrs.length) {
return
}
remoteMultiaddr = remoteAddrs[0].multiaddr
}
if (!remoteMultiaddr.protoNames().includes('p2p')) {
listenAddr = `${remoteMultiaddr.toString()}/p2p/${connection.remotePeer.toB58String()}/p2p-circuit`
} else {
listenAddr = `${remoteMultiaddr.toString()}/p2p-circuit`
}
// Attempt to listen on relay
this._listenRelays.add(id)
try {
await this._transportManager.listen([multiaddr(listenAddr)])
// Announce multiaddrs will update on listen success by TransportManager event being triggered
} catch (err) {
log.error(err)
this._listenRelays.delete(id)
}
}
/**
* Remove listen relay.
*
* @private
* @param {string} id - peer identifier string.
* @returns {void}
*/
_removeListenRelay (id) {
if (this._listenRelays.delete(id)) {
// TODO: this should be responsibility of the connMgr
this._listenOnAvailableHopRelays([id])
}
}
/**
* Try to listen on available hop relay connections.
* The following order will happen while we do not have enough relays.
* 1. Check the metadata store for known relays, try to listen on the ones we are already connected.
* 2. Dial and try to listen on the peers we know that support hop but are not connected.
* 3. Search the network.
*
* @param {Array<string>} [peersToIgnore]
* @returns {Promise<void>}
*/
async _listenOnAvailableHopRelays (peersToIgnore = []) {
// TODO: The peer redial issue on disconnect should be handled by connection gating
// Check if already listening on enough relays
if (this._listenRelays.size >= this.maxListeners) {
return
}
const knownHopsToDial = []
// Check if we have known hop peers to use and attempt to listen on the already connected
for (const [id, metadataMap] of this._peerStore.metadataBook.data.entries()) {
// Continue to next if listening on this or peer to ignore
if (this._listenRelays.has(id) || peersToIgnore.includes(id)) {
continue
}
const supportsHop = metadataMap.get(HOP_METADATA_KEY)
// Continue to next if it does not support Hop
if (!supportsHop || uint8ArrayToString(supportsHop) !== HOP_METADATA_VALUE) {
continue
}
const peerId = PeerId.createFromCID(id)
const connection = this._connectionManager.get(peerId)
// If not connected, store for possible later use.
if (!connection) {
knownHopsToDial.push(peerId)
continue
}
await this._addListenRelay(connection, id)
// Check if already listening on enough relays
if (this._listenRelays.size >= this.maxListeners) {
return
}
}
// Try to listen on known peers that are not connected
for (const peerId of knownHopsToDial) {
const connection = await this._libp2p.dial(peerId)
await this._addListenRelay(connection, peerId.toB58String())
// Check if already listening on enough relays
if (this._listenRelays.size >= this.maxListeners) {
return
}
}
// Try to find relays to hop on the network
try {
const cid = await namespaceToCid(RELAY_RENDEZVOUS_NS)
for await (const provider of this._libp2p.contentRouting.findProviders(cid)) {
if (!provider.multiaddrs.length) {
continue
}
const peerId = provider.id
this._peerStore.addressBook.add(peerId, provider.multiaddrs)
const connection = await this._libp2p.dial(peerId)
await this._addListenRelay(connection, peerId.toB58String())
// Check if already listening on enough relays
if (this._listenRelays.size >= this.maxListeners) {
return
}
}
} catch (err) {
log.error(err)
}
}
}
/**
* Compare function for array.sort().
* This sort aims to move the private adresses to the end of the array.
*
* @param {Address} a
* @param {Address} b
* @returns {number}
*/
function multiaddrsCompareFunction (a, b) {
const isAPrivate = isPrivate(a.multiaddr)
const isBPrivate = isPrivate(b.multiaddr)
if (isAPrivate && !isBPrivate) {
return 1
} else if (!isAPrivate && isBPrivate) {
return -1
}
return 0
}
module.exports = AutoRelay

View File

@@ -90,9 +90,8 @@ module.exports.handleHop = async function handleHop ({
* peer. A new, virtual, connection will be created between the two via the relay.
*
* @param {object} options
* @param {Connection} options.connection Connection to the relay
* @param {Connection} options.connection - Connection to the relay
* @param {*} options.request
* @param {Circuit} options.circuit
* @returns {Promise<Connection>}
*/
module.exports.hop = async function hop ({
@@ -117,8 +116,41 @@ module.exports.hop = async function hop ({
throw errCode(new Error(`HOP request failed with code ${response.code}`), Errors.ERR_HOP_REQUEST_FAILED)
}
/**
* Performs a CAN_HOP request to a relay peer, in order to understand its capabilities.
*
* @param {object} options
* @param {Connection} options.connection - Connection to the relay
* @returns {Promise<boolean>}
*/
module.exports.canHop = async function canHop ({
connection
}) {
// Create a new stream to the relay
const { stream } = await connection.newStream([multicodec.relay])
// Send the HOP request
const streamHandler = new StreamHandler({ stream })
streamHandler.write({
type: CircuitPB.Type.CAN_HOP
})
const response = await streamHandler.read()
await streamHandler.close()
if (response.code !== CircuitPB.Status.SUCCESS) {
return false
}
return true
}
/**
* Creates an unencoded CAN_HOP response based on the Circuits configuration
*
* @param {Object} options
* @param {Connection} options.connection
* @param {StreamHandler} options.streamHandler
* @param {Circuit} options.circuit
* @private
*/
module.exports.handleCanHop = function handleCanHop ({

View File

@@ -15,7 +15,7 @@ log.error = debug('libp2p:circuit:stop:error')
* @private
* @param {*} options
* @param {Connection} options.connection
* @param {*} options.request The CircuitRelay protobuf request (unencoded)
* @param {*} options.request - The CircuitRelay protobuf request (unencoded)
* @param {StreamHandler} options.streamHandler
* @returns {Promise<*>} Resolves a duplex iterable
*/
@@ -42,10 +42,11 @@ module.exports.handleStop = function handleStop ({
/**
* Creates a STOP request
*
* @private
* @param {*} options
* @param {Connection} options.connection
* @param {*} options.request The CircuitRelay protobuf request (unencoded)
* @param {*} options.request - The CircuitRelay protobuf request (unencoded)
* @returns {Promise<*>} Resolves a duplex iterable
*/
module.exports.stop = async function stop ({

View File

@@ -14,7 +14,7 @@ class StreamHandler {
*
* @param {object} options
* @param {*} options.stream - A duplex iterable
* @param {Number} options.maxLength - max bytes length of message
* @param {number} options.maxLength - max bytes length of message
*/
constructor ({ stream, maxLength = 4096 }) {
this.stream = stream
@@ -25,6 +25,7 @@ class StreamHandler {
/**
* Read and decode message
*
* @async
* @returns {void}
*/
@@ -44,7 +45,7 @@ class StreamHandler {
/**
* Encode and write array of buffers
*
* @param {*} msg An unencoded CircuitRelay protobuf message
* @param {*} msg - An unencoded CircuitRelay protobuf message
*/
write (msg) {
log('write message type %s', msg.type)
@@ -54,7 +55,7 @@ class StreamHandler {
/**
* Return the handshake rest stream and invalidate handler
*
* @return {*} A duplex iterable
* @returns {*} A duplex iterable
*/
rest () {
this.shake.rest()

View File

@@ -19,7 +19,7 @@ function writeResponse (streamHandler, status) {
/**
* Validate incomming HOP/STOP message
*
* @param {*} msg A CircuitRelay unencoded protobuf message
* @param {*} msg - A CircuitRelay unencoded protobuf message
* @param {StreamHandler} streamHandler
*/
function validateAddrs (msg, streamHandler) {

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

@@ -0,0 +1,12 @@
'use strict'
const minute = 60 * 1000
module.exports = {
ADVERTISE_BOOT_DELAY: 15 * minute, // Delay before HOP relay service is advertised on the network
ADVERTISE_TTL: 30 * minute, // Delay Between HOP relay service advertisements on the network
CIRCUIT_PROTO_CODE: 290, // Multicodec code
HOP_METADATA_KEY: 'hop_relay', // PeerStore metadaBook key for HOP relay service
HOP_METADATA_VALUE: 'true', // PeerStore metadaBook value for HOP relay service
RELAY_RENDEZVOUS_NS: '/libp2p/relay' // Relay HOP relay service namespace for discovery
}

View File

@@ -1,187 +1,91 @@
'use strict'
const mafmt = require('mafmt')
const multiaddr = require('multiaddr')
const PeerId = require('peer-id')
const withIs = require('class-is')
const { CircuitRelay: CircuitPB } = require('./protocol')
const debug = require('debug')
const log = debug('libp2p:circuit')
log.error = debug('libp2p:circuit:error')
const toConnection = require('libp2p-utils/src/stream-to-ma-conn')
const log = debug('libp2p:relay')
log.error = debug('libp2p:relay:error')
const { relay: multicodec } = require('./multicodec')
const createListener = require('./listener')
const { handleCanHop, handleHop, hop } = require('./circuit/hop')
const { handleStop } = require('./circuit/stop')
const StreamHandler = require('./circuit/stream-handler')
const AutoRelay = require('./auto-relay')
const { namespaceToCid } = require('./utils')
const {
ADVERTISE_BOOT_DELAY,
ADVERTISE_TTL,
RELAY_RENDEZVOUS_NS
} = require('./constants')
class Circuit {
class Relay {
/**
* Creates an instance of Circuit.
* Creates an instance of Relay.
*
* @constructor
* @param {object} options
* @param {Libp2p} options.libp2p
* @param {Upgrader} options.upgrader
* @class
* @param {Libp2p} libp2p
*/
constructor ({ libp2p, upgrader }) {
this._dialer = libp2p.dialer
this._registrar = libp2p.registrar
this._connectionManager = libp2p.connectionManager
this._upgrader = upgrader
this._options = libp2p._config.relay
constructor (libp2p) {
this._libp2p = libp2p
this.peerId = libp2p.peerId
this._registrar.handle(multicodec, this._onProtocol.bind(this))
}
async _onProtocol ({ connection, stream, protocol }) {
const streamHandler = new StreamHandler({ stream })
const request = await streamHandler.read()
const circuit = this
let virtualConnection
switch (request.type) {
case CircuitPB.Type.CAN_HOP: {
log('received CAN_HOP request from %s', connection.remotePeer.toB58String())
await handleCanHop({ circuit, connection, streamHandler })
break
}
case CircuitPB.Type.HOP: {
log('received HOP request from %s', connection.remotePeer.toB58String())
virtualConnection = await handleHop({
connection,
request,
streamHandler,
circuit
})
break
}
case CircuitPB.Type.STOP: {
log('received STOP request from %s', connection.remotePeer.toB58String())
virtualConnection = await handleStop({
connection,
request,
streamHandler,
circuit
})
break
}
default: {
log('Request of type %s not supported', request.type)
}
this._options = {
advertise: {
bootDelay: ADVERTISE_BOOT_DELAY,
enabled: true,
ttl: ADVERTISE_TTL,
...libp2p._config.relay.advertise
},
...libp2p._config.relay
}
if (virtualConnection) {
const remoteAddr = multiaddr(request.dstPeer.addrs[0])
const localAddr = multiaddr(request.srcPeer.addrs[0])
const maConn = toConnection({
stream: virtualConnection,
remoteAddr,
localAddr
})
const type = CircuitPB.Type === CircuitPB.Type.HOP ? 'relay' : 'inbound'
log('new %s connection %s', type, maConn.remoteAddr)
// Create autoRelay if enabled
this._autoRelay = this._options.autoRelay.enabled && new AutoRelay({ libp2p, ...this._options.autoRelay })
}
const conn = await this._upgrader.upgradeInbound(maConn)
log('%s connection %s upgraded', type, maConn.remoteAddr)
this.handler && this.handler(conn)
/**
* Start Relay service.
*
* @returns {void}
*/
start () {
// Advertise service if HOP enabled
const canHop = this._options.hop.enabled
if (canHop && this._options.advertise.enabled) {
this._timeout = setTimeout(() => {
this._advertiseService()
}, this._options.advertise.bootDelay)
}
}
/**
* Dial a peer over a relay
* Stop Relay service.
*
* @param {multiaddr} ma - the multiaddr of the peer to dial
* @param {Object} options - dial options
* @param {AbortSignal} [options.signal] - An optional abort signal
* @returns {Connection} - the connection
* @returns {void}
*/
async dial (ma, options) {
// Check the multiaddr to see if it contains a relay and a destination peer
const addrs = ma.toString().split('/p2p-circuit')
const relayAddr = multiaddr(addrs[0])
const destinationAddr = multiaddr(addrs[addrs.length - 1])
const relayPeer = PeerId.createFromCID(relayAddr.getPeerId())
const destinationPeer = PeerId.createFromCID(destinationAddr.getPeerId())
let disconnectOnFailure = false
let relayConnection = this._connectionManager.get(relayPeer)
if (!relayConnection) {
relayConnection = await this._dialer.connectToPeer(relayAddr, options)
disconnectOnFailure = true
}
stop () {
clearTimeout(this._timeout)
}
/**
* Advertise hop relay service in the network.
*
* @returns {Promise<void>}
*/
async _advertiseService () {
try {
const virtualConnection = await hop({
connection: relayConnection,
circuit: this,
request: {
type: CircuitPB.Type.HOP,
srcPeer: {
id: this.peerId.toBytes(),
addrs: this._libp2p.multiaddrs.map(addr => addr.bytes)
},
dstPeer: {
id: destinationPeer.toBytes(),
addrs: [multiaddr(destinationAddr).bytes]
}
}
})
const localAddr = relayAddr.encapsulate(`/p2p-circuit/p2p/${this.peerId.toB58String()}`)
const maConn = toConnection({
stream: virtualConnection,
remoteAddr: ma,
localAddr
})
log('new outbound connection %s', maConn.remoteAddr)
return this._upgrader.upgradeOutbound(maConn)
const cid = await namespaceToCid(RELAY_RENDEZVOUS_NS)
await this._libp2p.contentRouting.provide(cid)
} catch (err) {
log.error('Circuit relay dial failed', err)
disconnectOnFailure && await relayConnection.close()
throw err
}
}
if (err.code === 'NO_ROUTERS_AVAILABLE') {
log.error('a content router, such as a DHT, must be provided in order to advertise the relay service', err)
// Stop the advertise
this.stop()
} else {
log.error(err)
}
/**
* Create a listener
*
* @param {any} options
* @param {Function} handler
* @return {listener}
*/
createListener (options, handler) {
if (typeof options === 'function') {
handler = options
options = {}
return
}
// Called on successful HOP and STOP requests
this.handler = handler
return createListener(this, options)
}
/**
* Filter check for all Multiaddrs that this transport can dial on
*
* @param {Array<Multiaddr>} multiaddrs
* @returns {Array<Multiaddr>}
*/
filter (multiaddrs) {
multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs]
return multiaddrs.filter((ma) => {
return mafmt.Circuit.matches(ma)
})
// Restart timeout
this._timeout = setTimeout(() => {
this._advertiseService()
}, this._options.advertise.ttl)
}
}
/**
* @type {Circuit}
*/
module.exports = withIs(Circuit, { className: 'Circuit', symbolName: '@libp2p/js-libp2p-circuit/circuit' })
module.exports = Relay

View File

@@ -8,23 +8,33 @@ const log = debug('libp2p:circuit:listener')
log.err = debug('libp2p:circuit:error:listener')
/**
* @param {*} circuit
* @param {Libp2p} libp2p
* @returns {Listener} a transport listener
*/
module.exports = (circuit) => {
module.exports = (libp2p) => {
const listener = new EventEmitter()
const listeningAddrs = new Map()
// Remove listeningAddrs when a peer disconnects
libp2p.connectionManager.on('peer:disconnect', (connection) => {
const deleted = listeningAddrs.delete(connection.remotePeer.toB58String())
if (deleted) {
// Announce listen addresses change
listener.emit('close')
}
})
/**
* Add swarm handler and listen for incoming connections
*
* @param {Multiaddr} addr
* @return {void}
* @returns {void}
*/
listener.listen = async (addr) => {
const addrString = String(addr).split('/p2p-circuit').find(a => a !== '')
const relayConn = await circuit._dialer.connectToPeer(multiaddr(addrString))
const relayConn = await libp2p.dial(multiaddr(addrString))
const relayedAddr = relayConn.remoteAddr.encapsulate('/p2p-circuit')
listeningAddrs.set(relayConn.remotePeer.toB58String(), relayedAddr)
@@ -34,7 +44,7 @@ module.exports = (circuit) => {
/**
* TODO: Remove the peers from our topology
*
* @return {void}
* @returns {void}
*/
listener.close = () => {}
@@ -44,15 +54,15 @@ module.exports = (circuit) => {
* NOTE: This method will grab the peers multiaddrs and expand them such that:
*
* a) If it's an existing /p2p-circuit address for a specific relay i.e.
* `/ip4/0.0.0.0/tcp/0/ipfs/QmRelay/p2p-circuit` this method will expand the
* address to `/ip4/0.0.0.0/tcp/0/ipfs/QmRelay/p2p-circuit/ipfs/QmPeer` where
* `QmPeer` is this peers id
* `/ip4/0.0.0.0/tcp/0/ipfs/QmRelay/p2p-circuit` this method will expand the
* address to `/ip4/0.0.0.0/tcp/0/ipfs/QmRelay/p2p-circuit/ipfs/QmPeer` where
* `QmPeer` is this peers id
* b) If it's not a /p2p-circuit address, it will encapsulate the address as a /p2p-circuit
* addr, such when dialing over a relay with this address, it will create the circuit using
* the encapsulated transport address. This is useful when for example, a peer should only
* be dialed over TCP rather than any other transport
* addr, such when dialing over a relay with this address, it will create the circuit using
* the encapsulated transport address. This is useful when for example, a peer should only
* be dialed over TCP rather than any other transport
*
* @return {Multiaddr[]}
* @returns {Multiaddr[]}
*/
listener.getAddrs = () => {
const addrs = []

194
src/circuit/transport.js Normal file
View File

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

17
src/circuit/utils.js Normal file
View File

@@ -0,0 +1,17 @@
'use strict'
const CID = require('cids')
const multihashing = require('multihashing-async')
/**
* Convert a namespace string into a cid.
*
* @param {string} namespace
* @returns {Promise<CID>}
*/
module.exports.namespaceToCid = async (namespace) => {
const bytes = new TextEncoder('utf8').encode(namespace)
const hash = await multihashing(bytes, 'sha2-256')
return new CID(hash)
}

View File

@@ -1,7 +1,10 @@
'use strict'
const mergeOptions = require('merge-options')
const { dnsaddrResolver } = require('multiaddr/src/resolvers')
const Constants = require('./constants')
const RelayConstants = require('./circuit/constants')
const { FaultTolerance } = require('./transport-manager')
@@ -20,7 +23,10 @@ const DefaultConfig = {
dialer: {
maxParallelDials: Constants.MAX_PARALLEL_DIALS,
maxDialsPerPeer: Constants.MAX_PER_PEER_DIALS,
dialTimeout: Constants.DIAL_TIMEOUT
dialTimeout: Constants.DIAL_TIMEOUT,
resolvers: {
dnsaddr: dnsaddrResolver
}
},
metrics: {
enabled: false
@@ -51,9 +57,18 @@ const DefaultConfig = {
},
relay: {
enabled: true,
advertise: {
bootDelay: RelayConstants.ADVERTISE_BOOT_DELAY,
enabled: false,
ttl: RelayConstants.ADVERTISE_TTL
},
hop: {
enabled: false,
active: false
},
autoRelay: {
enabled: false,
maxListeners: 2
}
},
transport: {}

View File

@@ -32,25 +32,26 @@ const defaultOptions = {
/**
* 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
* @class
* @param {Libp2p} libp2p
* @param {object} options
* @param {Number} options.maxConnections The maximum number of connections allowed. Default=Infinity
* @param {Number} options.minConnections The minimum number of connections to avoid pruning. Default=0
* @param {Number} options.maxData The max data (in and out), per average interval to allow. Default=Infinity
* @param {Number} options.maxSentData The max outgoing data, per average interval to allow. Default=Infinity
* @param {Number} options.maxReceivedData The max incoming data, per average interval to allow.. Default=Infinity
* @param {Number} options.maxEventLoopDelay The upper limit the event loop can take to run. Default=Infinity
* @param {Number} options.pollInterval How often, in milliseconds, metrics and latency should be checked. Default=2000
* @param {Number} options.movingAverageInterval How often, in milliseconds, to compute averages. Default=60000
* @param {Number} options.defaultPeerValue The value of the peer. Default=1
* @param {boolean} options.autoDial Should preemptively guarantee connections are above the low watermark. Default=true
* @param {Number} options.autoDialInterval How often, in milliseconds, it should preemptively guarantee connections are above the low watermark. Default=10000
* @param {number} options.maxConnections - The maximum number of connections allowed. Default=Infinity
* @param {number} options.minConnections - The minimum number of connections to avoid pruning. Default=0
* @param {number} options.maxData - The max data (in and out), per average interval to allow. Default=Infinity
* @param {number} options.maxSentData - The max outgoing data, per average interval to allow. Default=Infinity
* @param {number} options.maxReceivedData - The max incoming data, per average interval to allow.. Default=Infinity
* @param {number} options.maxEventLoopDelay - The upper limit the event loop can take to run. Default=Infinity
* @param {number} options.pollInterval - How often, in milliseconds, metrics and latency should be checked. Default=2000
* @param {number} options.movingAverageInterval - How often, in milliseconds, to compute averages. Default=60000
* @param {number} options.defaultPeerValue - The value of the peer. Default=1
* @param {boolean} options.autoDial - Should preemptively guarantee connections are above the low watermark. Default=true
* @param {number} options.autoDialInterval - How often, in milliseconds, it should preemptively guarantee connections are above the low watermark. Default=10000
*/
constructor (libp2p, options) {
super()
@@ -69,12 +70,14 @@ class ConnectionManager extends EventEmitter {
/**
* Map of peer identifiers to their peer value for pruning connections.
*
* @type {Map<string, number>}
*/
this._peerValues = new Map()
/**
* Map of connections per peer
*
* @type {Map<string, Array<conn>>}
*/
this.connections = new Map()
@@ -119,6 +122,7 @@ class ConnectionManager extends EventEmitter {
/**
* Stops the Connection Manager
*
* @async
*/
async stop () {
@@ -133,6 +137,7 @@ class ConnectionManager extends EventEmitter {
/**
* Cleans up the connections
*
* @async
*/
async _close () {
@@ -151,8 +156,9 @@ class ConnectionManager extends EventEmitter {
/**
* Sets the value of the given peer. Peers with lower values
* will be disconnected first.
*
* @param {PeerId} peerId
* @param {number} value A number between 0 and 1
* @param {number} value - A number between 0 and 1
*/
setPeerValue (peerId, value) {
if (value < 0 || value > 1) {
@@ -167,6 +173,7 @@ class ConnectionManager extends EventEmitter {
/**
* Checks the libp2p metrics to determine if any values have exceeded
* the configured maximums.
*
* @private
*/
_checkMetrics () {
@@ -183,6 +190,7 @@ class ConnectionManager extends EventEmitter {
/**
* Tracks the incoming connection and check the connection limit
*
* @param {Connection} connection
*/
onConnect (connection) {
@@ -208,6 +216,7 @@ class ConnectionManager extends EventEmitter {
/**
* Removes the connection from tracking
*
* @param {Connection} connection
*/
onDisconnect (connection) {
@@ -226,6 +235,7 @@ class ConnectionManager extends EventEmitter {
/**
* Get a connection with a peer.
*
* @param {PeerId} peerId
* @returns {Connection}
*/
@@ -239,6 +249,7 @@ class ConnectionManager extends EventEmitter {
/**
* Get all open connections with a peer.
*
* @param {PeerId} peerId
* @returns {Array<Connection>}
*/
@@ -259,8 +270,9 @@ class ConnectionManager extends EventEmitter {
/**
* If the event loop is slow, maybe close a connection
*
* @private
* @param {*} summary The LatencyMonitor summary
* @param {*} summary - The LatencyMonitor summary
*/
_onLatencyMeasure (summary) {
this._checkMaxLimit('maxEventLoopDelay', summary.avgMs)
@@ -268,9 +280,10 @@ class ConnectionManager extends EventEmitter {
/**
* If the `value` of `name` has exceeded its limit, maybe close a connection
*
* @private
* @param {string} name The name of the field to check limits for
* @param {number} value The current value of the field
* @param {string} name - The name of the field to check limits for
* @param {number} value - The current value of the field
*/
_checkMaxLimit (name, value) {
const limit = this._options[name]
@@ -285,6 +298,7 @@ class ConnectionManager extends EventEmitter {
* Proactively tries to connect to known peers stored in the PeerStore.
* It will keep the number of connections below the upper limit and sort
* the peers to connect based on wether we know their keys and protocols.
*
* @async
* @private
*/
@@ -330,6 +344,7 @@ class ConnectionManager extends EventEmitter {
/**
* If we have more connections than our maximum, close a connection
* to the lowest valued peer.
*
* @private
*/
_maybeDisconnectOne () {

View File

@@ -12,11 +12,11 @@ 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
* @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
*/
/**
@@ -37,11 +37,12 @@ const debug = require('debug')('latency-monitor:LatencyMonitor')
*/
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.
*/
* @param {object} [options]
* @param {number} [options.latencyCheckIntervalMs=500] - How often to add a latency check event (ms)
* @param {number} [options.dataEmitIntervalMs=5000] - How often to summarize latency check events. null or 0 disables event firing
* @param {Function} [options.asyncTestFn] - What cb-style async function to use
* @param {number} [options.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
@@ -106,9 +107,10 @@ class LatencyMonitor extends EventEmitter {
}
/**
* Start internal timers
* @private
*/
* Start internal timers
*
* @private
*/
_startTimers () {
// Timer already started, ignore this
if (this._checkLatencyID) {
@@ -124,9 +126,10 @@ class LatencyMonitor extends EventEmitter {
}
/**
* Stop internal timers
* @private
*/
* Stop internal timers
*
* @private
*/
_stopTimers () {
if (this._checkLatencyID) {
clearTimeout(this._checkLatencyID)
@@ -139,9 +142,10 @@ class LatencyMonitor extends EventEmitter {
}
/**
* Emit summary only if there were events. It might not have any events if it was forced via a page hidden/show
* @private
*/
* 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) {
@@ -150,10 +154,11 @@ class LatencyMonitor extends EventEmitter {
}
/**
* 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}
*/
* 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
@@ -173,11 +178,11 @@ class LatencyMonitor extends EventEmitter {
}
/**
* 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
*/
* 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

View File

@@ -34,8 +34,8 @@ const debug = require('debug')('latency-monitor:VisibilityChangeEmitter')
*/
module.exports = class VisibilityChangeEmitter extends EventEmitter {
/**
* Creates a VisibilityChangeEmitter
*/
* Creates a VisibilityChangeEmitter
*/
constructor () {
super()
if (typeof document === 'undefined') {
@@ -47,13 +47,14 @@ module.exports = class VisibilityChangeEmitter extends EventEmitter {
}
/**
* 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
*/
* 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
@@ -75,10 +76,11 @@ module.exports = class VisibilityChangeEmitter extends EventEmitter {
}
/**
* 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
*/
* 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') {
@@ -90,10 +92,11 @@ module.exports = class VisibilityChangeEmitter extends EventEmitter {
}
/**
* 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)
*/
* 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
@@ -103,12 +106,12 @@ module.exports = class VisibilityChangeEmitter extends EventEmitter {
}
/**
* 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
*/
* 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')

View File

@@ -20,9 +20,9 @@ module.exports = (node) => {
* Iterates over all content routers in series to find providers of the given key.
* Once a content router succeeds, iteration will stop.
*
* @param {CID} key The CID key of the content to find
* @param {CID} key - The CID key of the content to find
* @param {object} [options]
* @param {number} [options.timeout] How long the query should run
* @param {number} [options.timeout] - How long the query should run
* @param {number} [options.maxNumProviders] - maximum number of providers to find
* @returns {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>}
*/
@@ -51,7 +51,7 @@ module.exports = (node) => {
* Iterates over all content routers in parallel to notify it is
* a provider of the given key.
*
* @param {CID} key The CID key of the content to find
* @param {CID} key - The CID key of the content to find
* @returns {Promise<void>}
*/
async provide (key) { // eslint-disable-line require-await
@@ -64,6 +64,7 @@ module.exports = (node) => {
/**
* Store the given key/value pair in the DHT.
*
* @param {Uint8Array} key
* @param {Uint8Array} value
* @param {Object} [options] - put options
@@ -81,6 +82,7 @@ module.exports = (node) => {
/**
* Get the value to the given key.
* Times out after 1 minute by default.
*
* @param {Uint8Array} key
* @param {Object} [options] - get options
* @param {number} [options.timeout] - optional timeout (default: 60000)
@@ -96,6 +98,7 @@ module.exports = (node) => {
/**
* Get the `n` values to the given key without sorting.
*
* @param {Uint8Array} key
* @param {number} nVals
* @param {Object} [options] - get options

View File

@@ -16,6 +16,7 @@ class DialRequest {
* from `dialer.getTokens`. Once a DialRequest is created, it can be
* started using `DialRequest.run(options)`. Once a single dial has succeeded,
* all other dials in the request will be cancelled.
*
* @param {object} options
* @param {Multiaddr[]} options.addrs
* @param {function(Multiaddr):Promise<Connection>} options.dialAction
@@ -34,7 +35,7 @@ class DialRequest {
/**
* @async
* @param {object} options
* @param {AbortSignal} options.signal An AbortController signal
* @param {AbortSignal} options.signal - An AbortController signal
* @returns {Connection}
*/
async run (options) {

View File

@@ -20,19 +20,22 @@ const {
class Dialer {
/**
* @constructor
* @class
* @param {object} options
* @param {TransportManager} options.transportManager
* @param {Peerstore} peerStore
* @param {number} options.concurrency Number of max concurrent dials. Defaults to `MAX_PARALLEL_DIALS`
* @param {number} options.timeout How long a dial attempt is allowed to take. Defaults to `DIAL_TIMEOUT`
* @param {Peerstore} options.peerStore
* @param {number} [options.concurrency = MAX_PARALLEL_DIALS] - Number of max concurrent dials.
* @param {number} [options.perPeerLimit = MAX_PER_PEER_DIALS] - Number of max concurrent dials per peer.
* @param {number} [options.timeout = DIAL_TIMEOUT] - How long a dial attempt is allowed to take.
* @param {object} [options.resolvers = {}] - multiaddr resolvers to use when dialing
*/
constructor ({
transportManager,
peerStore,
concurrency = MAX_PARALLEL_DIALS,
timeout = DIAL_TIMEOUT,
perPeerLimit = MAX_PER_PEER_DIALS
perPeerLimit = MAX_PER_PEER_DIALS,
resolvers = {}
}) {
this.transportManager = transportManager
this.peerStore = peerStore
@@ -41,6 +44,10 @@ class Dialer {
this.perPeerLimit = perPeerLimit
this.tokens = [...new Array(concurrency)].map((_, index) => index)
this._pendingDials = new Map()
for (const [key, value] of Object.entries(resolvers)) {
multiaddr.resolvers.set(key, value)
}
}
/**
@@ -62,13 +69,13 @@ class Dialer {
* The dial to the first address that is successfully able to upgrade a connection
* will be used.
*
* @param {PeerId|Multiaddr|string} peer The peer to dial
* @param {PeerId|Multiaddr|string} peer - The peer to dial
* @param {object} [options]
* @param {AbortSignal} [options.signal] An AbortController signal
* @param {AbortSignal} [options.signal] - An AbortController signal
* @returns {Promise<Connection>}
*/
async connectToPeer (peer, options = {}) {
const dialTarget = this._createDialTarget(peer)
const dialTarget = await this._createDialTarget(peer)
if (!dialTarget.addrs.length) {
throw errCode(new Error('The dial request has no addresses'), codes.ERR_NO_VALID_ADDRESSES)
@@ -101,24 +108,31 @@ class Dialer {
* Creates a DialTarget. The DialTarget is used to create and track
* the DialRequest to a given peer.
* If a multiaddr is received it should be the first address attempted.
*
* @private
* @param {PeerId|Multiaddr|string} peer A PeerId or Multiaddr
* @returns {DialTarget}
* @param {PeerId|Multiaddr|string} peer - A PeerId or Multiaddr
* @returns {Promise<DialTarget>}
*/
_createDialTarget (peer) {
async _createDialTarget (peer) {
const { id, multiaddrs } = getPeer(peer)
if (multiaddrs) {
this.peerStore.addressBook.add(id, multiaddrs)
}
let addrs = this.peerStore.addressBook.getMultiaddrsForPeer(id) || []
let knownAddrs = 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)
knownAddrs = knownAddrs.filter((addr) => !peer.equals(addr))
knownAddrs.unshift(peer)
}
const addrs = []
for (const a of knownAddrs) {
const resolvedAddrs = await this._resolve(a)
resolvedAddrs.forEach(ra => addrs.push(ra))
}
return {
@@ -137,10 +151,11 @@ class Dialer {
/**
* Creates a PendingDial that wraps the underlying DialRequest
*
* @private
* @param {DialTarget} dialTarget
* @param {object} [options]
* @param {AbortSignal} [options.signal] An AbortController signal
* @param {AbortSignal} [options.signal] - An AbortController signal
* @returns {PendingDial}
*/
_createPendingDial (dialTarget, options) {
@@ -187,6 +202,52 @@ class Dialer {
log('token %d released', token)
this.tokens.push(token)
}
/**
* Resolve multiaddr recursively.
*
* @param {Multiaddr} ma
* @returns {Promise<Array<Multiaddr>>}
*/
async _resolve (ma) {
// TODO: recursive logic should live in multiaddr once dns4/dns6 support is in place
// Now only supporting resolve for dnsaddr
const resolvableProto = ma.protoNames().includes('dnsaddr')
// Multiaddr is not resolvable? End recursion!
if (!resolvableProto) {
return [ma]
}
const resolvedMultiaddrs = await this._resolveRecord(ma)
const recursiveMultiaddrs = await Promise.all(resolvedMultiaddrs.map((nm) => {
return this._resolve(nm)
}))
return recursiveMultiaddrs.flat().reduce((array, newM) => {
if (!array.find(m => m.equals(newM))) {
array.push(newM)
}
return array
}, []) // Unique addresses
}
/**
* Resolve a given multiaddr. If this fails, an empty array will be returned
*
* @param {Multiaddr} ma
* @returns {Promise<Array<Multiaddr>>}
*/
async _resolveRecord (ma) {
try {
ma = multiaddr(ma.toString()) // Use current multiaddr module
const multiaddrs = await ma.resolve()
return multiaddrs
} catch (_) {
log.error(`multiaddr ${ma} could not be resolved`)
return []
}
}
}
module.exports = Dialer

View File

@@ -9,8 +9,8 @@ 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) {

View File

@@ -32,7 +32,8 @@ const { codes } = require('../errors')
class IdentifyService {
/**
* Takes the `addr` and converts it to a Multiaddr if possible
* @param {Uint8Array|String} addr
*
* @param {Uint8Array | string} addr
* @returns {Multiaddr|null}
*/
static getCleanMultiaddr (addr) {
@@ -47,12 +48,11 @@ class IdentifyService {
}
/**
* @constructor
* @class
* @param {object} options
* @param {Libp2p} options.libp2p
* @param {Map<string, handler>} options.protocols A reference to the protocols we support
*/
constructor ({ libp2p, protocols }) {
constructor ({ libp2p }) {
/**
* @property {PeerStore}
*/
@@ -63,12 +63,6 @@ class IdentifyService {
*/
this.connectionManager = libp2p.connectionManager
this.connectionManager.on('peer:connect', (connection) => {
const peerId = connection.remotePeer
this.identify(connection, peerId).catch(log.error)
})
/**
* @property {PeerId}
*/
@@ -79,20 +73,40 @@ class IdentifyService {
*/
this._libp2p = libp2p
this._protocols = protocols
this.handleMessage = this.handleMessage.bind(this)
// When a new connection happens, trigger identify
this.connectionManager.on('peer:connect', (connection) => {
const peerId = connection.remotePeer
this.identify(connection, peerId).catch(log.error)
})
// When self multiaddrs change, trigger identify-push
this.peerStore.on('change:multiaddrs', ({ peerId }) => {
if (peerId.toString() === this.peerId.toString()) {
this.pushToPeerStore()
}
})
// When self protocols change, trigger identify-push
this.peerStore.on('change:protocols', ({ peerId }) => {
if (peerId.toString() === this.peerId.toString()) {
this.pushToPeerStore()
}
})
}
/**
* Send an Identify Push update to the list of connections
*
* @param {Array<Connection>} connections
* @returns {Promise<void>}
*/
async push (connections) {
const signedPeerRecord = await this._getSelfPeerRecord()
const signedPeerRecord = await this.peerStore.addressBook.getRawEnvelope(this.peerId)
const listenAddrs = this._libp2p.multiaddrs.map((ma) => ma.bytes)
const protocols = Array.from(this._protocols.keys())
const protocols = this.peerStore.protoBook.get(this.peerId) || []
const pushes = connections.map(async connection => {
try {
@@ -119,12 +133,18 @@ class IdentifyService {
/**
* Calls `push` for all peers in the `peerStore` that are connected
* @param {PeerStore} peerStore
*
* @returns {void}
*/
pushToPeerStore (peerStore) {
pushToPeerStore () {
// Do not try to push if libp2p node is not running
if (!this._libp2p.isStarted()) {
return
}
const connections = []
let connection
for (const peer of peerStore.peers.values()) {
for (const peer of this.peerStore.peers.values()) {
if (peer.protocols.includes(MULTICODEC_IDENTIFY_PUSH) && (connection = this.connectionManager.get(peer.id))) {
connections.push(connection)
}
@@ -209,7 +229,7 @@ class IdentifyService {
* A handler to register with Libp2p to process identify messages.
*
* @param {object} options
* @param {String} options.protocol
* @param {string} options.protocol
* @param {*} options.stream
* @param {Connection} options.connection
* @returns {Promise<void>}
@@ -228,6 +248,7 @@ class IdentifyService {
/**
* Sends the `Identify` response with the Signed Peer Record
* to the requesting peer over the given `connection`
*
* @private
* @param {object} options
* @param {*} options.stream
@@ -239,7 +260,8 @@ class IdentifyService {
publicKey = this.peerId.pubKey.bytes
}
const signedPeerRecord = await this._getSelfPeerRecord()
const signedPeerRecord = await this.peerStore.addressBook.getRawEnvelope(this.peerId)
const protocols = this.peerStore.protoBook.get(this.peerId) || []
const message = Message.encode({
protocolVersion: PROTOCOL_VERSION,
@@ -248,7 +270,7 @@ class IdentifyService {
listenAddrs: this._libp2p.multiaddrs.map((ma) => ma.bytes),
signedPeerRecord,
observedAddr: connection.remoteAddr.bytes,
protocols: Array.from(this._protocols.keys())
protocols
})
try {
@@ -265,6 +287,7 @@ class IdentifyService {
/**
* Reads the Identify Push message from the given `connection`
*
* @private
* @param {object} options
* @param {*} options.stream
@@ -308,38 +331,12 @@ class IdentifyService {
// Update the protocols
this.peerStore.protoBook.set(id, message.protocols)
}
/**
* Get self signed peer record raw envelope.
* @return {Uint8Array}
*/
async _getSelfPeerRecord () {
const selfSignedPeerRecord = this.peerStore.addressBook.getRawEnvelope(this.peerId)
// TODO: support invalidation when dynamic multiaddrs are supported
if (selfSignedPeerRecord) {
return selfSignedPeerRecord
}
try {
const peerRecord = new PeerRecord({
peerId: this.peerId,
multiaddrs: this._libp2p.multiaddrs
})
const envelope = await Envelope.seal(peerRecord, this.peerId)
this.peerStore.addressBook.consumePeerRecord(envelope)
return this.peerStore.addressBook.getRawEnvelope(this.peerId)
} catch (err) {
log.error('failed to get self peer record')
}
return null
}
}
module.exports.IdentifyService = IdentifyService
/**
* The protocols the IdentifyService supports
*
* @property multicodecs
*/
module.exports.multicodecs = {

View File

@@ -17,7 +17,8 @@ const { codes, messages } = require('./errors')
const AddressManager = require('./address-manager')
const ConnectionManager = require('./connection-manager')
const Circuit = require('./circuit')
const Circuit = require('./circuit/transport')
const Relay = require('./circuit')
const Dialer = require('./dialer')
const Keychain = require('./keychain')
const Metrics = require('./metrics')
@@ -35,8 +36,6 @@ const {
/**
* @fires Libp2p#error Emitted when an error occurs
* @fires Libp2p#peer:connect Emitted when a peer is connected to this node
* @fires Libp2p#peer:disconnect Emitted when a peer disconnects from this node
* @fires Libp2p#peer:discovery Emitted when a peer is discovered
*/
class Libp2p extends EventEmitter {
@@ -136,7 +135,8 @@ class Libp2p extends EventEmitter {
peerStore: this.peerStore,
concurrency: this._options.dialer.maxParallelDials,
perPeerLimit: this._options.dialer.maxDialsPerPeer,
timeout: this._options.dialer.dialTimeout
timeout: this._options.dialer.dialTimeout,
resolvers: this._options.dialer.resolvers
})
this._modules.transport.forEach((Transport) => {
@@ -147,6 +147,7 @@ class Libp2p extends EventEmitter {
if (this._config.relay.enabled) {
this.transportManager.add(Circuit.prototype[Symbol.toStringTag], Circuit)
this.relay = new Relay(this)
}
// Attach stream multiplexers
@@ -157,10 +158,7 @@ class Libp2p extends EventEmitter {
})
// Add the identify service since we can multiplex
this.identifyService = new IdentifyService({
libp2p: this,
protocols: this.upgrader.protocols
})
this.identifyService = new IdentifyService({ libp2p: this })
this.handle(Object.values(IDENTIFY_PROTOCOLS), this.identifyService.handleMessage)
}
@@ -206,6 +204,7 @@ class Libp2p extends EventEmitter {
/**
* Overrides EventEmitter.emit to conditionally emit errors
* if there is a handler. If not, errors will be logged.
*
* @param {string} eventName
* @param {...any} args
* @returns {void}
@@ -240,6 +239,7 @@ class Libp2p extends EventEmitter {
/**
* Stop the libp2p node by closing its listeners and open connections
*
* @async
* @returns {void}
*/
@@ -247,6 +247,11 @@ class Libp2p extends EventEmitter {
log('libp2p is stopping')
try {
this._isStarted = false
// Relay
this.relay && this.relay.stop()
for (const service of this._discovery.values()) {
service.removeListener('peer', this._onDiscoveryPeer)
}
@@ -274,13 +279,13 @@ class Libp2p extends EventEmitter {
this.emit('error', err)
}
}
this._isStarted = false
log('libp2p has stopped')
}
/**
* Load keychain keys from the datastore.
* Imports the private key as 'self', if needed.
*
* @async
* @returns {void}
*/
@@ -299,6 +304,7 @@ class Libp2p extends EventEmitter {
/**
* Gets a Map of the current connections. The keys are the stringified
* `PeerId` of the peer. The value is an array of Connections to that peer.
*
* @returns {Map<string, Connection[]>}
*/
get connections () {
@@ -308,7 +314,8 @@ class Libp2p extends EventEmitter {
/**
* Dials to the provided peer. If successful, the known metadata of the
* peer will be added to the nodes `peerStore`
* @param {PeerId|Multiaddr|string} peer The peer to dial
*
* @param {PeerId|Multiaddr|string} peer - The peer to dial
* @param {object} options
* @param {AbortSignal} [options.signal]
* @returns {Promise<Connection>}
@@ -321,8 +328,9 @@ class Libp2p extends EventEmitter {
* 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`,
* and the `Connection` will be returned
*
* @async
* @param {PeerId|Multiaddr|string} peer The peer to dial
* @param {PeerId|Multiaddr|string} peer - The peer to dial
* @param {string[]|string} protocols
* @param {object} options
* @param {AbortSignal} [options.signal]
@@ -350,7 +358,8 @@ class Libp2p extends EventEmitter {
* 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>}
*
* @returns {Array<Multiaddr>}
*/
get multiaddrs () {
// Filter noAnnounce multiaddrs
@@ -376,7 +385,8 @@ class Libp2p extends EventEmitter {
/**
* Disconnects all connections to the given `peer`
* @param {PeerId|multiaddr|string} peer the peer to close connections to
*
* @param {PeerId|multiaddr|string} peer - the peer to close connections to
* @returns {Promise<void>}
*/
async hangUp (peer) {
@@ -397,7 +407,8 @@ class Libp2p extends EventEmitter {
/**
* Pings the given peer in order to obtain the operation latency.
* @param {PeerId|Multiaddr|string} peer The peer to ping
*
* @param {PeerId|Multiaddr|string} peer - The peer to ping
* @returns {Promise<number>}
*/
ping (peer) {
@@ -413,6 +424,7 @@ class Libp2p extends EventEmitter {
/**
* Registers the `handler` for each protocol
*
* @param {string[]|string} protocols
* @param {function({ connection:*, stream:*, protocol:string })} handler
*/
@@ -422,15 +434,14 @@ class Libp2p extends EventEmitter {
this.upgrader.protocols.set(protocol, handler)
})
// Only push if libp2p is running
if (this.isStarted() && this.identifyService) {
this.identifyService.pushToPeerStore(this.peerStore)
}
// Add new protocols to self protocols in the Protobook
this.peerStore.protoBook.add(this.peerId, protocols)
}
/**
* Removes the handler for each protocol. The protocol
* will no longer be supported on streams.
*
* @param {string[]|string} protocols
*/
unhandle (protocols) {
@@ -439,15 +450,14 @@ class Libp2p extends EventEmitter {
this.upgrader.protocols.delete(protocol)
})
// Only push if libp2p is running
if (this.isStarted() && this.identifyService) {
this.identifyService.pushToPeerStore(this.peerStore)
}
// Remove protocols from self protocols in the Protobook
this.peerStore.protoBook.remove(this.peerId, protocols)
}
async _onStarting () {
// Listen on the provided transports
await this.transportManager.listen()
// Listen on the provided transports for the provided addresses
const addrs = this.addressManager.getListenAddrs()
await this.transportManager.listen(addrs)
// Start PeerStore
await this.peerStore.start()
@@ -471,6 +481,7 @@ class Libp2p extends EventEmitter {
/**
* Called when libp2p has started and before it returns
*
* @private
*/
async _onDidStart () {
@@ -491,11 +502,15 @@ class Libp2p extends EventEmitter {
// Peer discovery
await this._setupPeerDiscovery()
// Relay
this.relay && this.relay.start()
}
/**
* Called whenever peer discovery services emit `peer` events.
* Known peers may be emitted.
*
* @private
* @param {{ id: PeerId, multiaddrs: Array<Multiaddr>, protocols: Array<string> }} peer
*/
@@ -513,6 +528,7 @@ class Libp2p extends EventEmitter {
* Will dial to the given `peerId` if the current number of
* connected peers is less than the configured `ConnectionManager`
* minConnections.
*
* @private
* @param {PeerId} peerId
*/
@@ -586,7 +602,8 @@ class Libp2p extends EventEmitter {
/**
* Like `new Libp2p(options)` except it will create a `PeerId`
* instance if one is not provided in options.
* @param {object} options Libp2p configuration options
*
* @param {object} options - Libp2p configuration options
* @returns {Libp2p}
*/
Libp2p.create = async function create (options = {}) {

View File

@@ -228,7 +228,7 @@ class Keychain {
/**
* List all the keys.
*
* @returns {KeyInfo[]}
* @returns {KeyInfo[]}
*/
async listKeys () {
const self = this
@@ -248,7 +248,7 @@ class Keychain {
* Find a key by it's id.
*
* @param {string} id - The universally unique key identifier.
* @returns {KeyInfo}
* @returns {KeyInfo}
*/
async findKeyById (id) {
try {
@@ -263,7 +263,7 @@ class Keychain {
* Find a key by it's name.
*
* @param {string} name - The local key name.
* @returns {KeyInfo}
* @returns {KeyInfo}
*/
async findKeyByName (name) {
if (!validateKeyName(name)) {
@@ -283,7 +283,7 @@ class Keychain {
* Remove an existing key.
*
* @param {string} name - The local key name; must already exist.
* @returns {KeyInfo}
* @returns {KeyInfo}
*/
async removeKey (name) {
const self = this
@@ -304,7 +304,7 @@ class Keychain {
*
* @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}
* @returns {KeyInfo}
*/
async renameKey (oldName, newName) {
const self = this
@@ -345,7 +345,7 @@ class Keychain {
*
* @param {string} name - The local key name; must already exist.
* @param {string} password - The password
* @returns {string}
* @returns {string}
*/
async exportKey (name, password) {
if (!validateKeyName(name)) {
@@ -372,7 +372,7 @@ class Keychain {
* @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}
* @returns {KeyInfo}
*/
async importKey (name, pem, password) {
const self = this
@@ -448,7 +448,7 @@ class Keychain {
* Gets the private key as PEM encoded PKCS #8 string.
*
* @param {string} name
* @returns {string}
* @returns {string}
* @private
*/
async _getPrivateKey (name) {

View File

@@ -77,7 +77,7 @@ exports.certificateForKey = (key, privateKey) => {
* resolve to either `true` or `false`.
*
* @param {Array} array
* @param {function(*)} asyncCompare An async function that returns a boolean
* @param {function(*)} asyncCompare - An async function that returns a boolean
*/
async function findAsync (array, asyncCompare) {
const promises = array.map(asyncCompare)

View File

@@ -66,6 +66,7 @@ class Metrics {
/**
* Gets the global `Stats` object
*
* @returns {Stats}
*/
get global () {
@@ -74,6 +75,7 @@ class Metrics {
/**
* Returns a list of `PeerId` strings currently being tracked
*
* @returns {Array<string>}
*/
get peers () {
@@ -83,6 +85,7 @@ class Metrics {
/**
* Returns the `Stats` object for the given `PeerId` whether it
* is a live peer, or in the disconnected peer LRU cache.
*
* @param {PeerId} peerId
* @returns {Stats}
*/
@@ -93,6 +96,7 @@ class Metrics {
/**
* Returns a list of all protocol strings currently being tracked.
*
* @returns {Array<string>}
*/
get protocols () {
@@ -101,6 +105,7 @@ class Metrics {
/**
* Returns the `Stats` object for the given `protocol`.
*
* @param {string} protocol
* @returns {Stats}
*/
@@ -112,6 +117,7 @@ class Metrics {
* Should be called when all connections to a given peer
* have closed. The `Stats` collection for the peer will
* be stopped and moved to an LRU for temporary retention.
*
* @param {PeerId} peerId
*/
onPeerDisconnected (peerId) {
@@ -131,10 +137,10 @@ class Metrics {
*
* @private
* @param {object} params
* @param {PeerId} params.remotePeer Remote peer
* @param {string} [params.protocol] Protocol string the stream is running
* @param {string} params.direction One of ['in','out']
* @param {number} params.dataLength Size of the message
* @param {PeerId} params.remotePeer - Remote peer
* @param {string} [params.protocol] - Protocol string the stream is running
* @param {string} params.direction - One of ['in','out']
* @param {number} params.dataLength - Size of the message
* @returns {void}
*/
_onMessage ({ remotePeer, protocol, direction, dataLength }) {
@@ -167,7 +173,8 @@ class Metrics {
* Replaces the `PeerId` string with the given `peerId`.
* If stats are already being tracked for the given `peerId`, the
* placeholder stats will be merged with the existing stats.
* @param {PeerId} placeholder A peerId string
*
* @param {PeerId} placeholder - A peerId string
* @param {PeerId} peerId
*/
updatePlaceholder (placeholder, peerId) {
@@ -198,9 +205,9 @@ class Metrics {
* with the placeholder string returned from here, and the known `PeerId`.
*
* @param {Object} options
* @param {{ sink: function(*), source: function() }} options.stream A duplex iterable stream
* @param {PeerId} [options.peerId] The id of the remote peer that's connected
* @param {string} [options.protocol] The protocol the stream is running
* @param {{ sink: function(*), source: function() }} options.stream - A duplex iterable stream
* @param {PeerId} [options.remotePeer] - The id of the remote peer that's connected
* @param {string} [options.protocol] - The protocol the stream is running
* @returns {string} The peerId string or placeholder string
*/
trackStream ({ stream, remotePeer, protocol }) {
@@ -233,6 +240,7 @@ class Metrics {
/**
* Merges `other` into `target`. `target` will be modified
* and returned.
*
* @param {Stats} target
* @param {Stats} other
* @returns {Stats}

View File

@@ -5,7 +5,7 @@ const LRU = require('hashlru')
/**
* Creates and returns a Least Recently Used Cache
*
* @param {Number} maxSize
* @param {number} maxSize
* @returns {LRUCache}
*/
module.exports = (maxSize) => {

View File

@@ -196,8 +196,8 @@ class Stats extends EventEmitter {
*
* @private
* @param {string} key
* @param {number} timeDiffMS Time in milliseconds
* @param {Timestamp} latestTime Time in ticks
* @param {number} timeDiffMS - Time in milliseconds
* @param {Timestamp} latestTime - Time in ticks
* @returns {void}
*/
_updateFrequencyFor (key, timeDiffMS, latestTime) {

View File

@@ -15,9 +15,9 @@ module.exports = (node) => {
/**
* Iterates over all peer routers in series to find the given peer.
*
* @param {String} id The id of the peer to find
* @param {string} id - The id of the peer to find
* @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[] }>}
*/
findPeer: async (id, options) => { // eslint-disable-line require-await

View File

@@ -23,29 +23,32 @@ const Envelope = require('../record/envelope')
class AddressBook extends Book {
/**
* Address object
*
* @typedef {Object} Address
* @property {Multiaddr} multiaddr peer multiaddr.
* @property {boolean} isCertified obtained from a signed peer record.
*/
/**
* CertifiedRecord object
* @typedef {Object} CertifiedRecord
* @property {Uint8Array} raw raw envelope.
* @property {number} seqNumber seq counter.
*/
* CertifiedRecord object
*
* @typedef {Object} CertifiedRecord
* @property {Uint8Array} raw raw envelope.
* @property {number} seqNumber seq counter.
*/
/**
* Entry object for the addressBook
* @typedef {Object} Entry
* @property {Array<Address>} addresses peer Addresses.
* @property {CertifiedRecord} record certified peer record.
*/
* Entry object for the addressBook
*
* @typedef {Object} Entry
* @property {Array<Address>} addresses peer Addresses.
* @property {CertifiedRecord} record certified peer record.
*/
/**
* @constructor
* @param {PeerStore} peerStore
*/
* @class
* @param {PeerStore} peerStore
*/
constructor (peerStore) {
/**
* PeerStore Event emitter, used by the AddressBook to emit:
@@ -66,6 +69,7 @@ class AddressBook extends Book {
/**
* Map known peers to their known Address Entries.
*
* @type {Map<string, Array<Entry>>}
*/
this.data = new Map()
@@ -75,8 +79,9 @@ class AddressBook extends Book {
* ConsumePeerRecord adds addresses from a signed peer record contained in a record envelope.
* This will return a boolean that indicates if the record was successfully processed and added
* into the AddressBook.
*
* @param {Envelope} envelope
* @return {boolean}
* @returns {boolean}
*/
consumePeerRecord (envelope) {
let peerRecord
@@ -127,8 +132,9 @@ class AddressBook extends Book {
/**
* Get the raw Envelope for a peer. Returns
* undefined if no Envelope is found.
*
* @param {PeerId} peerId
* @return {Uint8Array|undefined}
* @returns {Uint8Array|undefined}
*/
getRawEnvelope (peerId) {
const entry = this.data.get(peerId.toB58String())
@@ -143,8 +149,9 @@ class AddressBook extends Book {
/**
* Get an Envelope containing a PeerRecord for the given peer.
* Returns undefined if no record exists.
*
* @param {PeerId} peerId
* @return {Promise<Envelope|void>}
* @returns {Promise<Envelope|void>}
*/
getPeerRecord (peerId) {
const raw = this.getRawEnvelope(peerId)
@@ -161,6 +168,7 @@ class AddressBook extends Book {
* This will replace previously stored multiaddrs, if available.
* Replacing stored multiaddrs might result in losing obtained certified addresses.
* If you are not sure, it's recommended to use `add` instead.
*
* @override
* @param {PeerId} peerId
* @param {Array<Multiaddr>} multiaddrs
@@ -211,6 +219,7 @@ class AddressBook extends Book {
/**
* 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}
@@ -258,9 +267,10 @@ class AddressBook extends Book {
/**
* Get the known data of a provided peer.
*
* @override
* @param {PeerId} peerId
* @returns {Array<data>}
* @returns {Array<Address>|undefined}
*/
get (peerId) {
if (!PeerId.isPeerId(peerId)) {
@@ -274,6 +284,7 @@ class AddressBook extends Book {
/**
* Transforms received multiaddrs into Address.
*
* @private
* @param {Array<Multiaddr>} multiaddrs
* @param {boolean} [isCertified]
@@ -306,6 +317,7 @@ class AddressBook extends Book {
* Get the known multiaddrs for a given peer. All returned multiaddrs
* will include the encapsulated `PeerId` of the peer.
* Returns `undefined` if there are no known multiaddrs for the given peer.
*
* @param {PeerId} peerId
* @returns {Array<Multiaddr>|undefined}
*/

View File

@@ -14,12 +14,12 @@ const passthrough = data => data
*/
class Book {
/**
* @constructor
* @class
* @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.
* @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
@@ -29,6 +29,7 @@ class Book {
/**
* Map known peers to their data.
*
* @type {Map<string, Array<Data>}
*/
this.data = new Map()
@@ -36,6 +37,7 @@ class Book {
/**
* Set known data of a provided peer.
*
* @param {PeerId} peerId
* @param {Array<Data>|Data} data
*/
@@ -45,12 +47,13 @@ class Book {
/**
* 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}
* @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.
* @returns {void}
*/
_setData (peerId, data, { emit = true } = {}) {
const b58key = peerId.toB58String()
@@ -64,6 +67,7 @@ class Book {
/**
* Emit data.
*
* @private
* @param {PeerId} peerId
* @param {*} data
@@ -78,6 +82,7 @@ class Book {
/**
* Get the known data of a provided peer.
* Returns `undefined` if there is no available data for the given peer.
*
* @param {PeerId} peerId
* @returns {Array<Data>|undefined}
*/
@@ -93,6 +98,7 @@ class Book {
/**
* Deletes the provided peer from the book.
*
* @param {PeerId} peerId
* @returns {boolean}
*/

View File

@@ -19,6 +19,7 @@ const {
/**
* Responsible for managing known peers, as well as their addresses, protocols and metadata.
*
* @fires PeerStore#peer Emitted when a new peer is added.
* @fires PeerStore#change:protocols Emitted when a known peer supports a different set of protocols.
* @fires PeerStore#change:multiaddrs Emitted when a known peer has a different set of multiaddrs.
@@ -28,6 +29,7 @@ const {
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.
@@ -36,7 +38,9 @@ class PeerStore extends EventEmitter {
*/
/**
* @constructor
* @param {object} options
* @param {PeerId} options.peerId
* @class
*/
constructor ({ peerId }) {
super()
@@ -76,6 +80,7 @@ class PeerStore extends EventEmitter {
/**
* Get all the stored information of every peer known.
*
* @returns {Map<string, Peer>}
*/
get peers () {
@@ -99,6 +104,7 @@ class PeerStore extends EventEmitter {
/**
* Delete the information of the given peer in every book.
*
* @param {PeerId} peerId
* @returns {boolean} true if found and removed
*/
@@ -113,6 +119,7 @@ class PeerStore extends EventEmitter {
/**
* Get the stored information of a given peer.
*
* @param {PeerId} peerId
* @returns {Peer}
*/

View File

@@ -18,9 +18,9 @@ const {
*/
class KeyBook extends Book {
/**
* @constructor
* @param {PeerStore} peerStore
*/
* @class
* @param {PeerStore} peerStore
*/
constructor (peerStore) {
super({
peerStore,
@@ -31,6 +31,7 @@ class KeyBook extends Book {
/**
* Map known peers to their known Public Key.
*
* @type {Map<string, PeerId>}
*/
this.data = new Map()
@@ -38,11 +39,12 @@ class KeyBook extends Book {
/**
* Set the Peer public key.
*
* @override
* @param {PeerId} peerId
* @param {RsaPublicKey|Ed25519PublicKey|Secp256k1PublicKey} publicKey
* @return {KeyBook}
*/
* @returns {KeyBook}
*/
set (peerId, publicKey) {
if (!PeerId.isPeerId(peerId)) {
log.error('peerId must be an instance of peer-id to store data')
@@ -67,9 +69,10 @@ class KeyBook extends Book {
/**
* Get Public key of the given PeerId, if stored.
*
* @override
* @param {PeerId} peerId
* @return {RsaPublicKey|Ed25519PublicKey|Secp256k1PublicKey}
* @returns {RsaPublicKey|Ed25519PublicKey|Secp256k1PublicKey}
*/
get (peerId) {
if (!PeerId.isPeerId(peerId)) {

View File

@@ -17,13 +17,14 @@ const {
/**
* 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
*/
* @class
* @param {PeerStore} peerStore
*/
constructor (peerStore) {
/**
* PeerStore Event emitter, used by the MetadataBook to emit:
@@ -37,6 +38,7 @@ class MetadataBook extends Book {
/**
* Map known peers to their known protocols.
*
* @type {Map<string, Map<string, Uint8Array>>}
*/
this.data = new Map()
@@ -44,10 +46,11 @@ class MetadataBook extends Book {
/**
* Set metadata key and value of a provided peer.
*
* @override
* @param {PeerId} peerId
* @param {string} key metadata key
* @param {Uint8Array} value metadata value
* @param {string} key - metadata key
* @param {Uint8Array} value - metadata value
* @returns {ProtoBook}
*/
set (peerId, key, value) {
@@ -68,6 +71,7 @@ class MetadataBook extends Book {
/**
* Set data into the datastructure
*
* @override
*/
_setValue (peerId, key, value, { emit = true } = {}) {
@@ -89,6 +93,7 @@ class MetadataBook extends Book {
/**
* Get the known data of a provided peer.
*
* @param {PeerId} peerId
* @returns {Map<string, Uint8Array>}
*/
@@ -102,6 +107,7 @@ class MetadataBook extends Book {
/**
* Get specific metadata value, if it exists
*
* @param {PeerId} peerId
* @param {string} key
* @returns {Uint8Array}
@@ -117,6 +123,7 @@ class MetadataBook extends Book {
/**
* Deletes the provided peer from the book.
*
* @param {PeerId} peerId
* @returns {boolean}
*/
@@ -136,6 +143,7 @@ class MetadataBook extends Book {
/**
* Deletes the provided peer metadata key from the book.
*
* @param {PeerId} peerId
* @param {string} key
* @returns {boolean}

View File

@@ -26,11 +26,11 @@ const Protocols = require('./pb/proto-book.proto')
*/
class PersistentPeerStore extends PeerStore {
/**
* @constructor
* @class
* @param {Object} properties
* @param {PeerId} properties.peerId
* @param {Datastore} properties.datastore Datastore to persist data.
* @param {number} [properties.threshold = 5] Number of dirty peers allowed before commit data.
* @param {Datastore} properties.datastore - Datastore to persist data.
* @param {number} [properties.threshold = 5] - Number of dirty peers allowed before commit data.
*/
constructor ({ peerId, datastore, threshold = 5 }) {
super({ peerId })
@@ -47,6 +47,7 @@ class PersistentPeerStore extends PeerStore {
/**
* Peers metadata changed mapping peer identifers to metadata changed.
*
* @type {Map<string, Set<string>>}
*/
this._dirtyMetadata = new Map()
@@ -57,7 +58,8 @@ class PersistentPeerStore extends PeerStore {
/**
* Start Persistent PeerStore.
* @return {Promise<void>}
*
* @returns {Promise<void>}
*/
async start () {
log('PeerStore is starting')
@@ -65,7 +67,7 @@ class PersistentPeerStore extends PeerStore {
// 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:pubkey', this._addDirtyPeerKey)
this.on('change:metadata', this._addDirtyPeerMetadata)
// Load data
@@ -76,6 +78,11 @@ class PersistentPeerStore extends PeerStore {
log('PeerStore started')
}
/**
* Stop Persistent PeerStore.
*
* @returns {Promise<void>}
*/
async stop () {
log('PeerStore is stopping')
this.removeAllListeners()
@@ -85,6 +92,7 @@ class PersistentPeerStore extends PeerStore {
/**
* Add modified peer to the dirty set
*
* @private
* @param {Object} params
* @param {PeerId} params.peerId
@@ -103,8 +111,35 @@ class PersistentPeerStore extends PeerStore {
}
}
/**
* Add modified peer key to the dirty set
*
* @private
* @param {Object} params
* @param {PeerId} params.peerId
*/
_addDirtyPeerKey ({ peerId }) {
// Not add if inline key available
if (peerId.hasInlinePublicKey()) {
return
}
const peerIdstr = peerId.toB58String()
log('add dirty peer key', 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
@@ -131,9 +166,9 @@ class PersistentPeerStore extends PeerStore {
/**
* Add all the peers current data to a datastore batch and commit it.
*
* @private
* @param {Array<string>} peers
* @return {Promise<void>}
* @returns {Promise<void>}
*/
async _commitData () {
const commitPeers = Array.from(this._dirtyPeers)
@@ -155,7 +190,7 @@ class PersistentPeerStore extends PeerStore {
this._batchAddressBook(peerId, batch)
// Key Book
this._batchKeyBook(peerId, batch)
!peerId.hasInlinePublicKey() && this._batchKeyBook(peerId, batch)
// Metadata Book
this._batchMetadataBook(peerId, batch)
@@ -170,6 +205,7 @@ class PersistentPeerStore extends PeerStore {
/**
* Add address book data of the peer to the batch.
*
* @private
* @param {PeerId} peerId
* @param {Object} batch
@@ -206,6 +242,7 @@ class PersistentPeerStore extends PeerStore {
/**
* Add Key book data of the peer to the batch.
*
* @private
* @param {PeerId} peerId
* @param {Object} batch
@@ -231,6 +268,7 @@ class PersistentPeerStore extends PeerStore {
/**
* Add metadata book data of the peer to the batch.
*
* @private
* @param {PeerId} peerId
* @param {Object} batch
@@ -257,6 +295,7 @@ class PersistentPeerStore extends PeerStore {
/**
* Add proto book data of the peer to the batch.
*
* @private
* @param {PeerId} peerId
* @param {Object} batch
@@ -284,11 +323,12 @@ class PersistentPeerStore extends PeerStore {
/**
* Process datastore entry and add its data to the correct book.
*
* @private
* @param {Object} params
* @param {Key} params.key datastore key
* @param {Uint8Array} params.value datastore value stored
* @return {Promise<void>}
* @param {Key} params.key - datastore key
* @param {Uint8Array} params.value - datastore value stored
* @returns {Promise<void>}
*/
async _processDatastoreEntry ({ key, value }) {
try {

View File

@@ -16,13 +16,14 @@ const {
/**
* 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
*/
* @class
* @param {PeerStore} peerStore
*/
constructor (peerStore) {
/**
* PeerStore Event emitter, used by the ProtoBook to emit:
@@ -37,6 +38,7 @@ class ProtoBook extends Book {
/**
* Map known peers to their known protocols.
*
* @type {Map<string, Set<string>>}
*/
this.data = new Map()
@@ -45,6 +47,7 @@ class ProtoBook extends Book {
/**
* 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
@@ -83,6 +86,7 @@ class ProtoBook extends Book {
/**
* 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}
@@ -108,13 +112,50 @@ class ProtoBook extends Book {
return this
}
protocols = [...newSet]
this._setData(peerId, newSet)
log(`added provided protocols for ${id}`)
return this
}
/**
* Removes known protocols of a provided peer.
* If the protocols did not exist before, nothing will be done.
*
* @param {PeerId} peerId
* @param {Array<string>} protocols
* @returns {ProtoBook}
*/
remove (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)
if (recSet) {
const newSet = new Set([
...recSet
].filter((p) => !protocols.includes(p)))
// Any protocol removed?
if (recSet.size === newSet.size) {
return this
}
this._setData(peerId, newSet)
log(`removed provided protocols for ${id}`)
}
return this
}
}
module.exports = ProtoBook

View File

@@ -14,9 +14,10 @@ const { PROTOCOL, PING_LENGTH } = require('./constants')
/**
* Ping a given peer and wait for its response, getting the operation latency.
*
* @param {Libp2p} node
* @param {PeerId|multiaddr} peer
* @returns {Promise<Number>}
* @returns {Promise<number>}
*/
async function ping (node, peer) {
log('dialing %s to %s', PROTOCOL, peer.toB58String ? peer.toB58String() : peer)
@@ -44,6 +45,7 @@ async function ping (node, peer) {
/**
* Subscribe ping protocol handler.
*
* @param {Libp2p} node
*/
function mount (node) {
@@ -52,6 +54,7 @@ function mount (node) {
/**
* Unsubscribe ping protocol handler.
*
* @param {Libp2p} node
*/
function unmount (node) {

View File

@@ -12,10 +12,10 @@ log.trace = debug('libp2p:pnet:trace')
log.error = debug('libp2p:pnet:err')
/**
* Creates a stream iterable to encrypt messages in a private network
* Creates a stream iterable to encrypt messages in a private network
*
* @param {Uint8Array} nonce The nonce to use in encryption
* @param {Uint8Array} psk The private shared key to use in encryption
* @param {Uint8Array} nonce - The nonce to use in encryption
* @param {Uint8Array} psk - The private shared key to use in encryption
* @returns {*} a through iterable
*/
module.exports.createBoxStream = (nonce, psk) => {
@@ -28,10 +28,10 @@ module.exports.createBoxStream = (nonce, psk) => {
}
/**
* Creates a stream iterable to decrypt messages in a private network
* Creates a stream iterable to decrypt messages in a private network
*
* @param {Uint8Array} nonce The nonce of the remote peer
* @param {Uint8Array} psk The private shared key to use in decryption
* @param {Uint8Array} nonce - The nonce of the remote peer
* @param {Uint8Array} psk - The private shared key to use in decryption
* @returns {*} a through iterable
*/
module.exports.createUnboxStream = (nonce, psk) => {

View File

@@ -25,8 +25,8 @@ log.error = debug('libp2p:pnet:err')
*/
class Protector {
/**
* @param {Uint8Array} keyBuffer The private shared key buffer
* @constructor
* @param {Uint8Array} keyBuffer - The private shared key buffer
* @class
*/
constructor (keyBuffer) {
const decodedPSK = decodeV1PSK(keyBuffer)
@@ -39,7 +39,7 @@ class Protector {
* between its two peers from the PSK the Protector instance was
* created with.
*
* @param {Connection} connection The connection to protect
* @param {Connection} connection - The connection to protect
* @returns {*} A protected duplex iterable
*/
async protect (connection) {

View File

@@ -7,7 +7,8 @@ const uint8ArrayFromString = require('uint8arrays/from-string')
/**
* Generates a PSK that can be used in a libp2p-pnet private network
* @param {Uint8Array} bytes An object to write the psk into
*
* @param {Uint8Array} bytes - An object to write the psk into
* @returns {void}
*/
function generate (bytes) {

View File

@@ -5,6 +5,7 @@ module.exports = (PubsubRouter, libp2p, options) => {
class Pubsub extends PubsubRouter {
/**
* Subscribes to a given topic.
*
* @override
* @param {string} topic
* @param {function(msg: InMessage)} [handler]
@@ -18,6 +19,7 @@ module.exports = (PubsubRouter, libp2p, options) => {
/**
* Unsubscribe from the given topic.
*
* @override
* @param {string} topic
* @param {function(msg: InMessage)} [handler]

View File

@@ -20,12 +20,12 @@ const Protobuf = require('./envelope.proto')
*/
class Envelope {
/**
* @constructor
* @class
* @param {object} params
* @param {PeerId} params.peerId
* @param {Uint8Array} params.payloadType
* @param {Uint8Array} params.payload marshaled record
* @param {Uint8Array} params.signature signature of the domain string :: type hint :: payload.
* @param {Uint8Array} params.payload - marshaled record
* @param {Uint8Array} params.signature - signature of the domain string :: type hint :: payload.
*/
constructor ({ peerId, payloadType, payload, signature }) {
this.peerId = peerId
@@ -39,7 +39,8 @@ class Envelope {
/**
* Marshal the envelope content.
* @return {Uint8Array}
*
* @returns {Uint8Array}
*/
marshal () {
if (this._marshal) {
@@ -60,8 +61,9 @@ class Envelope {
/**
* Verifies if the other Envelope is identical to this one.
*
* @param {Envelope} other
* @return {boolean}
* @returns {boolean}
*/
equals (other) {
return uint8arraysEquals(this.peerId.pubKey.bytes, other.peerId.pubKey.bytes) &&
@@ -72,8 +74,9 @@ class Envelope {
/**
* Validate envelope data signature for the given domain.
*
* @param {string} domain
* @return {Promise<boolean>}
* @returns {Promise<boolean>}
*/
validate (domain) {
const signData = formatSignaturePayload(domain, this.payloadType, this.payload)
@@ -84,10 +87,11 @@ class Envelope {
/**
* Helper function that prepares a Uint8Array to sign or verify a signature.
*
* @param {string} domain
* @param {Uint8Array} payloadType
* @param {Uint8Array} payload
* @return {Uint8Array}
* @returns {Uint8Array}
*/
const formatSignaturePayload = (domain, payloadType, payload) => {
// When signing, a peer will prepare a Uint8Array by concatenating the following:
@@ -115,8 +119,9 @@ const formatSignaturePayload = (domain, payloadType, payload) => {
/**
* Unmarshal a serialized Envelope protobuf message.
*
* @param {Uint8Array} data
* @return {Promise<Envelope>}
* @returns {Promise<Envelope>}
*/
Envelope.createFromProtobuf = async (data) => {
const envelopeData = Protobuf.decode(data)
@@ -131,13 +136,14 @@ Envelope.createFromProtobuf = async (data) => {
}
/**
* Seal marshals the given Record, places the marshaled bytes inside an Envelope
* and signs it with the given peerId's private key.
* @async
* @param {Record} record
* @param {PeerId} peerId
* @return {Envelope}
*/
* Seal marshals the given Record, places the marshaled bytes inside an Envelope
* and signs it with the given peerId's private key.
*
* @async
* @param {Record} record
* @param {PeerId} peerId
* @returns {Envelope}
*/
Envelope.seal = async (record, peerId) => {
const domain = record.domain
const payloadType = record.codec
@@ -157,9 +163,10 @@ Envelope.seal = async (record, peerId) => {
/**
* Open and certify a given marshalled envelope.
* Data is unmarshalled and the signature validated for the given domain.
*
* @param {Uint8Array} data
* @param {string} domain
* @return {Envelope}
* @returns {Envelope}
*/
Envelope.openAndCertify = async (data, domain) => {
const envelope = await Envelope.createFromProtobuf(data)

View File

@@ -17,11 +17,11 @@ const {
*/
class PeerRecord extends Record {
/**
* @constructor
* @class
* @param {object} params
* @param {PeerId} params.peerId
* @param {Array<multiaddr>} params.multiaddrs addresses of the associated peer.
* @param {number} [params.seqNumber] monotonically-increasing sequence counter that's used to order PeerRecords in time.
* @param {Array<multiaddr>} params.multiaddrs - addresses of the associated peer.
* @param {number} [params.seqNumber] - monotonically-increasing sequence counter that's used to order PeerRecords in time.
*/
constructor ({ peerId, multiaddrs = [], seqNumber = Date.now() }) {
super(ENVELOPE_DOMAIN_PEER_RECORD, ENVELOPE_PAYLOAD_TYPE_PEER_RECORD)
@@ -36,7 +36,8 @@ class PeerRecord extends Record {
/**
* Marshal a record to be used in an envelope.
* @return {Uint8Array}
*
* @returns {Uint8Array}
*/
marshal () {
if (this._marshal) {
@@ -56,8 +57,9 @@ class PeerRecord extends Record {
/**
* Returns true if `this` record equals the `other`.
*
* @param {Record} other
* @return {boolean}
* @returns {boolean}
*/
equals (other) {
// Validate PeerId
@@ -81,8 +83,9 @@ class PeerRecord extends Record {
/**
* Unmarshal Peer Record Protobuf.
* @param {Uint8Array} buf marshaled peer record.
* @return {PeerRecord}
*
* @param {Uint8Array} buf - marshaled peer record.
* @returns {PeerRecord}
*/
PeerRecord.createFromProtobuf = (buf) => {
// Decode

21
src/record/utils.js Normal file
View File

@@ -0,0 +1,21 @@
'use strict'
const Envelope = require('./envelope')
const PeerRecord = require('./peer-record')
/**
* Create (or update if existing) self peer record and store it in the AddressBook.
*
* @param {libp2p} libp2p
* @returns {Promise<void>}
*/
async function updateSelfPeerRecord (libp2p) {
const peerRecord = new PeerRecord({
peerId: libp2p.peerId,
multiaddrs: libp2p.multiaddrs
})
const envelope = await Envelope.seal(peerRecord, libp2p.peerId)
libp2p.peerStore.addressBook.consumePeerRecord(envelope)
}
module.exports.updateSelfPeerRecord = updateSelfPeerRecord

View File

@@ -18,7 +18,7 @@ class Registrar {
* @param {Object} props
* @param {PeerStore} props.peerStore
* @param {connectionManager} props.connectionManager
* @constructor
* @class
*/
constructor ({ peerStore, connectionManager }) {
// Used on topology to listen for protocol changes
@@ -49,6 +49,7 @@ class Registrar {
/**
* Get a connection with a peer.
*
* @param {PeerId} peerId
* @returns {Connection}
*/
@@ -58,8 +59,9 @@ class Registrar {
/**
* Register handlers for a set of multicodecs given
* @param {Topology} topology protocol topology
* @return {string} registrar identifier
*
* @param {Topology} topology - protocol topology
* @returns {string} registrar identifier
*/
register (topology) {
if (!Topology.isTopology(topology)) {
@@ -79,8 +81,9 @@ class Registrar {
/**
* Unregister topology.
* @param {string} id registrar identifier
* @return {boolean} unregistered successfully
*
* @param {string} id - registrar identifier
* @returns {boolean} unregistered successfully
*/
unregister (id) {
return this.topologies.delete(id)
@@ -88,6 +91,7 @@ class Registrar {
/**
* Remove a disconnected peer from the record
*
* @param {Connection} connection
* @param {Error} [error]
* @returns {void}

View File

@@ -7,13 +7,15 @@ const debug = require('debug')
const log = debug('libp2p:transports')
log.error = debug('libp2p:transports:error')
const { updateSelfPeerRecord } = require('./record/utils')
class TransportManager {
/**
* @constructor
* @class
* @param {object} options
* @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 {boolean} [options.faultTolerance = FAULT_TOLERANCE.FATAL_ALL] Address listen error tolerance.
* @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 {boolean} [options.faultTolerance = FAULT_TOLERANCE.FATAL_ALL] - Address listen error tolerance.
*/
constructor ({ libp2p, upgrader, faultTolerance = FAULT_TOLERANCE.FATAL_ALL }) {
this.libp2p = libp2p
@@ -26,9 +28,9 @@ class TransportManager {
/**
* Adds a `Transport` to the manager
*
* @param {String} key
* @param {string} key
* @param {Transport} Transport
* @param {*} transportOptions Additional options to pass to the transport
* @param {*} transportOptions - Additional options to pass to the transport
* @returns {void}
*/
add (key, Transport, transportOptions = {}) {
@@ -54,6 +56,7 @@ class TransportManager {
/**
* Stops all listeners
*
* @async
*/
async close () {
@@ -62,6 +65,8 @@ class TransportManager {
log('closing listeners for %s', key)
while (listeners.length) {
const listener = listeners.pop()
listener.removeAllListeners('listening')
listener.removeAllListeners('close')
tasks.push(listener.close())
}
}
@@ -75,6 +80,7 @@ class TransportManager {
/**
* Dials the given Multiaddr over it's supported transport
*
* @param {Multiaddr} ma
* @param {*} options
* @returns {Promise<Connection>}
@@ -95,6 +101,7 @@ class TransportManager {
/**
* Returns all Multiaddr's the listeners are using
*
* @returns {Multiaddr[]}
*/
getAddrs () {
@@ -109,6 +116,7 @@ class TransportManager {
/**
* Returns all the transports instances.
*
* @returns {Iterator<Transport>}
*/
getTransports () {
@@ -117,6 +125,7 @@ class TransportManager {
/**
* Finds a transport that matches the given Multiaddr
*
* @param {Multiaddr} ma
* @returns {Transport|null}
*/
@@ -130,12 +139,12 @@ class TransportManager {
/**
* Starts listeners for each listen Multiaddr.
*
* @async
* @param {Array<Multiaddr>} addrs - addresses to attempt to listen on
*/
async listen () {
const addrs = this.libp2p.addressManager.getListenAddrs()
if (addrs.length === 0) {
async listen (addrs) {
if (!addrs || addrs.length === 0) {
log('no addresses were provided for listening, this node is dial only')
return
}
@@ -151,6 +160,10 @@ class TransportManager {
const listener = transport.createListener({}, this.onConnection)
this._listeners.get(key).push(listener)
// Track listen/close events
listener.on('listening', () => updateSelfPeerRecord(this.libp2p))
listener.on('close', () => updateSelfPeerRecord(this.libp2p))
// We need to attempt to listen on everything
tasks.push(listener.listen(addr))
}
@@ -195,6 +208,8 @@ class TransportManager {
if (this._listeners.has(key)) {
// Close any running listeners
for (const listener of this._listeners.get(key)) {
listener.removeAllListeners('listening')
listener.removeAllListeners('close')
await listener.close()
}
}
@@ -206,6 +221,7 @@ class TransportManager {
/**
* Removes all transports from the manager.
* If any listeners are running, they will be closed.
*
* @async
*/
async removeAll () {
@@ -222,6 +238,7 @@ 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}
*/

View File

@@ -5,6 +5,7 @@ const log = debug('libp2p:upgrader')
log.error = debug('libp2p:upgrader:error')
const Multistream = require('multistream-select')
const { Connection } = require('libp2p-interfaces/src/connection')
const ConnectionStatus = require('libp2p-interfaces/src/connection/status')
const PeerId = require('peer-id')
const pipe = require('it-pipe')
const errCode = require('err-code')
@@ -14,7 +15,7 @@ const { codes } = require('./errors')
/**
* @typedef MultiaddrConnection
* @property {function} sink
* @property {Function} sink
* @property {AsyncIterator} source
* @property {*} conn
* @property {Multiaddr} remoteAddr
@@ -34,7 +35,7 @@ class Upgrader {
* @param {Metrics} options.metrics
* @param {Map<string, Crypto>} options.cryptos
* @param {Map<string, Muxer>} options.muxers
* @param {function(Connection)} options.onConnection Called when a connection is upgraded
* @param {function(Connection)} options.onConnection - Called when a connection is upgraded
* @param {function(Connection)} options.onConnectionEnd
*/
constructor ({
@@ -57,6 +58,7 @@ class Upgrader {
/**
* Upgrades an inbound connection
*
* @async
* @param {MultiaddrConnection} maConn
* @returns {Promise<Connection>}
@@ -124,6 +126,7 @@ class Upgrader {
/**
* Upgrades an outbound connection
*
* @async
* @param {MultiaddrConnection} maConn
* @returns {Promise<Connection>}
@@ -198,14 +201,15 @@ class Upgrader {
/**
* A convenience method for generating a new `Connection`
*
* @private
* @param {object} options
* @param {string} cryptoProtocol The crypto protocol that was negotiated
* @param {string} direction One of ['inbound', 'outbound']
* @param {MultiaddrConnection} maConn The transport layer connection
* @param {*} upgradedConn A duplex connection returned from multiplexer and/or crypto selection
* @param {Muxer} Muxer The muxer to be used for muxing
* @param {PeerId} remotePeer The peer the connection is with
* @param {string} options.cryptoProtocol - The crypto protocol that was negotiated
* @param {string} options.direction - One of ['inbound', 'outbound']
* @param {MultiaddrConnection} options.maConn - The transport layer connection
* @param {*} options.upgradedConn - A duplex connection returned from multiplexer and/or crypto selection
* @param {Muxer} options.Muxer - The muxer to be used for muxing
* @param {PeerId} options.remotePeer - The peer the connection is with
* @returns {Connection}
*/
_createConnection ({
@@ -265,8 +269,18 @@ class Upgrader {
maConn.timeline = new Proxy(_timeline, {
set: (...args) => {
if (connection && args[1] === 'close' && args[2] && !_timeline.close) {
connection.stat.status = 'closed'
this.onConnectionEnd(connection)
// Wait for close to finish before notifying of the closure
(async () => {
try {
if (connection.stat.status === ConnectionStatus.OPEN) {
await connection.close()
}
} catch (err) {
log.error(err)
} finally {
this.onConnectionEnd(connection)
}
})()
}
return Reflect.set(...args)
@@ -292,7 +306,13 @@ class Upgrader {
},
newStream: newStream || errConnectionNotMultiplexed,
getStreams: () => muxer ? muxer.streams : errConnectionNotMultiplexed,
close: err => maConn.close(err)
close: async (err) => {
await maConn.close(err)
// Ensure remaining streams are aborted
if (muxer) {
muxer.streams.map(stream => stream.abort(err))
}
}
})
this.onConnection(connection)
@@ -302,9 +322,10 @@ class Upgrader {
/**
* Routes incoming streams to the correct handler
*
* @private
* @param {object} options
* @param {Connection} options.connection The connection the stream belongs to
* @param {Connection} options.connection - The connection the stream belongs to
* @param {Stream} options.stream
* @param {string} options.protocol
*/
@@ -315,9 +336,10 @@ class Upgrader {
/**
* Attempts to encrypt the incoming `connection` with the provided `cryptos`.
*
* @private
* @async
* @param {PeerId} localPeer The initiators PeerId
* @param {PeerId} localPeer - The initiators PeerId
* @param {*} connection
* @param {Map<string, Crypto>} cryptos
* @returns {CryptoResult} An encrypted connection, remote peer `PeerId` and the protocol of the `Crypto` used
@@ -344,9 +366,10 @@ class Upgrader {
/**
* Attempts to encrypt the given `connection` with the provided `cryptos`.
* The first `Crypto` module to succeed will be used
*
* @private
* @async
* @param {PeerId} localPeer The initiators PeerId
* @param {PeerId} localPeer - The initiators PeerId
* @param {*} connection
* @param {PeerId} remotePeerId
* @param {Map<string, Crypto>} cryptos
@@ -374,10 +397,11 @@ class Upgrader {
/**
* Selects one of the given muxers via multistream-select. That
* muxer will be used for all future streams on the connection.
*
* @private
* @async
* @param {*} connection A basic duplex connection to multiplex
* @param {Map<string, Muxer>} muxers The muxers to attempt multiplexing with
* @param {*} connection - A basic duplex connection to multiplex
* @param {Map<string, Muxer>} muxers - The muxers to attempt multiplexing with
* @returns {*} A muxed connection
*/
async _multiplexOutbound (connection, muxers) {
@@ -397,10 +421,11 @@ class Upgrader {
/**
* Registers support for one of the given muxers via multistream-select. The
* selected muxer will be used for all future streams on the connection.
*
* @private
* @async
* @param {*} connection A basic duplex connection to multiplex
* @param {Map<string, Muxer>} muxers The muxers to attempt multiplexing with
* @param {*} connection - A basic duplex connection to multiplex
* @param {Map<string, Muxer>} muxers - The muxers to attempt multiplexing with
* @returns {*} A muxed connection
*/
async _multiplexInbound (connection, muxers) {

View File

@@ -12,6 +12,7 @@ const pDefer = require('p-defer')
const mergeOptions = require('merge-options')
const CID = require('cids')
const ipfsHttpClient = require('ipfs-http-client')
const DelegatedContentRouter = require('libp2p-delegated-content-routing')
const multiaddr = require('multiaddr')
@@ -99,11 +100,11 @@ describe('content-routing', () => {
beforeEach(async () => {
const [peerId] = await peerUtils.createPeerId({ fixture: true })
delegate = new DelegatedContentRouter(peerId, {
delegate = new DelegatedContentRouter(peerId, ipfsHttpClient({
host: '0.0.0.0',
protocol: 'http',
port: 60197
}, [
}), [
multiaddr('/ip4/0.0.0.0/tcp/60197')
])
@@ -230,11 +231,11 @@ describe('content-routing', () => {
beforeEach(async () => {
const [peerId] = await peerUtils.createPeerId({ fixture: true })
delegate = new DelegatedContentRouter(peerId, {
delegate = new DelegatedContentRouter(peerId, ipfsHttpClient({
host: '0.0.0.0',
protocol: 'http',
port: 60197
}, [
}), [
multiaddr('/ip4/0.0.0.0/tcp/60197')
])

View File

@@ -11,7 +11,9 @@ const PeerId = require('peer-id')
const delay = require('delay')
const pDefer = require('p-defer')
const pSettle = require('p-settle')
const pWaitFor = require('p-wait-for')
const pipe = require('it-pipe')
const pushable = require('it-pushable')
const AggregateError = require('aggregate-error')
const { Connection } = require('libp2p-interfaces/src/connection')
const { AbortError } = require('libp2p-interfaces/src/transport/errors')
@@ -40,21 +42,28 @@ describe('Dialing (direct, TCP)', () => {
let peerStore
let remoteAddr
before(async () => {
const [remotePeerId] = await Promise.all([
PeerId.createFromJSON(Peers[0])
beforeEach(async () => {
const [localPeerId, remotePeerId] = await Promise.all([
PeerId.createFromJSON(Peers[0]),
PeerId.createFromJSON(Peers[1])
])
peerStore = new PeerStore({ peerId: remotePeerId })
remoteTM = new TransportManager({
libp2p: {
addressManager: new AddressManager({ listen: [listenAddr] })
addressManager: new AddressManager({ listen: [listenAddr] }),
peerId: remotePeerId,
peerStore
},
upgrader: mockUpgrader
})
remoteTM.add(Transport.prototype[Symbol.toStringTag], Transport)
peerStore = new PeerStore({ peerId: remotePeerId })
localTM = new TransportManager({
libp2p: {},
libp2p: {
peerId: localPeerId,
peerStore: new PeerStore({ peerId: localPeerId })
},
upgrader: mockUpgrader
})
localTM.add(Transport.prototype[Symbol.toStringTag], Transport)
@@ -64,7 +73,7 @@ describe('Dialing (direct, TCP)', () => {
remoteAddr = remoteTM.getAddrs()[0].encapsulate(`/p2p/${remotePeerId.toB58String()}`)
})
after(() => remoteTM.close())
afterEach(() => remoteTM.close())
afterEach(() => {
sinon.restore()
@@ -110,7 +119,7 @@ describe('Dialing (direct, TCP)', () => {
peerStore
})
peerStore.addressBook.set(peerId, [remoteAddr])
peerStore.addressBook.set(peerId, remoteTM.getAddrs())
const connection = await dialer.connectToPeer(peerId)
expect(connection).to.exist()
@@ -156,9 +165,9 @@ describe('Dialing (direct, TCP)', () => {
it('should dial to the max concurrency', async () => {
const addrs = [
'/ip4/0.0.0.0/tcp/8000',
'/ip4/0.0.0.0/tcp/8001',
'/ip4/0.0.0.0/tcp/8002'
multiaddr('/ip4/0.0.0.0/tcp/8000'),
multiaddr('/ip4/0.0.0.0/tcp/8001'),
multiaddr('/ip4/0.0.0.0/tcp/8002')
]
const dialer = new Dialer({
transportManager: localTM,
@@ -299,6 +308,50 @@ describe('Dialing (direct, TCP)', () => {
expect(libp2p.dialer.connectToPeer.callCount).to.equal(1)
})
it('should close all streams when the connection closes', async () => {
libp2p = new Libp2p({
peerId,
modules: {
transport: [Transport],
streamMuxer: [Muxer],
connEncryption: [Crypto]
}
})
// register some stream handlers to simulate several protocols
libp2p.handle('/stream-count/1', ({ stream }) => pipe(stream, stream))
libp2p.handle('/stream-count/2', ({ stream }) => pipe(stream, stream))
remoteLibp2p.handle('/stream-count/3', ({ stream }) => pipe(stream, stream))
remoteLibp2p.handle('/stream-count/4', ({ stream }) => pipe(stream, stream))
libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs)
const connection = await libp2p.dial(remotePeerId)
// Create local to remote streams
const { stream } = await connection.newStream('/echo/1.0.0')
await connection.newStream('/stream-count/3')
await libp2p.dialProtocol(remoteLibp2p.peerId, '/stream-count/4')
// Partially write to the echo stream
const source = pushable()
stream.sink(source)
source.push('hello')
// Create remote to local streams
await remoteLibp2p.dialProtocol(libp2p.peerId, '/stream-count/1')
await remoteLibp2p.dialProtocol(libp2p.peerId, '/stream-count/2')
// Verify stream count
const remoteConn = remoteLibp2p.connectionManager.get(libp2p.peerId)
expect(connection.streams).to.have.length(5)
expect(remoteConn.streams).to.have.length(5)
// Close the connection and verify all streams have been closed
await connection.close()
await pWaitFor(() => connection.streams.length === 0)
await pWaitFor(() => remoteConn.streams.length === 0)
})
it('should be able to use hangup to close connections', async () => {
libp2p = new Libp2p({
peerId,

View File

@@ -263,7 +263,6 @@ describe('Dialing (direct, WebSockets)', () => {
describe('libp2p.dialer', () => {
let libp2p
let remoteLibp2p
afterEach(async () => {
sinon.restore()
@@ -271,10 +270,6 @@ describe('Dialing (direct, WebSockets)', () => {
libp2p = null
})
after(async () => {
remoteLibp2p && await remoteLibp2p.stop()
})
it('should create a dialer', () => {
libp2p = new Libp2p({
peerId,
@@ -354,7 +349,6 @@ describe('Dialing (direct, WebSockets)', () => {
const connection = await libp2p.dial(remoteAddr)
expect(connection).to.exist()
sinon.spy(libp2p.peerStore.addressBook, 'consumePeerRecord')
sinon.spy(libp2p.peerStore.protoBook, 'set')
// Wait for onConnection to be called
@@ -363,8 +357,6 @@ describe('Dialing (direct, WebSockets)', () => {
expect(libp2p.identifyService.identify.callCount).to.equal(1)
await libp2p.identifyService.identify.firstCall.returnValue
// Self + New peer
expect(libp2p.peerStore.addressBook.consumePeerRecord.callCount).to.equal(2)
expect(libp2p.peerStore.protoBook.set.callCount).to.equal(1)
})

View File

@@ -0,0 +1,176 @@
'use strict'
/* eslint-env mocha */
const { expect } = require('aegir/utils/chai')
const sinon = require('sinon')
const multiaddr = require('multiaddr')
const { Resolver } = require('multiaddr/src/resolvers/dns')
const { codes: ErrorCodes } = require('../../src/errors')
const peerUtils = require('../utils/creators/peer')
const baseOptions = require('../utils/base-options.browser')
const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser')
const relayAddr = MULTIADDRS_WEBSOCKETS[0]
const getDnsaddrStub = (peerId) => [
[`dnsaddr=/dnsaddr/ams-1.bootstrap.libp2p.io/p2p/${peerId}`],
[`dnsaddr=/dnsaddr/ams-2.bootstrap.libp2p.io/p2p/${peerId}`],
[`dnsaddr=/dnsaddr/lon-1.bootstrap.libp2p.io/p2p/${peerId}`],
[`dnsaddr=/dnsaddr/nrt-1.bootstrap.libp2p.io/p2p/${peerId}`],
[`dnsaddr=/dnsaddr/nyc-1.bootstrap.libp2p.io/p2p/${peerId}`],
[`dnsaddr=/dnsaddr/sfo-2.bootstrap.libp2p.io/p2p/${peerId}`]
]
const relayedAddr = (peerId) => `${relayAddr}/p2p-circuit/p2p/${peerId}`
const getDnsRelayedAddrStub = (peerId) => [
[`dnsaddr=${relayedAddr(peerId)}`]
]
describe('Dialing (resolvable addresses)', () => {
let libp2p, remoteLibp2p
beforeEach(async () => {
[libp2p, remoteLibp2p] = await peerUtils.createPeer({
number: 2,
config: {
modules: baseOptions.modules,
addresses: {
listen: [multiaddr(`${relayAddr}/p2p-circuit`)]
},
config: {
peerDiscovery: {
autoDial: false
}
}
},
started: true,
populateAddressBooks: false
})
})
afterEach(async () => {
sinon.restore()
await Promise.all([libp2p, remoteLibp2p].map(n => n.stop()))
})
it('resolves dnsaddr to ws local address', async () => {
const remoteId = remoteLibp2p.peerId.toB58String()
const dialAddr = multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId}`)
const relayedAddrFetched = multiaddr(relayedAddr(remoteId))
// Transport spy
const transport = libp2p.transportManager._transports.get('Circuit')
sinon.spy(transport, 'dial')
// Resolver stub
const stub = sinon.stub(Resolver.prototype, 'resolveTxt')
stub.onCall(0).returns(Promise.resolve(getDnsRelayedAddrStub(remoteId)))
// Dial with address resolve
const connection = await libp2p.dial(dialAddr)
expect(connection).to.exist()
expect(connection.remoteAddr.equals(relayedAddrFetched))
const dialArgs = transport.dial.firstCall.args
expect(dialArgs[0].equals(relayedAddrFetched)).to.eql(true)
})
it('resolves a dnsaddr recursively', async () => {
const remoteId = remoteLibp2p.peerId.toB58String()
const dialAddr = multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId}`)
const relayedAddrFetched = multiaddr(relayedAddr(remoteId))
// Transport spy
const transport = libp2p.transportManager._transports.get('Circuit')
sinon.spy(transport, 'dial')
// Resolver stub
const stub = sinon.stub(Resolver.prototype, 'resolveTxt')
let firstCall = false
stub.callsFake(() => {
if (!firstCall) {
firstCall = true
// Return an array of dnsaddr
return Promise.resolve(getDnsaddrStub(remoteId))
}
return Promise.resolve(getDnsRelayedAddrStub(remoteId))
})
// Dial with address resolve
const connection = await libp2p.dial(dialAddr)
expect(connection).to.exist()
expect(connection.remoteAddr.equals(relayedAddrFetched))
const dialArgs = transport.dial.firstCall.args
expect(dialArgs[0].equals(relayedAddrFetched)).to.eql(true)
})
// TODO: Temporary solution does not resolve dns4/dns6
// Resolver just returns the received multiaddrs
it('stops recursive resolve if finds dns4/dns6 and dials it', async () => {
const remoteId = remoteLibp2p.peerId.toB58String()
const dialAddr = multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId}`)
// Stub resolver
const dnsMa = multiaddr(`/dns4/ams-1.remote.libp2p.io/tcp/443/wss/p2p/${remoteId}`)
const stubResolve = sinon.stub(Resolver.prototype, 'resolveTxt')
stubResolve.returns(Promise.resolve([
[`dnsaddr=${dnsMa}`]
]))
// Stub transport
const transport = libp2p.transportManager._transports.get('WebSockets')
const stubTransport = sinon.stub(transport, 'dial')
stubTransport.callsFake((multiaddr) => {
expect(multiaddr.equals(dnsMa)).to.eql(true)
})
await libp2p.dial(dialAddr)
})
it('resolves a dnsaddr recursively not failing if one address fails to resolve', async () => {
const remoteId = remoteLibp2p.peerId.toB58String()
const dialAddr = multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId}`)
const relayedAddrFetched = multiaddr(relayedAddr(remoteId))
// Transport spy
const transport = libp2p.transportManager._transports.get('Circuit')
sinon.spy(transport, 'dial')
// Resolver stub
const stub = sinon.stub(Resolver.prototype, 'resolveTxt')
stub.onCall(0).callsFake(() => Promise.resolve(getDnsaddrStub(remoteId)))
stub.onCall(1).callsFake(() => Promise.reject(new Error()))
stub.callsFake(() => Promise.resolve(getDnsRelayedAddrStub(remoteId)))
// Dial with address resolve
const connection = await libp2p.dial(dialAddr)
expect(connection).to.exist()
expect(connection.remoteAddr.equals(relayedAddrFetched))
const dialArgs = transport.dial.firstCall.args
expect(dialArgs[0].equals(relayedAddrFetched)).to.eql(true)
})
it('fails to dial if resolve fails and there are no addresses to dial', async () => {
const remoteId = remoteLibp2p.peerId.toB58String()
const dialAddr = multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId}`)
// Stub resolver
const stubResolve = sinon.stub(Resolver.prototype, 'resolveTxt')
stubResolve.returns(Promise.reject(new Error()))
// Stub transport
const transport = libp2p.transportManager._transports.get('WebSockets')
const spy = sinon.spy(transport, 'dial')
await expect(libp2p.dial(dialAddr))
.to.eventually.be.rejectedWith(Error)
.and.to.have.nested.property('.code', ErrorCodes.ERR_NO_VALID_ADDRESSES)
expect(spy.callCount).to.eql(0)
})
})

View File

@@ -3,5 +3,5 @@
const multiaddr = require('multiaddr')
module.exports.MULTIADDRS_WEBSOCKETS = [
multiaddr('/ip4/127.0.0.1/tcp/15001/ws/p2p/QmckxVrJw1Yo8LqvmDJNUmdAsKtSbiKWmrXJFyKmUraBoN')
multiaddr('/ip4/127.0.0.1/tcp/15001/ws/p2p/12D3KooWHFKTMzwerBtsVmtz4ZZEQy2heafxzWw6wNn5PPYkBxJ5')
]

View File

@@ -1,27 +1,27 @@
'use strict'
module.exports = [{
id: 'QmNMMAqSxPetRS1cVMmutW5BCN1qQQyEr4u98kUvZjcfEw',
privKey: 'CAASpQkwggShAgEAAoIBAQDPek2aeHMa0blL42RTKd6xgtkk4Zkldvq4LHxzcag5uXepiQzWANEUvoD3KcUTmMRmx14PvsxdLCNst7S2JSa0R2n5wSRs14zGy6892lx4H4tLBD1KSpQlJ6vabYM1CJhIQRG90BtzDPrJ/X1iJ2HA0PPDz0Mflam2QUMDDrU0IuV2m7gSCJ5r4EmMs3U0xnH/1gShkVx4ir0WUdoWf5KQUJOmLn1clTRHYPv4KL9A/E38+imNAXfkH3c2T7DrCcYRkZSpK+WecjMsH1dCX15hhhggNqfp3iulO1tGPxHjm7PDGTPUjpCWKpD5e50sLqsUwexac1ja6ktMfszIR+FPAgMBAAECggEAB2H2uPRoRCAKU+T3gO4QeoiJaYKNjIO7UCplE0aMEeHDnEjAKC1HQ1G0DRdzZ8sb0fxuIGlNpFMZv5iZ2ZFg2zFfV//DaAwTek9tIOpQOAYHUtgHxkj5FIlg2BjlflGb+ZY3J2XsVB+2HNHkUEXOeKn2wpTxcoJE07NmywkO8Zfr1OL5oPxOPlRN1gI4ffYH2LbfaQVtRhwONR2+fs5ISfubk5iKso6BX4moMYkxubYwZbpucvKKi/rIjUA3SK86wdCUnno1KbDfdXSgCiUlvxt/IbRFXFURQoTV6BOi3sP5crBLw8OiVubMr9/8WE6KzJ0R7hPd5+eeWvYiYnWj4QKBgQD6jRlAFo/MgPO5NZ/HRAk6LUG+fdEWexA+GGV7CwJI61W/Dpbn9ZswPDhRJKo3rquyDFVZPdd7+RlXYg1wpmp1k54z++L1srsgj72vlg4I8wkZ4YLBg0+zVgHlQ0kxnp16DvQdOgiRFvMUUMEgetsoIx1CQWTd67hTExGsW+WAZQKBgQDT/WaHWvwyq9oaZ8G7F/tfeuXvNTk3HIJdfbWGgRXB7lJ7Gf6FsX4x7PeERfL5a67JLV6JdiLLVuYC2CBhipqLqC2DB962aKMvxobQpSljBBZvZyqP1IGPoKskrSo+2mqpYkeCLbDMuJ1nujgMP7gqVjabs2zj6ACKmmpYH/oNowJ/T0ZVtvFsjkg+1VsiMupUARRQuPUWMwa9HOibM1NIZcoQV2NGXB5Z++kR6JqxQO0DZlKArrviclderUdY+UuuY4VRiSEprpPeoW7ZlbTku/Ap8QZpWNEzZorQDro7bnfBW91fX9/81ets/gCPGrfEn+58U3pdb9oleCOQc/ifpQKBgBTYGbi9bYbd9vgZs6bd2M2um+VFanbMytS+g5bSIn2LHXkVOT2UEkB+eGf9KML1n54QY/dIMmukA8HL1oNAyalpw+/aWj+9Ui5kauUhGEywHjSeBEVYM9UXizxz+m9rsoktLLLUI0o97NxCJzitG0Kub3gn0FEogsUeIc7AdinZAoGBANnM1vcteSQDs7x94TDEnvvqwSkA2UWyLidD2jXgE0PG4V6tTkK//QPBmC9eq6TIqXkzYlsErSw4XeKO91knFofmdBzzVh/ddgx/NufJV4tXF+a2iTpqYBUJiz9wpIKgf43/Ob+P1EA99GAhSdxz1ess9O2aTqf3ANzn6v6g62Pv',
pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPek2aeHMa0blL42RTKd6xgtkk4Zkldvq4LHxzcag5uXepiQzWANEUvoD3KcUTmMRmx14PvsxdLCNst7S2JSa0R2n5wSRs14zGy6892lx4H4tLBD1KSpQlJ6vabYM1CJhIQRG90BtzDPrJ/X1iJ2HA0PPDz0Mflam2QUMDDrU0IuV2m7gSCJ5r4EmMs3U0xnH/1gShkVx4ir0WUdoWf5KQUJOmLn1clTRHYPv4KL9A/E38+imNAXfkH3c2T7DrCcYRkZSpK+WecjMsH1dCX15hhhggNqfp3iulO1tGPxHjm7PDGTPUjpCWKpD5e50sLqsUwexac1ja6ktMfszIR+FPAgMBAAE='
id: '12D3KooWNvSZnPi3RrhrTwEY4LuuBeB6K6facKUCJcyWG1aoDd2p',
privKey: 'CAESYHyCgD+3HtEHm6kzPO6fuwP+BAr/PxfJKlvAOWhc/IqAwrZjCNn0jz93sSl81cP6R6x/g+iVYmR5Wxmn4ZtzJFnCtmMI2fSPP3exKXzVw/pHrH+D6JViZHlbGafhm3MkWQ==',
pubKey: 'CAESIMK2YwjZ9I8/d7EpfNXD+kesf4PolWJkeVsZp+GbcyRZ'
}, {
id: 'QmW8rAgaaA6sRydK1k6vonShQME47aDxaFidbtMevWs73t',
privKey: 'CAASpwkwggSjAgEAAoIBAQCTU3gVDv3SRXLOsFln9GEf1nJ/uCEDhOG10eC0H9l9IPpVxjuPT1ep+ykFUdvefq3D3q+W3hbmiHm81o8dYv26RxZIEioToUWp7Ec5M2B/niYoE93za9/ZDwJdl7eh2hNKwAdxTmdbXUPjkIU4vLyHKRFbJIn9X8w9djldz8hoUvC1BK4L1XrT6F2l0ruJXErH2ZwI1youfSzo87TdXIoFKdrQLuW6hOtDCGKTiS+ab/DkMODc6zl8N47Oczv7vjzoWOJMUJs1Pg0ZsD1zmISY38P0y/QyEhatZn0B8BmSWxlLQuukatzOepQI6k+HtfyAAjn4UEqnMaXTP1uwLldVAgMBAAECggEAHq2f8MqpYjLiAFZKl9IUs3uFZkEiZsgx9BmbMAb91Aec+WWJG4OLHrNVTG1KWp+IcaQablEa9bBvoToQnS7y5OpOon1d066egg7Ymfmv24NEMM5KRpktCNcOSA0CySpPIB6yrg6EiUr3ixiaFUGABKkxmwgVz/Q15IqM0ZMmCUsC174PMAz1COFZxD0ZX0zgHblOJQW3dc0X3XSzhht8vU02SMoVObQHQfeXEHv3K/RiVj/Ax0bTc5JVkT8dm8xksTtsFCNOzRBqFS6MYqX6U/u0Onz3Jm5Jt7fLWb5n97gZR4SleyGrqxYNb46d9X7mP0ie7E6bzFW0DsWBIeAqVQKBgQDW0We2L1n44yOvJaMs3evpj0nps13jWidt2I3RlZXjWzWHiYQfvhWUWqps/xZBnAYgnN/38xbKzHZeRNhrqOo+VB0WK1IYl0lZVE4l6TNKCsLsUfQzsb1pePkd1eRZA+TSqsi+I/IOQlQU7HA0bMrah/5FYyUBP0jYvCOvYTlZuwKBgQCvkcVRydVlzjUgv7lY5lYvT8IHV5iYO4Qkk2q6Wjv9VUKAJZauurMdiy05PboWfs5kbETdwFybXMBcknIvZO4ihxmwL8mcoNwDVZHI4bXapIKMTCyHgUKvJ9SeTcKGC7ZuQJ8mslRmYox/HloTOXEJgQgPRxXcwa3amzvdZI+6LwKBgQCLsnQqgxKUi0m6bdR2qf7vzTH4258z6X34rjpT0F5AEyF1edVFOz0XU/q+lQhpNEi7zqjLuvbYfSyA026WXKuwSsz7jMJ/oWqev/duKgAjp2npesY/E9gkjfobD+zGgoS9BzkyhXe1FCdP0A6L2S/1+zg88WOwMvJxl6/xLl24XwKBgCm60xSajX8yIQyUpWBM9yUtpueJ2Xotgz4ST+bVNbcEAddll8gWFiaqgug9FLLuFu5lkYTHiPtgc1RNdphvO+62/9MRuLDixwh/2TPO+iNqwKDKJjda8Nei9vVddCPaOtU/xNQ0xLzFJbG9LBmvqH9izOCcu8SJwGHaTcNUeJj/AoGADCJ26cY30c13F/8awAAmFYpZWCuTP5ppTsRmjd63ixlrqgkeLGpJ7kYb5fXkcTycRGYgP0e1kssBGcmE7DuG955fx3ZJESX3GQZ+XfMHvYGONwF1EiK1f0p6+GReC2VlQ7PIkoD9o0hojM6SnWvv9EXNjCPALEbfPFFvcniKVsE=',
pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCTU3gVDv3SRXLOsFln9GEf1nJ/uCEDhOG10eC0H9l9IPpVxjuPT1ep+ykFUdvefq3D3q+W3hbmiHm81o8dYv26RxZIEioToUWp7Ec5M2B/niYoE93za9/ZDwJdl7eh2hNKwAdxTmdbXUPjkIU4vLyHKRFbJIn9X8w9djldz8hoUvC1BK4L1XrT6F2l0ruJXErH2ZwI1youfSzo87TdXIoFKdrQLuW6hOtDCGKTiS+ab/DkMODc6zl8N47Oczv7vjzoWOJMUJs1Pg0ZsD1zmISY38P0y/QyEhatZn0B8BmSWxlLQuukatzOepQI6k+HtfyAAjn4UEqnMaXTP1uwLldVAgMBAAE='
id: '12D3KooWLV3w42LqUb9MWE7oTzG7vwaFjPw9GvDqmsuDif5chTn9',
privKey: 'CAESYI44p8HiCHtCBhuUcetU9XdIEtWvon15a5ZLsfyssSj9nn3mt4oZI0t6wXTHOvIA0GSFWrYkdKp1338oFIambdKefea3ihkjS3rBdMc68gDQZIVatiR0qnXffygUhqZt0g==',
pubKey: 'CAESIJ595reKGSNLesF0xzryANBkhVq2JHSqdd9/KBSGpm3S'
}, {
id: 'QmZqCdSzgpsmB3Qweb9s4fojAoqELWzqku21UVrqtVSKi4',
privKey: 'CAASpgkwggSiAgEAAoIBAQCdbSEsTmw7lp5HagRcx57DaLiSUEkh4iBcKc7Y+jHICEIA8NIVi9FlfGEZj9G21FpiTR4Cy+BLVEuf8Nm90bym4iV+cSumeS21fvD8xGTEbeKGljs6OYHy3M45JhWF85gqHQJOqZufI2NRDuRgMZEO2+qGEXmSlv9mMXba/+9ecze8nSpB7bG2Z2pnKDeYwhF9Cz+ElMyn7TBWDjJERGVgFbTpdM3rBnbhB/TGpvs732QqZmIBlxnDb/Jn0l1gNZCgkEDcJ/0NDMBJTQ8vbvcdmaw3eaMPLkn1ix4wdu9QWCA0IBtuY1R7vSUtf4irnLJG7DnAw2GfM5QrF3xF1GLXAgMBAAECggEAQ1N0qHoxl5pmvqv8iaFlqLSUmx5y6GbI6CGJMQpvV9kQQU68yjItr3VuIXx8d/CBZyEMAK4oko7OeOyMcr3MLKLy3gyQWnXgsopDjhZ/8fH8uwps8g2+IZuFJrO+6LaxEPGvFu06fOiphPUVfn40R2KN/iBjGeox+AaXijmCqaV2vEdNJJPpMfz6VKZBDLTrbiqvo/3GN1U99PUqfPWpOWR29oAhh/Au6blSqvqTUPXB2+D/X6e1JXv31mxMPK68atDHSUjZWKB9lE4FMK1bkSKJRbyXmNIlbZ9V8X4/0r8/6T7JnW7ZT8ugRkquohmwgG7KkDXB1YsOCKXYUqzVYQKBgQDtnopFXWYl7XUyePJ/2MA5i7eoko9jmF44L31irqmHc5unNf6JlNBjlxTNx3WyfzhUzrn3c18psnGkqtow0tkBj5hmqn8/WaPbc5UA/5R1FNaNf8W5khn7MDm6KtYRPjN9djqTDiVHyC6ljONYd+5S+MqyKVWZ3t/xvG60sw85qwKBgQCpmpDtL+2JBwkfeUr3LyDcQxvbfzcv8lXj2otopWxWiLiZF1HzcqgAa2CIwu9kCGEt9Zr+9E4uINbe1To0b01/FhvR6xKO/ukceGA/mBB3vsKDcRmvpBUp+3SmnhY0nOk+ArQl4DhJ34k8pDM3EDPrixPf8SfVdU/8IM32lsdHhQKBgHLgpvCKCwxjFLnmBzcPzz8C8TOqR3BbBZIcQ34l+wflOGdKj1hsfaLoM8KYn6pAHzfBCd88A9Hg11hI0VuxVACRL5jS7NnvuGwsIOluppNEE8Ys86aXn7/0vLPoab3EWJhbRE48FIHzobmft3nZ4XpzlWs02JGfUp1IAC2UM9QpAoGAeWy3pZhSr2/iEC5+hUmwdQF2yEbj8+fDpkWo2VrVnX506uXPPkQwE1zM2Bz31t5I9OaJ+U5fSpcoPpDaAwBMs1fYwwlRWB8YNdHY1q6/23svN3uZsC4BGPV2JnO34iMUudilsRg+NGVdk5TbNejbwx7nM8Urh59djFzQGGMKeSECgYA0QMCARPpdMY50Mf2xQaCP7HfMJhESSPaBq9V3xY6ToEOEnXgAR5pNjnU85wnspHp+82r5XrKfEQlFxGpj2YA4DRRmn239sjDa29qP42UNAFg1+C3OvXTht1d5oOabaGhU0udwKmkEKUbb0bG5xPQJ5qeSJ5T1gLzLk3SIP0GlSw==',
pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCdbSEsTmw7lp5HagRcx57DaLiSUEkh4iBcKc7Y+jHICEIA8NIVi9FlfGEZj9G21FpiTR4Cy+BLVEuf8Nm90bym4iV+cSumeS21fvD8xGTEbeKGljs6OYHy3M45JhWF85gqHQJOqZufI2NRDuRgMZEO2+qGEXmSlv9mMXba/+9ecze8nSpB7bG2Z2pnKDeYwhF9Cz+ElMyn7TBWDjJERGVgFbTpdM3rBnbhB/TGpvs732QqZmIBlxnDb/Jn0l1gNZCgkEDcJ/0NDMBJTQ8vbvcdmaw3eaMPLkn1ix4wdu9QWCA0IBtuY1R7vSUtf4irnLJG7DnAw2GfM5QrF3xF1GLXAgMBAAE='
id: '12D3KooWDRHe5x3tEQfZi4yirsdhA3zgqhE8awxAjET7zVF23JHB',
privKey: 'CAESYP+GrxgDqKnx79W5l4sgpCEYvNF9bBlCSVu3McENPluhNYVEwxo5KboVuOPnYO6ZOeeTmglqc2vcN8pldRF8lq41hUTDGjkpuhW44+dg7pk555OaCWpza9w3ymV1EXyWrg==',
pubKey: 'CAESIDWFRMMaOSm6Fbjj52DumTnnk5oJanNr3DfKZXURfJau'
}, {
id: 'QmR5VwgsL7jyfZHAGyp66tguVrQhCRQuRc3NokocsCZ3fA',
privKey: 'CAASpwkwggSjAgEAAoIBAQCGXYU+uc2nn1zuJhfdFOl34upztnrD1gpHu58ousgHdGlGgYgbqLBAvIAauXdEL0+e30HofjA634SQxE+9nV+0FQBam1DDzHQlXsuwHV+2SKvSDkk4bVllMFpu2SJtts6VH+OXC/2ANJOm+eTALykQPYXgLIBxrhp/eD+Jz5r6wW2nq3k6OmYyK/4pgGzFjo5UyX+fa/171AJ68UPboFpDy6BZCcUjS0ondxPvD7cv5jMNqqMKIB/7rpi8n+Q3oeccRqVL56wH+FE3/QLjwYHwY6ILNRyvNXRqHjwBEXB2R5moXN0AFUWTw9rt3KhFiEjR1U81BTw5/xS7W2Iu0FgZAgMBAAECggEAS64HK8JZfE09eYGJNWPe8ECmD1C7quw21BpwVe+GVPSTizvQHswPohbKDMNj0srXDMPxCnNw1OgqcaOwyjsGuZaOoXoTroTM8nOHRIX27+PUqzaStS6aCG2IsiCozKUHjGTuupftS7XRaF4eIsUtWtFcQ1ytZ9pJYHypRQTi5NMSrTze5ThjnWxtHilK7gnBXik+aR0mYEVfSn13czQEC4rMOs+b9RAc/iibDNoLopfIdvmCCvfxzmySnR7Cu1iSUAONkir7PB+2Mt/qRFCH6P+jMamtCgQ8AmifXgVmDUlun+4MnKg3KrPd6ZjOEKhVe9mCHtGozk65RDREShfDdQKBgQDi+x2MuRa9peEMOHnOyXTS+v+MFcfmG0InsO08rFNBKZChLB+c9UHBdIvexpfBHigSyERfuDye4z6lxi8ZnierWMYJP30nxmrnxwTGTk1MQquhfs1A0kpmDnPsjlOS/drEIEIssNx2WbfJ7YtMxLWBtp+BJzGpQmr0LKC+NHRSrwKBgQCXiy2kJESIUkIs2ihV55hhT6/bZo1B1O5DPA2nkjOBXqXF6fvijzMDX82JjLd07lQZlI0n1Q/Hw0p4iYi9YVd2bLkLXF5UIb2qOeHj76enVFOrPHUSkC9Y2g/0Xs+60Ths2xRd8RrrfQU3kl5iVpBywkCIrb2M5+wRnNTk1W3TtwKBgQCvplyrteAfSurpJhs9JzE8w/hWU9SqAZYkWQp91W1oE95Um2yrbjBAoQxMjaqKS+f/APPIjy56Vqj4aHGyhW11b/Fw3qzfxvCcBKtxOs8eoMlo5FO6QgJJEA4tlcafDcvp0nzjUMqK28safLU7503+33B35fjMXxWdd5u9FaKfCQKBgC4W6j6tuRosymuRvgrCcRnHfpify/5loEFallyMnpWOD6Tt0OnK25z/GifnYDRz96gAAh5HMpFy18dpLOlMHamqz2yhHx8/U8vd5tHIJZlCkF/X91M5/uxrBccwvsT2tM6Got8fYSyVzWxlW8dUxIHiinYHQUsFjkqdBDLEpq5pAoGASoTw5RBEWFM0GuAZdXsyNyxU+4S+grkTS7WdW/Ymkukh+bJZbnvF9a6MkSehqXnknthmufonds2AFNS//63gixENsoOhzT5+2cdfc6tJECvJ9xXVXkf85AoQ6T/RrXF0W4m9yQyCngNJUrKUOIH3oDIfdZITlYzOC3u1ojj7VuQ=',
pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCGXYU+uc2nn1zuJhfdFOl34upztnrD1gpHu58ousgHdGlGgYgbqLBAvIAauXdEL0+e30HofjA634SQxE+9nV+0FQBam1DDzHQlXsuwHV+2SKvSDkk4bVllMFpu2SJtts6VH+OXC/2ANJOm+eTALykQPYXgLIBxrhp/eD+Jz5r6wW2nq3k6OmYyK/4pgGzFjo5UyX+fa/171AJ68UPboFpDy6BZCcUjS0ondxPvD7cv5jMNqqMKIB/7rpi8n+Q3oeccRqVL56wH+FE3/QLjwYHwY6ILNRyvNXRqHjwBEXB2R5moXN0AFUWTw9rt3KhFiEjR1U81BTw5/xS7W2Iu0FgZAgMBAAE='
id: '12D3KooWQJMnsoT7js35ZgkboxzUjXpVhfvG8cMqZnBJTP4XPuhU',
privKey: 'CAESYL1Fwm/+layh15V1ITWkK9tEQLuGeJFi16VkNDUU+GFs1y90DFs9vlkRziuJFZ/QtEIlYZWjFTsNRJxFA/etwCvXL3QMWz2+WRHOK4kVn9C0QiVhlaMVOw1EnEUD963AKw==',
pubKey: 'CAESINcvdAxbPb5ZEc4riRWf0LRCJWGVoxU7DUScRQP3rcAr'
}, {
id: 'QmScLDqRg7H6ipCYxm9fVk152UWavQFKscTdoT4YNHxgqp',
privKey: 'CAASpwkwggSjAgEAAoIBAQCWEHaTZ6LBLFP5OPrUqjDM/cF4b2zrfh1Zm3kd02ZtgQB3iYtZqRPJT5ctT3A7WdVF/7dCxPGOCkJlLekTx4Y4gD8JtjA+EfN9fR/2RBKbti2N3CD4vkGp9ss4hbBFcXIhl8zuD/ELHutbV6b8b4QXJGnxfp/B+1kNPnyd7SJznS0QyvI8OLI1nAkVKdYLDRW8kPKeHyx1xhdNDuTQVTFyAjRGQ4e3UYFB7bYIHW3E6kCtCoJDlj+JPC02Yt1LHzIzZVLvPvNFnYY2mag6OiGFuh/oMBIqvnPc1zRZ3eLUqeGZjQVaoR0kdgZUKz7Q2TBeNldxK/s6XO0DnkQTlelNAgMBAAECggEAdmt1dyswR2p4tdIeNpY7Pnj9JNIhTNDPznefI0dArCdBvBMhkVaYk6MoNIxcj6l7YOrDroAF8sXr0TZimMY6B/pERKCt/z1hPWTxRQBBAvnHhwvwRPq2jK6BfhAZoyM8IoBNKowP9mum5QUNdGV4Al8s73KyFX0IsCfgZSvNpRdlt+DzPh+hu/CyoZaMpRchJc1UmK8Fyk3KfO+m0DZNfHP5P08lXNfM6MZLgTJVVgERHyG+vBOzTd2RElMe19nVCzHwb3dPPRZSQ7Fnz3rA+GeLqsM2Zi4HNhfbD1OcD9C4wDj5tYL6hWTkdz4IlfVcjCeUHxgIOhdDV2K+OwbuAQKBgQD0FjUZ09UW2FQ/fitbvIB5f1SkXWPxTF9l6mAeuXhoGv2EtQUO4vq/PK6N08RjrZdWQy6UsqHgffi7lVQ8o3hvCKdbtf4sP+cM92OrY0WZV89os79ndj4tyvmnP8WojwRjt/2XEfgdoWcgWxW9DiYINTOQVimZX+X/3on4s8hEgQKBgQCdY3kOMbyQeLTRkqHXjVTY4ddO+v4S4wOUa1l4rTqAbq1W3JYWwoDQgFuIu3limIHmjnSJpCD4EioXFsM7p6csenoc20sHxsaHnJ6Mn5Te41UYmY9EW0otkQ0C3KbXM0hwQkjyplnEmZawGKmjEHW8DJ3vRYTv9TUCgYKxDHgOzQKBgB4A/NYH7BG61eBYKgxEx6YnuMfbkwV+Vdu5S8d7FQn3B2LgvZZu4FPRqcNVXLbEB+5ao8czjiKCWaj1Wj15+rvrXGcxn+Tglg5J+r5+nXeUC7LbJZQaPNp0MOwWMr3dlrSLUWjYlJ9Pz9VyXOG4c4Rexc/gR4zK9QLW4C7qKpwBAoGAZzyUb0cYlPtYQA+asTU3bnvVKy1f8yuNcZFowst+EDiI4u0WVh+HNzy6zdmLKa03p+/RaWeLaK0hhrubnEnAUmCUMNF3ScaM+u804LDcicc8TkKLwx7ObU0z56isl4RAA8K27tNHFrpYKXJD834cfBkaj5ReOrfw6Y/iFhhDuBECgYEA8gbC76uz7LSHhW30DSRTcqOzTyoe2oYKQaxuxYNp7vSSOkcdRen+mrdflDvud2q/zN2QdL4pgqdldHlR35M/lJ0f0B6zp74jlzbO9700wzsOqreezGc5eWiroDL100U9uIZ50BKb8CKtixIHpinUSPIUcVDkSAZ2y7mbfCxQwqQ=',
pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCWEHaTZ6LBLFP5OPrUqjDM/cF4b2zrfh1Zm3kd02ZtgQB3iYtZqRPJT5ctT3A7WdVF/7dCxPGOCkJlLekTx4Y4gD8JtjA+EfN9fR/2RBKbti2N3CD4vkGp9ss4hbBFcXIhl8zuD/ELHutbV6b8b4QXJGnxfp/B+1kNPnyd7SJznS0QyvI8OLI1nAkVKdYLDRW8kPKeHyx1xhdNDuTQVTFyAjRGQ4e3UYFB7bYIHW3E6kCtCoJDlj+JPC02Yt1LHzIzZVLvPvNFnYY2mag6OiGFuh/oMBIqvnPc1zRZ3eLUqeGZjQVaoR0kdgZUKz7Q2TBeNldxK/s6XO0DnkQTlelNAgMBAAE='
id: '12D3KooWFYyvJysHGbbYiruVY8bgjKn7sYN9axgbnMxrWVkGXABF',
privKey: 'CAESYCtlyHA9SQ9F0yO6frmkrFFmboLCzGt8syr0ix8QkuTcVTVAp9JiBXb2xI1lzK6Fn2mRJUxtQIuuW+3V2mu3DZZVNUCn0mIFdvbEjWXMroWfaZElTG1Ai65b7dXaa7cNlg==',
pubKey: 'CAESIFU1QKfSYgV29sSNZcyuhZ9pkSVMbUCLrlvt1dprtw2W'
}, {
id: 'QmckxVrJw1Yo8LqvmDJNUmdAsKtSbiKWmrXJFyKmUraBoN',
privKey: 'CAASpwkwggSjAgEAAoIBAQC1/GFud/7xutux7qRfMj1sIdMRh99/chR6HqVj6LQqrgk4jil0mdN/LCk/tqPqmDtObHdmEhCoybzuhLbCKgUqryKDwO6yBJHSKWY9QqrKZtLJ37SgKwGjE3+NUD4r1dJHhtQrICFdOdSCBzs/v8gi+J+KZLHo7+Nms4z09ysy7qZh94Pd7cW4gmSMergqUeANLD9C0ERw1NXolswOW7Bi7UGr7yuBxejICLO3nkxe0OtpQBrYrqdCD9vs3t/HQZbPWVoiRj4VO7fxkAPKLl30HzcIfxj/ayg8NHcH59d08D+N2v5Sdh28gsiYKIPE9CXvuw//HUY2WVRY5fDC5JglAgMBAAECggEBAKb5aN/1w3pBqz/HqRMbQpYLNuD33M3PexBNPAy+P0iFpDo63bh5Rz+A4lvuFNmzUX70MFz7qENlzi6+n/zolxMB29YtWBUH8k904rTEjXXl//NviQgITZk106tx+4k2x5gPEm57LYGfBOdFAUzNhzDnE2LkXwRNzkS161f7zKwOEsaGWRscj6UvhO4MIFxjb32CVwt5eK4yOVqtyMs9u30K4Og+AZYTlhtm+bHg6ndCCBO6CQurCQ3jD6YOkT+L3MotKqt1kORpvzIB0ujZRf49Um8wlcjC5G9aexBeGriXaVdPF62zm7GA7RMsbQM/6aRbA1fEQXvJhHUNF9UFeaECgYEA8wCjKqQA7UQnHjRwTsktdwG6szfxd7z+5MTqHHTWhWzgcQLgdh5/dO/zanEoOThadMk5C1Bqjq96gH2xim8dg5XQofSVtV3Ui0dDa+XRB3E3fyY4D3RF5hHv85O0GcvQc6DIb+Ja1oOhvHowFB1C+CT3yEgwzX/EK9xpe+KtYAkCgYEAv7hCnj/DcZFU3fAfS+unBLuVoVJT/drxv66P686s7J8UM6tW+39yDBZ1IcwY9vHFepBvxY2fFfEeLI02QFM+lZXVhNGzFkP90agNHK01psGgrmIufl9zAo8WOKgkLgbYbSHzkkDeqyjEPU+B0QSsZOCE+qLCHSdsnTmo/TjQhj0CgYAz1+j3yfGgrS+jVBC53lXi0+2fGspbf2jqKdDArXSvFqFzuudki/EpY6AND4NDYfB6hguzjD6PnoSGMUrVfAtR7X6LbwEZpqEX7eZGeMt1yQPMDr1bHrVi9mS5FMQR1NfuM1lP9Xzn00GIUpE7WVrWUhzDEBPJY/7YVLf0hFH08QKBgDWBRQZJIVBmkNrHktRrVddaSq4U/d/Q5LrsCrpymYwH8WliHgpeTQPWmKXwAd+ZJdXIzYjCt202N4eTeVqGYOb6Q/anV2WVYBbM4avpIxoA28kPGY6nML+8EyWIt2ApBOmgGgvtEreNzwaVU9NzjHEyv6n7FlVwlT1jxCe3XWq5AoGASYPKQoPeDlW+NmRG7z9EJXJRPVtmLL40fmGgtju9QIjLnjuK8XaczjAWT+ySI93Whu+Eujf2Uj7Q+NfUjvAEzJgwzuOd3jlQvoALq11kuaxlNQTn7rx0A1QhBgUJE8AkvShPC9FEnA4j/CLJU0re9H/8VvyN6qE0Mho0+YbjpP8=',
pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1/GFud/7xutux7qRfMj1sIdMRh99/chR6HqVj6LQqrgk4jil0mdN/LCk/tqPqmDtObHdmEhCoybzuhLbCKgUqryKDwO6yBJHSKWY9QqrKZtLJ37SgKwGjE3+NUD4r1dJHhtQrICFdOdSCBzs/v8gi+J+KZLHo7+Nms4z09ysy7qZh94Pd7cW4gmSMergqUeANLD9C0ERw1NXolswOW7Bi7UGr7yuBxejICLO3nkxe0OtpQBrYrqdCD9vs3t/HQZbPWVoiRj4VO7fxkAPKLl30HzcIfxj/ayg8NHcH59d08D+N2v5Sdh28gsiYKIPE9CXvuw//HUY2WVRY5fDC5JglAgMBAAE='
id: '12D3KooWHFKTMzwerBtsVmtz4ZZEQy2heafxzWw6wNn5PPYkBxJ5',
privKey: 'CAESYLU/qFxBHsdsQa63w3MrP8VvxJDyAk7rB7gLnIN01CyibmZCtQc7a1gIEDOGb10maUltL8wJxEdmOw3Bpjo7xrpuZkK1BztrWAgQM4ZvXSZpSW0vzAnER2Y7DcGmOjvGug==',
pubKey: 'CAESIG5mQrUHO2tYCBAzhm9dJmlJbS/MCcRHZjsNwaY6O8a6'
}]

View File

@@ -8,7 +8,6 @@ const { expect } = chai
const sinon = require('sinon')
const { EventEmitter } = require('events')
const delay = require('delay')
const PeerId = require('peer-id')
const duplexPair = require('it-pair/duplex')
const multiaddr = require('multiaddr')
@@ -22,6 +21,7 @@ const Libp2p = require('../../src')
const Envelope = require('../../src/record/envelope')
const PeerStore = require('../../src/peer-store')
const baseOptions = require('../utils/base-options.browser')
const { updateSelfPeerRecord } = require('../../src/record/utils')
const pkg = require('../../package.json')
const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser')
@@ -29,18 +29,21 @@ const remoteAddr = MULTIADDRS_WEBSOCKETS[0]
const listenMaddrs = [multiaddr('/ip4/127.0.0.1/tcp/15002/ws')]
describe('Identify', () => {
let localPeer
let remotePeer
const protocols = new Map([
[multicodecs.IDENTIFY, () => {}],
[multicodecs.IDENTIFY_PUSH, () => {}]
])
let localPeer, localPeerStore
let remotePeer, remotePeerStore
const protocols = [multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH]
before(async () => {
[localPeer, remotePeer] = (await Promise.all([
PeerId.createFromJSON(Peers[0]),
PeerId.createFromJSON(Peers[1])
]))
localPeerStore = new PeerStore({ peerId: localPeer })
localPeerStore.protoBook.set(localPeer, protocols)
remotePeerStore = new PeerStore({ peerId: remotePeer })
remotePeerStore.protoBook.set(remotePeer, protocols)
})
afterEach(() => {
@@ -52,20 +55,19 @@ describe('Identify', () => {
libp2p: {
peerId: localPeer,
connectionManager: new EventEmitter(),
peerStore: new PeerStore({ peerId: localPeer }),
multiaddrs: listenMaddrs
},
protocols
peerStore: localPeerStore,
multiaddrs: listenMaddrs,
isStarted: () => true
}
})
const remoteIdentify = new IdentifyService({
libp2p: {
peerId: remotePeer,
connectionManager: new EventEmitter(),
peerStore: new PeerStore({ peerId: remotePeer }),
multiaddrs: listenMaddrs
},
protocols
peerStore: remotePeerStore,
multiaddrs: listenMaddrs,
isStarted: () => true
}
})
const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234')
@@ -78,6 +80,9 @@ describe('Identify', () => {
sinon.spy(localIdentify.peerStore.addressBook, 'consumePeerRecord')
sinon.spy(localIdentify.peerStore.protoBook, 'set')
// Transport Manager creates signed peer record
await updateSelfPeerRecord(remoteIdentify._libp2p)
// Run identify
await Promise.all([
localIdentify.identify(localConnectionMock),
@@ -105,20 +110,20 @@ describe('Identify', () => {
libp2p: {
peerId: localPeer,
connectionManager: new EventEmitter(),
peerStore: new PeerStore({ peerId: localPeer }),
multiaddrs: listenMaddrs
},
protocols
peerStore: localPeerStore,
multiaddrs: listenMaddrs,
isStarted: () => true
}
})
const remoteIdentify = new IdentifyService({
libp2p: {
peerId: remotePeer,
connectionManager: new EventEmitter(),
peerStore: new PeerStore({ peerId: remotePeer }),
multiaddrs: listenMaddrs
},
protocols
peerStore: remotePeerStore,
multiaddrs: listenMaddrs,
isStarted: () => true
}
})
const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234')
@@ -164,19 +169,17 @@ describe('Identify', () => {
libp2p: {
peerId: localPeer,
connectionManager: new EventEmitter(),
peerStore: new PeerStore({ peerId: localPeer }),
peerStore: localPeerStore,
multiaddrs: []
},
protocols
}
})
const remoteIdentify = new IdentifyService({
libp2p: {
peerId: remotePeer,
connectionManager: new EventEmitter(),
peerStore: new PeerStore({ peerId: remotePeer }),
peerStore: remotePeerStore,
multiaddrs: []
},
protocols
}
})
const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234')
@@ -203,33 +206,38 @@ describe('Identify', () => {
describe('push', () => {
it('should be able to push identify updates to another peer', async () => {
const storedProtocols = [multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH, '/echo/1.0.0']
const connectionManager = new EventEmitter()
connectionManager.getConnection = () => { }
const localPeerStore = new PeerStore({ peerId: localPeer })
localPeerStore.protoBook.set(localPeer, storedProtocols)
const localIdentify = new IdentifyService({
libp2p: {
peerId: localPeer,
connectionManager: new EventEmitter(),
peerStore: new PeerStore({ peerId: localPeer }),
multiaddrs: listenMaddrs
},
protocols: new Map([
[multicodecs.IDENTIFY],
[multicodecs.IDENTIFY_PUSH],
['/echo/1.0.0']
])
peerStore: localPeerStore,
multiaddrs: listenMaddrs,
isStarted: () => true
}
})
const remotePeerStore = new PeerStore({ peerId: remotePeer })
remotePeerStore.protoBook.set(remotePeer, storedProtocols)
const remoteIdentify = new IdentifyService({
libp2p: {
peerId: remotePeer,
connectionManager,
peerStore: new PeerStore({ peerId: remotePeer }),
multiaddrs: []
peerStore: remotePeerStore,
multiaddrs: [],
isStarted: () => true
}
})
// Setup peer protocols and multiaddrs
const localProtocols = new Set([multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH, '/echo/1.0.0'])
const localProtocols = new Set(storedProtocols)
const localConnectionMock = { newStream: () => { } }
const remoteConnectionMock = { remotePeer: localPeer }
@@ -239,6 +247,10 @@ describe('Identify', () => {
sinon.spy(remoteIdentify.peerStore.addressBook, 'consumePeerRecord')
sinon.spy(remoteIdentify.peerStore.protoBook, 'set')
// Transport Manager creates signed peer record
await updateSelfPeerRecord(localIdentify._libp2p)
await updateSelfPeerRecord(remoteIdentify._libp2p)
// Run identify
await Promise.all([
localIdentify.push([localConnectionMock]),
@@ -249,7 +261,7 @@ describe('Identify', () => {
})
])
expect(remoteIdentify.peerStore.addressBook.consumePeerRecord.callCount).to.equal(1)
expect(remoteIdentify.peerStore.addressBook.consumePeerRecord.callCount).to.equal(2)
expect(remoteIdentify.peerStore.protoBook.set.callCount).to.equal(1)
const addresses = localIdentify.peerStore.addressBook.get(localPeer)
@@ -264,33 +276,38 @@ describe('Identify', () => {
// LEGACY
it('should be able to push identify updates to another peer with no certified peer records support', async () => {
const storedProtocols = [multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH, '/echo/1.0.0']
const connectionManager = new EventEmitter()
connectionManager.getConnection = () => { }
const localPeerStore = new PeerStore({ peerId: localPeer })
localPeerStore.protoBook.set(localPeer, storedProtocols)
const localIdentify = new IdentifyService({
libp2p: {
peerId: localPeer,
connectionManager: new EventEmitter(),
peerStore: new PeerStore({ peerId: localPeer }),
multiaddrs: listenMaddrs
},
protocols: new Map([
[multicodecs.IDENTIFY],
[multicodecs.IDENTIFY_PUSH],
['/echo/1.0.0']
])
peerStore: localPeerStore,
multiaddrs: listenMaddrs,
isStarted: () => true
}
})
const remotePeerStore = new PeerStore({ peerId: remotePeer })
remotePeerStore.protoBook.set(remotePeer, storedProtocols)
const remoteIdentify = new IdentifyService({
libp2p: {
peerId: remotePeer,
connectionManager,
peerStore: new PeerStore({ peerId: remotePeer }),
multiaddrs: []
multiaddrs: [],
isStarted: () => true
}
})
// Setup peer protocols and multiaddrs
const localProtocols = new Set([multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH, '/echo/1.0.0'])
const localProtocols = new Set(storedProtocols)
const localConnectionMock = { newStream: () => {} }
const remoteConnectionMock = { remotePeer: localPeer }
@@ -359,12 +376,12 @@ describe('Identify', () => {
expect(connection).to.exist()
// Wait for peer store to be updated
// Dialer._createDialTarget (add), Identify (consume), Create self (consume)
await pWaitFor(() => peerStoreSpyConsumeRecord.callCount === 2 && peerStoreSpyAdd.callCount === 1)
// Dialer._createDialTarget (add), Identify (consume)
await pWaitFor(() => peerStoreSpyConsumeRecord.callCount === 1 && peerStoreSpyAdd.callCount === 1)
expect(libp2p.identifyService.identify.callCount).to.equal(1)
// The connection should have no open streams
expect(connection.streams).to.have.length(0)
await pWaitFor(() => connection.streams.length === 0)
await connection.close()
})
@@ -381,8 +398,6 @@ describe('Identify', () => {
const connection = await libp2p.dialer.connectToPeer(remoteAddr)
expect(connection).to.exist()
// Wait for nextTick to trigger the identify call
await delay(1)
// Wait for identify to finish
await libp2p.identifyService.identify.firstCall.returnValue
@@ -404,5 +419,39 @@ describe('Identify', () => {
// Verify the streams close
await pWaitFor(() => connection.streams.length === 0)
})
it('should push multiaddr updates to an already connected peer', async () => {
libp2p = new Libp2p({
...baseOptions,
peerId
})
await libp2p.start()
sinon.spy(libp2p.identifyService, 'identify')
sinon.spy(libp2p.identifyService, 'push')
const connection = await libp2p.dialer.connectToPeer(remoteAddr)
expect(connection).to.exist()
// Wait for identify to finish
await libp2p.identifyService.identify.firstCall.returnValue
sinon.stub(libp2p, 'isStarted').returns(true)
libp2p.peerStore.addressBook.add(libp2p.peerId, [multiaddr('/ip4/180.0.0.1/tcp/15001/ws')])
// Verify the remote peer is notified of change
expect(libp2p.identifyService.push.callCount).to.equal(1)
for (const call of libp2p.identifyService.push.getCalls()) {
const [connections] = call.args
expect(connections.length).to.equal(1)
expect(connections[0].remotePeer.toB58String()).to.equal(remoteAddr.getPeerId())
const results = await call.returnValue
expect(results.length).to.equal(1)
}
// Verify the streams close
await pWaitFor(() => connection.streams.length === 0)
})
})
})

View File

@@ -10,6 +10,7 @@ const sinon = require('sinon')
const pDefer = require('p-defer')
const mergeOptions = require('merge-options')
const ipfsHttpClient = require('ipfs-http-client')
const DelegatedPeerRouter = require('libp2p-delegated-peer-routing')
const peerUtils = require('../utils/creators/peer')
@@ -72,11 +73,11 @@ describe('peer-routing', () => {
let delegate
beforeEach(async () => {
delegate = new DelegatedPeerRouter({
delegate = new DelegatedPeerRouter(ipfsHttpClient({
host: '0.0.0.0',
protocol: 'http',
port: 60197
})
}))
;[node] = await peerUtils.createPeer({
config: mergeOptions(baseOptions, {
@@ -162,11 +163,11 @@ describe('peer-routing', () => {
let delegate
beforeEach(async () => {
delegate = new DelegatedPeerRouter({
delegate = new DelegatedPeerRouter(ipfsHttpClient({
host: '0.0.0.0',
protocol: 'http',
port: 60197
})
}))
;[node] = await peerUtils.createPeer({
config: mergeOptions(routingOptions, {

View File

@@ -135,8 +135,7 @@ describe('Persisted PeerStore', () => {
peerStore.keyBook.set(peers[0], peers[0].pubKey)
peerStore.keyBook.set(peers[1], peers[1].pubKey)
// let batch commit complete
await Promise.all(commitSpy.returnValues)
// no batch commit as public key inline
// ProtoBook
peerStore.protoBook.set(peers[0], protocols)
@@ -151,7 +150,7 @@ describe('Persisted PeerStore', () => {
// let batch commit complete
await Promise.all(commitSpy.returnValues)
expect(spyDs).to.have.property('callCount', 7) // 2 Address + 2 Key + 2 Proto + 1 Metadata
expect(spyDs).to.have.property('callCount', 5) // 2 Address + 2 Proto + 1 Metadata
expect(peerStore.peers.size).to.equal(2)
await peerStore.stop()
@@ -164,12 +163,12 @@ describe('Persisted PeerStore', () => {
await peerStore.start()
expect(spy).to.have.property('callCount', 7)
expect(spyDs).to.have.property('callCount', 7)
expect(spy).to.have.property('callCount', 5)
expect(spyDs).to.have.property('callCount', 5)
expect(peerStore.peers.size).to.equal(2)
expect(peerStore.addressBook.data.size).to.equal(2)
expect(peerStore.keyBook.data.size).to.equal(2)
expect(peerStore.keyBook.data.size).to.equal(0)
expect(peerStore.protoBook.data.size).to.equal(2)
expect(peerStore.metadataBook.data.size).to.equal(1)
})

View File

@@ -5,7 +5,9 @@ const chai = require('chai')
chai.use(require('dirty-chai'))
const { expect } = chai
const sinon = require('sinon')
const pDefer = require('p-defer')
const pWaitFor = require('p-wait-for')
const PeerStore = require('../../src/peer-store')
@@ -224,6 +226,96 @@ describe('protoBook', () => {
})
})
describe('protoBook.remove', () => {
let peerStore, pb
beforeEach(() => {
peerStore = new PeerStore({ peerId })
pb = peerStore.protoBook
})
afterEach(() => {
peerStore.removeAllListeners()
})
it('throws invalid parameters error if invalid PeerId is provided', () => {
expect(() => {
pb.remove('invalid peerId')
}).to.throw(ERR_INVALID_PARAMETERS)
})
it('throws invalid parameters error if no protocols provided', () => {
expect(() => {
pb.remove(peerId)
}).to.throw(ERR_INVALID_PARAMETERS)
})
it('removes the given protocol and emits change event', async () => {
const spy = sinon.spy()
const supportedProtocols = ['protocol1', 'protocol2']
const removedProtocols = ['protocol1']
const finalProtocols = supportedProtocols.filter(p => !removedProtocols.includes(p))
peerStore.on('change:protocols', spy)
// Replace
pb.set(peerId, supportedProtocols)
let protocols = pb.get(peerId)
expect(protocols).to.have.deep.members(supportedProtocols)
// Remove
pb.remove(peerId, removedProtocols)
protocols = pb.get(peerId)
expect(protocols).to.have.deep.members(finalProtocols)
await pWaitFor(() => spy.callCount === 2)
const [firstCallArgs] = spy.firstCall.args
const [secondCallArgs] = spy.secondCall.args
expect(arraysAreEqual(firstCallArgs.protocols, supportedProtocols))
expect(arraysAreEqual(secondCallArgs.protocols, finalProtocols))
})
it('emits on remove if the content changes', () => {
const spy = sinon.spy()
const supportedProtocols = ['protocol1', 'protocol2']
const removedProtocols = ['protocol2']
const finalProtocols = supportedProtocols.filter(p => !removedProtocols.includes(p))
peerStore.on('change:protocols', spy)
// set
pb.set(peerId, supportedProtocols)
// remove (content already existing)
pb.remove(peerId, removedProtocols)
const protocols = pb.get(peerId)
expect(protocols).to.have.deep.members(finalProtocols)
return pWaitFor(() => spy.callCount === 2)
})
it('does not emit on remove if the content does not change', () => {
const spy = sinon.spy()
const supportedProtocols = ['protocol1', 'protocol2']
const removedProtocols = ['protocol3']
peerStore.on('change:protocols', spy)
// set
pb.set(peerId, supportedProtocols)
// remove
pb.remove(peerId, removedProtocols)
// Only one event
expect(spy.callCount).to.eql(1)
})
})
describe('protoBook.get', () => {
let peerStore, pb

View File

@@ -0,0 +1,585 @@
'use strict'
/* eslint-env mocha */
const chai = require('chai')
chai.use(require('dirty-chai'))
const { expect } = chai
const delay = require('delay')
const pWaitFor = require('p-wait-for')
const sinon = require('sinon')
const nock = require('nock')
const ipfsHttpClient = require('ipfs-http-client')
const DelegatedContentRouter = require('libp2p-delegated-content-routing')
const multiaddr = require('multiaddr')
const Libp2p = require('../../src')
const { relay: relayMulticodec } = require('../../src/circuit/multicodec')
const { createPeerId } = require('../utils/creators/peer')
const baseOptions = require('../utils/base-options')
const listenAddr = '/ip4/0.0.0.0/tcp/0'
describe('auto-relay', () => {
describe('basics', () => {
let libp2p
let relayLibp2p
let autoRelay
beforeEach(async () => {
const peerIds = await createPeerId({ number: 2 })
// Create 2 nodes, and turn HOP on for the relay
;[libp2p, relayLibp2p] = peerIds.map((peerId, index) => {
const opts = {
...baseOptions,
config: {
...baseOptions.config,
relay: {
hop: {
enabled: index !== 0
},
autoRelay: {
enabled: true,
maxListeners: 1
}
}
}
}
return new Libp2p({
...opts,
addresses: {
listen: [listenAddr]
},
connectionManager: {
autoDial: false
},
peerDiscovery: {
autoDial: false
},
peerId
})
})
autoRelay = libp2p.relay._autoRelay
expect(autoRelay.maxListeners).to.eql(1)
})
beforeEach(() => {
// Start each node
return Promise.all([libp2p, relayLibp2p].map(libp2p => libp2p.start()))
})
afterEach(() => {
// Stop each node
return Promise.all([libp2p, relayLibp2p].map(libp2p => libp2p.stop()))
})
it('should ask if node supports hop on protocol change (relay protocol) and add to listen multiaddrs', async () => {
// Spy if a connected peer is being added as listen relay
sinon.spy(autoRelay, '_addListenRelay')
const originalMultiaddrsLength = relayLibp2p.multiaddrs.length
// Discover relay
libp2p.peerStore.addressBook.add(relayLibp2p.peerId, relayLibp2p.multiaddrs)
await libp2p.dial(relayLibp2p.peerId)
// Wait for peer added as listen relay
await pWaitFor(() => autoRelay._addListenRelay.callCount === 1)
expect(autoRelay._listenRelays.size).to.equal(1)
// Wait for listen multiaddr update
await pWaitFor(() => libp2p.multiaddrs.length === originalMultiaddrsLength + 1)
expect(libp2p.multiaddrs[originalMultiaddrsLength].getPeerId()).to.eql(relayLibp2p.peerId.toB58String())
// Peer has relay multicodec
const knownProtocols = libp2p.peerStore.protoBook.get(relayLibp2p.peerId)
expect(knownProtocols).to.include(relayMulticodec)
})
})
describe('flows with 1 listener max', () => {
let libp2p
let relayLibp2p1
let relayLibp2p2
let relayLibp2p3
let autoRelay1
beforeEach(async () => {
const peerIds = await createPeerId({ number: 4 })
// Create 4 nodes, and turn HOP on for the relay
;[libp2p, relayLibp2p1, relayLibp2p2, relayLibp2p3] = peerIds.map((peerId, index) => {
let opts = baseOptions
if (index !== 0) {
opts = {
...baseOptions,
config: {
...baseOptions.config,
relay: {
hop: {
enabled: true
},
autoRelay: {
enabled: true,
maxListeners: 1
}
}
}
}
}
return new Libp2p({
...opts,
addresses: {
listen: [listenAddr]
},
connectionManager: {
autoDial: false
},
peerDiscovery: {
autoDial: false
},
peerId
})
})
autoRelay1 = relayLibp2p1.relay._autoRelay
expect(autoRelay1.maxListeners).to.eql(1)
})
beforeEach(() => {
// Start each node
return Promise.all([libp2p, relayLibp2p1, relayLibp2p2, relayLibp2p3].map(libp2p => libp2p.start()))
})
afterEach(() => {
// Stop each node
return Promise.all([libp2p, relayLibp2p1, relayLibp2p2, relayLibp2p3].map(libp2p => libp2p.stop()))
})
it('should ask if node supports hop on protocol change (relay protocol) and add to listen multiaddrs', async () => {
// Spy if a connected peer is being added as listen relay
sinon.spy(autoRelay1, '_addListenRelay')
// Discover relay
relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs)
const originalMultiaddrs1Length = relayLibp2p1.multiaddrs.length
const originalMultiaddrs2Length = relayLibp2p2.multiaddrs.length
await relayLibp2p1.dial(relayLibp2p2.peerId)
// Wait for peer added as listen relay
await pWaitFor(() => autoRelay1._addListenRelay.callCount === 1)
expect(autoRelay1._listenRelays.size).to.equal(1)
// Wait for listen multiaddr update
await Promise.all([
pWaitFor(() => relayLibp2p1.multiaddrs.length === originalMultiaddrs1Length + 1),
pWaitFor(() => relayLibp2p2.multiaddrs.length === originalMultiaddrs2Length + 1)
])
expect(relayLibp2p1.multiaddrs[originalMultiaddrs1Length].getPeerId()).to.eql(relayLibp2p2.peerId.toB58String())
// Peer has relay multicodec
const knownProtocols = relayLibp2p1.peerStore.protoBook.get(relayLibp2p2.peerId)
expect(knownProtocols).to.include(relayMulticodec)
})
it('should be able to dial a peer from its relayed address previously added', async () => {
const originalMultiaddrs1Length = relayLibp2p1.multiaddrs.length
const originalMultiaddrs2Length = relayLibp2p2.multiaddrs.length
// Discover relay
relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs)
await relayLibp2p1.dial(relayLibp2p2.peerId)
// Wait for listen multiaddr update
await Promise.all([
pWaitFor(() => relayLibp2p1.multiaddrs.length === originalMultiaddrs1Length + 1),
pWaitFor(() => relayLibp2p2.multiaddrs.length === originalMultiaddrs2Length + 1)
])
expect(relayLibp2p1.multiaddrs[originalMultiaddrs1Length].getPeerId()).to.eql(relayLibp2p2.peerId.toB58String())
// Dial from the other through a relay
const relayedMultiaddr2 = multiaddr(`${relayLibp2p1.multiaddrs[0]}/p2p/${relayLibp2p1.peerId.toB58String()}/p2p-circuit`)
libp2p.peerStore.addressBook.add(relayLibp2p2.peerId, [relayedMultiaddr2])
await libp2p.dial(relayLibp2p2.peerId)
})
it('should only add maxListeners relayed addresses', async () => {
const originalMultiaddrs1Length = relayLibp2p1.multiaddrs.length
const originalMultiaddrs2Length = relayLibp2p2.multiaddrs.length
// Spy if a connected peer is being added as listen relay
sinon.spy(autoRelay1, '_addListenRelay')
sinon.spy(autoRelay1._listenRelays, 'add')
// Discover one relay and connect
relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs)
await relayLibp2p1.dial(relayLibp2p2.peerId)
expect(relayLibp2p1.connectionManager.size).to.eql(1)
// Wait for peer added as listen relay
await pWaitFor(() => autoRelay1._addListenRelay.callCount === 1 && autoRelay1._listenRelays.add.callCount === 1)
expect(autoRelay1._listenRelays.size).to.equal(1)
// Wait for listen multiaddr update
await Promise.all([
pWaitFor(() => relayLibp2p1.multiaddrs.length === originalMultiaddrs1Length + 1),
pWaitFor(() => relayLibp2p2.multiaddrs.length === originalMultiaddrs2Length + 1)
])
expect(relayLibp2p1.multiaddrs[originalMultiaddrs1Length].getPeerId()).to.eql(relayLibp2p2.peerId.toB58String())
// Relay2 has relay multicodec
const knownProtocols2 = relayLibp2p1.peerStore.protoBook.get(relayLibp2p2.peerId)
expect(knownProtocols2).to.include(relayMulticodec)
// Discover an extra relay and connect
relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs)
await relayLibp2p1.dial(relayLibp2p3.peerId)
// Wait to guarantee the dialed peer is not added as a listen relay
await delay(300)
expect(autoRelay1._addListenRelay.callCount).to.equal(2)
expect(autoRelay1._listenRelays.add.callCount).to.equal(1)
expect(autoRelay1._listenRelays.size).to.equal(1)
expect(relayLibp2p1.connectionManager.size).to.eql(2)
// Relay2 has relay multicodec
const knownProtocols3 = relayLibp2p1.peerStore.protoBook.get(relayLibp2p3.peerId)
expect(knownProtocols3).to.include(relayMulticodec)
})
it('should not listen on a relayed address if peer disconnects', async () => {
const originalMultiaddrs1Length = relayLibp2p1.multiaddrs.length
// Spy if identify push is fired on adding/removing listen addr
sinon.spy(relayLibp2p1.identifyService, 'pushToPeerStore')
// Discover one relay and connect
relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs)
await relayLibp2p1.dial(relayLibp2p2.peerId)
// Wait for listenning on the relay
await pWaitFor(() => relayLibp2p1.multiaddrs.length === originalMultiaddrs1Length + 1)
expect(autoRelay1._listenRelays.size).to.equal(1)
expect(relayLibp2p1.multiaddrs[originalMultiaddrs1Length].getPeerId()).to.eql(relayLibp2p2.peerId.toB58String())
// Identify push for adding listen relay multiaddr
expect(relayLibp2p1.identifyService.pushToPeerStore.callCount).to.equal(1)
// Disconnect from peer used for relay
await relayLibp2p1.hangUp(relayLibp2p2.peerId)
// Wait for removed listening on the relay
await pWaitFor(() => relayLibp2p1.multiaddrs.length === originalMultiaddrs1Length)
expect(autoRelay1._listenRelays.size).to.equal(0)
// Identify push for removing listen relay multiaddr
expect(relayLibp2p1.identifyService.pushToPeerStore.callCount).to.equal(2)
})
it('should try to listen on other connected peers relayed address if one used relay disconnects', async () => {
const originalMultiaddrs1Length = relayLibp2p1.multiaddrs.length
// Spy if a connected peer is being added as listen relay
sinon.spy(autoRelay1, '_addListenRelay')
sinon.spy(relayLibp2p1.transportManager, 'listen')
// Discover one relay and connect
relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs)
await relayLibp2p1.dial(relayLibp2p2.peerId)
// Discover an extra relay and connect
relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs)
await relayLibp2p1.dial(relayLibp2p3.peerId)
// Wait for both peer to be attempted to added as listen relay
await pWaitFor(() => autoRelay1._addListenRelay.callCount === 1)
expect(autoRelay1._listenRelays.size).to.equal(1)
expect(relayLibp2p1.connectionManager.size).to.equal(2)
// Wait for listen multiaddr update
await pWaitFor(() => relayLibp2p1.multiaddrs.length === originalMultiaddrs1Length + 1)
expect(relayLibp2p1.multiaddrs[originalMultiaddrs1Length].getPeerId()).to.eql(relayLibp2p2.peerId.toB58String())
// Only one will be used for listeninng
expect(relayLibp2p1.transportManager.listen.callCount).to.equal(1)
// Spy if relay from listen map was removed
sinon.spy(autoRelay1._listenRelays, 'delete')
// Disconnect from peer used for relay
await relayLibp2p1.hangUp(relayLibp2p2.peerId)
expect(autoRelay1._listenRelays.delete.callCount).to.equal(1)
expect(autoRelay1._addListenRelay.callCount).to.equal(1)
// Wait for other peer connected to be added as listen addr
await pWaitFor(() => relayLibp2p1.transportManager.listen.callCount === 2)
expect(autoRelay1._listenRelays.size).to.equal(1)
expect(relayLibp2p1.connectionManager.size).to.eql(1)
// Wait for listen multiaddr update
await pWaitFor(() => relayLibp2p1.multiaddrs.length === originalMultiaddrs1Length + 1)
expect(relayLibp2p1.multiaddrs[originalMultiaddrs1Length].getPeerId()).to.eql(relayLibp2p3.peerId.toB58String())
})
it('should try to listen on stored peers relayed address if one used relay disconnects and there are not enough connected', async () => {
// Spy if a connected peer is being added as listen relay
sinon.spy(autoRelay1, '_addListenRelay')
sinon.spy(relayLibp2p1.transportManager, 'listen')
// Discover one relay and connect
relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs)
await relayLibp2p1.dial(relayLibp2p2.peerId)
// Discover an extra relay and connect to gather its Hop support
relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs)
await relayLibp2p1.dial(relayLibp2p3.peerId)
// Wait for both peer to be attempted to added as listen relay
await pWaitFor(() => autoRelay1._addListenRelay.callCount === 2)
expect(autoRelay1._listenRelays.size).to.equal(1)
expect(relayLibp2p1.connectionManager.size).to.equal(2)
// Only one will be used for listeninng
expect(relayLibp2p1.transportManager.listen.callCount).to.equal(1)
// Disconnect not used listen relay
await relayLibp2p1.hangUp(relayLibp2p3.peerId)
expect(autoRelay1._listenRelays.size).to.equal(1)
expect(relayLibp2p1.connectionManager.size).to.equal(1)
// Spy on dial
sinon.spy(relayLibp2p1, 'dial')
// Remove peer used as relay from peerStore and disconnect it
relayLibp2p1.peerStore.delete(relayLibp2p2.peerId)
await relayLibp2p1.hangUp(relayLibp2p2.peerId)
expect(autoRelay1._listenRelays.size).to.equal(0)
expect(relayLibp2p1.connectionManager.size).to.equal(0)
// Wait for other peer connected to be added as listen addr
await pWaitFor(() => relayLibp2p1.transportManager.listen.callCount === 2)
expect(autoRelay1._listenRelays.size).to.equal(1)
expect(relayLibp2p1.connectionManager.size).to.eql(1)
})
})
describe('flows with 2 max listeners', () => {
let relayLibp2p1
let relayLibp2p2
let relayLibp2p3
let autoRelay1
let autoRelay2
beforeEach(async () => {
const peerIds = await createPeerId({ number: 3 })
// Create 3 nodes, and turn HOP on for the relay
;[relayLibp2p1, relayLibp2p2, relayLibp2p3] = peerIds.map((peerId) => {
return new Libp2p({
...baseOptions,
config: {
...baseOptions.config,
relay: {
...baseOptions.config.relay,
hop: {
enabled: true
},
autoRelay: {
enabled: true,
maxListeners: 2
}
}
},
addresses: {
listen: [listenAddr]
},
connectionManager: {
autoDial: false
},
peerDiscovery: {
autoDial: false
},
peerId
})
})
autoRelay1 = relayLibp2p1.relay._autoRelay
autoRelay2 = relayLibp2p2.relay._autoRelay
})
beforeEach(() => {
// Start each node
return Promise.all([relayLibp2p1, relayLibp2p2, relayLibp2p3].map(libp2p => libp2p.start()))
})
afterEach(() => {
// Stop each node
return Promise.all([relayLibp2p1, relayLibp2p2, relayLibp2p3].map(libp2p => libp2p.stop()))
})
it('should not add listener to a already relayed connection', async () => {
// Spy if a connected peer is being added as listen relay
sinon.spy(autoRelay1, '_addListenRelay')
sinon.spy(autoRelay2, '_addListenRelay')
// Relay 1 discovers Relay 3 and connect
relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs)
await relayLibp2p1.dial(relayLibp2p3.peerId)
// Wait for peer added as listen relay
await pWaitFor(() => autoRelay1._addListenRelay.callCount === 1)
expect(autoRelay1._listenRelays.size).to.equal(1)
// Relay 2 discovers Relay 3 and connect
relayLibp2p2.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs)
await relayLibp2p2.dial(relayLibp2p3.peerId)
// Wait for peer added as listen relay
await pWaitFor(() => autoRelay2._addListenRelay.callCount === 1)
expect(autoRelay2._listenRelays.size).to.equal(1)
// Relay 1 discovers Relay 2 relayed multiaddr via Relay 3
const ma2RelayedBy3 = relayLibp2p2.multiaddrs[relayLibp2p2.multiaddrs.length - 1]
relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, [ma2RelayedBy3])
await relayLibp2p1.dial(relayLibp2p2.peerId)
// Peer not added as listen relay
expect(autoRelay1._addListenRelay.callCount).to.equal(1)
expect(autoRelay1._listenRelays.size).to.equal(1)
})
})
describe('discovery', () => {
let local
let remote
let relayLibp2p
beforeEach(async () => {
const peerIds = await createPeerId({ number: 3 })
// Create 2 nodes, and turn HOP on for the relay
;[local, remote, relayLibp2p] = peerIds.map((peerId, index) => {
const delegate = new DelegatedContentRouter(peerId, ipfsHttpClient({
host: '0.0.0.0',
protocol: 'http',
port: 60197
}), [
multiaddr('/ip4/0.0.0.0/tcp/60197')
])
const opts = {
...baseOptions,
config: {
...baseOptions.config,
relay: {
advertise: {
bootDelay: 1000,
ttl: 1000,
enabled: true
},
hop: {
enabled: index === 2
},
autoRelay: {
enabled: true,
maxListeners: 1
}
}
}
}
return new Libp2p({
...opts,
modules: {
...opts.modules,
contentRouting: [delegate]
},
addresses: {
listen: [listenAddr]
},
connectionManager: {
autoDial: false
},
peerDiscovery: {
autoDial: false
},
peerId
})
})
sinon.spy(relayLibp2p.contentRouting, 'provide')
})
beforeEach(async () => {
nock('http://0.0.0.0:60197')
// mock the refs call
.post('/api/v0/refs')
.query(true)
.reply(200, null, [
'Content-Type', 'application/json',
'X-Chunked-Output', '1'
])
// Start each node
await Promise.all([local, remote, relayLibp2p].map(libp2p => libp2p.start()))
// Should provide on start
await pWaitFor(() => relayLibp2p.contentRouting.provide.callCount === 1)
const provider = relayLibp2p.peerId.toB58String()
const multiaddrs = relayLibp2p.multiaddrs.map((m) => m.toString())
// Mock findProviders
nock('http://0.0.0.0:60197')
.post('/api/v0/dht/findprovs')
.query(true)
.reply(200, `{"Extra":"","ID":"${provider}","Responses":[{"Addrs":${JSON.stringify(multiaddrs)},"ID":"${provider}"}],"Type":4}\n`, [
'Content-Type', 'application/json',
'X-Chunked-Output', '1'
])
})
afterEach(() => {
// Stop each node
return Promise.all([local, remote, relayLibp2p].map(libp2p => libp2p.stop()))
})
it('should find providers for relay and add it as listen relay', async () => {
const originalMultiaddrsLength = local.multiaddrs.length
// Spy add listen relay
sinon.spy(local.relay._autoRelay, '_addListenRelay')
// Spy Find Providers
sinon.spy(local.contentRouting, 'findProviders')
// Try to listen on Available hop relays
await local.relay._autoRelay._listenOnAvailableHopRelays()
// Should try to find relay service providers
await pWaitFor(() => local.contentRouting.findProviders.callCount === 1)
// Wait for peer added as listen relay
await pWaitFor(() => local.relay._autoRelay._addListenRelay.callCount === 1)
expect(local.relay._autoRelay._listenRelays.size).to.equal(1)
await pWaitFor(() => local.multiaddrs.length === originalMultiaddrsLength + 1)
const relayedAddr = local.multiaddrs[local.multiaddrs.length - 1]
remote.peerStore.addressBook.set(local.peerId, [relayedAddr])
// Dial from remote through the relayed address
const conn = await remote.dial(local.peerId)
expect(conn).to.exist()
})
})
})

View File

@@ -72,7 +72,7 @@ describe('Dialing (via relay, TCP)', () => {
const tcpAddrs = dstLibp2p.transportManager.getAddrs()
sinon.stub(dstLibp2p.addressManager, 'listen').value([multiaddr(`/p2p-circuit${relayAddr}/p2p/${relayIdString}`)])
await dstLibp2p.transportManager.listen()
await dstLibp2p.transportManager.listen(dstLibp2p.addressManager.getListenAddrs())
expect(dstLibp2p.transportManager.getAddrs()).to.have.deep.members([...tcpAddrs, dialAddr.decapsulate('p2p')])
const connection = await srcLibp2p.dial(dialAddr)
@@ -157,7 +157,7 @@ describe('Dialing (via relay, TCP)', () => {
const tcpAddrs = dstLibp2p.transportManager.getAddrs()
sinon.stub(dstLibp2p.addressManager, 'getListenAddrs').returns([multiaddr(`${relayAddr}/p2p-circuit`)])
await dstLibp2p.transportManager.listen()
await dstLibp2p.transportManager.listen(dstLibp2p.addressManager.getListenAddrs())
expect(dstLibp2p.transportManager.getAddrs()).to.have.deep.members([...tcpAddrs, dialAddr.decapsulate('p2p')])
// Tamper with the our multiaddrs for the circuit message

View File

@@ -7,9 +7,13 @@ const { expect } = chai
const AddressManager = require('../../src/address-manager')
const TransportManager = require('../../src/transport-manager')
const PeerStore = require('../../src/peer-store')
const PeerRecord = require('../../src/record/peer-record')
const Transport = require('libp2p-tcp')
const PeerId = require('peer-id')
const multiaddr = require('multiaddr')
const mockUpgrader = require('../utils/mockUpgrader')
const Peers = require('../fixtures/peers')
const addrs = [
multiaddr('/ip4/127.0.0.1/tcp/0'),
multiaddr('/ip4/127.0.0.1/tcp/0')
@@ -17,11 +21,19 @@ const addrs = [
describe('Transport Manager (TCP)', () => {
let tm
let localPeer
before(() => {
before(async () => {
localPeer = await PeerId.createFromJSON(Peers[0])
})
beforeEach(() => {
tm = new TransportManager({
libp2p: {
addressManager: new AddressManager({ listen: addrs })
peerId: localPeer,
multiaddrs: addrs,
addressManager: new AddressManager({ listen: addrs }),
peerStore: new PeerStore({ peerId: localPeer })
},
upgrader: mockUpgrader,
onConnection: () => {}
@@ -41,18 +53,38 @@ describe('Transport Manager (TCP)', () => {
it('should be able to listen', async () => {
tm.add(Transport.prototype[Symbol.toStringTag], Transport)
await tm.listen()
await tm.listen(addrs)
expect(tm._listeners).to.have.key(Transport.prototype[Symbol.toStringTag])
expect(tm._listeners.get(Transport.prototype[Symbol.toStringTag])).to.have.length(addrs.length)
// Ephemeral ip addresses may result in multiple listeners
expect(tm.getAddrs().length).to.equal(addrs.length)
await tm.close()
expect(tm._listeners.get(Transport.prototype[Symbol.toStringTag])).to.have.length(0)
})
it('should create self signed peer record on listen', async () => {
let signedPeerRecord = await tm.libp2p.peerStore.addressBook.getPeerRecord(localPeer)
expect(signedPeerRecord).to.not.exist()
tm.add(Transport.prototype[Symbol.toStringTag], Transport)
await tm.listen(addrs)
// Should created Self Peer record on new listen address
signedPeerRecord = await tm.libp2p.peerStore.addressBook.getPeerRecord(localPeer)
expect(signedPeerRecord).to.exist()
const record = PeerRecord.createFromProtobuf(signedPeerRecord.payload)
expect(record).to.exist()
expect(record.multiaddrs.length).to.equal(addrs.length)
addrs.forEach((a, i) => {
expect(record.multiaddrs[i].equals(a)).to.be.true()
})
})
it('should be able to dial', async () => {
tm.add(Transport.prototype[Symbol.toStringTag], Transport)
await tm.listen()
await tm.listen(addrs)
const addr = tm.getAddrs().shift()
const connection = await tm.dial(addr)
expect(connection).to.exist()

View File

@@ -87,7 +87,7 @@ describe('Transport Manager (WebSockets)', () => {
it('should fail to listen with no valid address', async () => {
tm.add(Transport.prototype[Symbol.toStringTag], Transport)
await expect(tm.listen())
await expect(tm.listen([listenAddr]))
.to.eventually.be.rejected()
.and.to.have.property('code', ErrorCodes.ERR_NO_VALID_ADDRESSES)
})

View File

@@ -13,13 +13,14 @@ const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0')
/**
* Create libp2p nodes.
*
* @param {Object} [properties]
* @param {Object} [properties.config]
* @param {number} [properties.number] number of peers (default: 1).
* @param {boolean} [properties.fixture] use fixture for peer-id generation (default: true)
* @param {boolean} [properties.started] nodes should start (default: true)
* @param {boolean} [properties.populateAddressBooks] nodes addressBooks should be populated with other peers (default: true)
* @return {Promise<Array<Libp2p>>}
* @param {number} [properties.number] - number of peers (default: 1).
* @param {boolean} [properties.fixture] - use fixture for peer-id generation (default: true)
* @param {boolean} [properties.started] - nodes should start (default: true)
* @param {boolean} [properties.populateAddressBooks] - nodes addressBooks should be populated with other peers (default: true)
* @returns {Promise<Array<Libp2p>>}
*/
async function createPeer ({ number = 1, fixture = true, started = true, populateAddressBooks = true, config = {} } = {}) {
const peerIds = await createPeerId({ number, fixture })
@@ -53,10 +54,11 @@ function _populateAddressBooks (peers) {
/**
* Create Peer-ids.
*
* @param {Object} [properties]
* @param {number} [properties.number] number of peers (default: 1).
* @param {boolean} [properties.fixture] use fixture for peer-id generation (default: true)
* @return {Promise<Array<PeerId>>}
* @param {number} [properties.number] - number of peers (default: 1).
* @param {boolean} [properties.fixture] - use fixture for peer-id generation (default: true)
* @returns {Promise<Array<PeerId>>}
*/
function createPeerId ({ number = 1, fixture = true } = {}) {
return pTimes(number, (i) => fixture

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