mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-07-22 12:01:57 +00:00
Compare commits
53 Commits
fix/use-un
...
v0.28.10
Author | SHA1 | Date | |
---|---|---|---|
|
0e18735b8c | ||
|
f68ff35625 | ||
|
8c56ec0d23 | ||
|
fa5ee873e3 | ||
|
51d7ca44c1 | ||
|
726a746479 | ||
|
a331b84f13 | ||
|
78d152dd68 | ||
|
7e14aa19b5 | ||
|
2440c872df | ||
|
6c7e5e5eef | ||
|
388df6b6e6 | ||
|
7dbfe6ab1a | ||
|
cea59a1fe4 | ||
|
7da9ad44ab | ||
|
3896941128 | ||
|
856b38de67 | ||
|
798d7b73c1 | ||
|
f2d0d8b51d | ||
|
999c1b7740 | ||
|
99ff361a55 | ||
|
6115f8b680 | ||
|
9ccab40fc8 | ||
|
619e5dd73c | ||
|
0fd23f6a5f | ||
|
5a84dd56d0 | ||
|
7b05d6922b | ||
|
08a4fad80b | ||
|
51da8874d8 | ||
|
5cd8c19567 | ||
|
db766eaca9 | ||
|
a1308d640d | ||
|
19e7254c3d | ||
|
f4898eb883 | ||
|
9bec719fce | ||
|
b664c0aafd | ||
|
b524bbf627 | ||
|
07bd269fb0 | ||
|
a8219e61a0 | ||
|
a1590acc8b | ||
|
a0cf83c640 | ||
|
b56cdda0ef | ||
|
b621fbdfdc | ||
|
24dd1d22c5 | ||
|
a1a1213bdc | ||
|
90a9d93968 | ||
|
8f680e20e9 | ||
|
afafd08943 | ||
|
c038550fad | ||
|
d60a3215d0 | ||
|
1e51295150 | ||
|
5299995998 | ||
|
64a66f08ac |
@@ -45,7 +45,7 @@ const after = async () => {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
bundlesize: { maxSize: '200kB' },
|
||||
bundlesize: { maxSize: '202kB' },
|
||||
hooks: {
|
||||
pre: before,
|
||||
post: after
|
||||
|
111
CHANGELOG.md
111
CHANGELOG.md
@@ -1,3 +1,114 @@
|
||||
<a name="0.28.10"></a>
|
||||
## [0.28.10](https://github.com/libp2p/js-libp2p/compare/v0.28.9...v0.28.10) (2020-08-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow certain keychain operations without a password ([#726](https://github.com/libp2p/js-libp2p/issues/726)) ([8c56ec0](https://github.com/libp2p/js-libp2p/commit/8c56ec0))
|
||||
* **identify:** make agentversion dynamic and add it to the peerstore ([#724](https://github.com/libp2p/js-libp2p/issues/724)) ([726a746](https://github.com/libp2p/js-libp2p/commit/726a746))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **keychain:** add support for ed25519 and secp keys ([#725](https://github.com/libp2p/js-libp2p/issues/725)) ([51d7ca4](https://github.com/libp2p/js-libp2p/commit/51d7ca4))
|
||||
|
||||
|
||||
|
||||
<a name="0.28.9"></a>
|
||||
## [0.28.9](https://github.com/libp2p/js-libp2p/compare/v0.28.8...v0.28.9) (2020-07-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ping multiaddr from peer not previously stored in peerstore ([#719](https://github.com/libp2p/js-libp2p/issues/719)) ([2440c87](https://github.com/libp2p/js-libp2p/commit/2440c87))
|
||||
|
||||
|
||||
|
||||
<a name="0.28.8"></a>
|
||||
## [0.28.8](https://github.com/libp2p/js-libp2p/compare/v0.28.7...v0.28.8) (2020-07-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* create dial target for peer with no known addrs ([#715](https://github.com/libp2p/js-libp2p/issues/715)) ([7da9ad4](https://github.com/libp2p/js-libp2p/commit/7da9ad4))
|
||||
|
||||
|
||||
|
||||
<a name="0.28.7"></a>
|
||||
## [0.28.7](https://github.com/libp2p/js-libp2p/compare/v0.28.6...v0.28.7) (2020-07-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* retimer reschedule does not work as interval ([#710](https://github.com/libp2p/js-libp2p/issues/710)) ([999c1b7](https://github.com/libp2p/js-libp2p/commit/999c1b7))
|
||||
|
||||
|
||||
|
||||
<a name="0.28.6"></a>
|
||||
## [0.28.6](https://github.com/libp2p/js-libp2p/compare/v0.28.5...v0.28.6) (2020-07-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* not dial all known peers in parallel on startup ([#698](https://github.com/libp2p/js-libp2p/issues/698)) ([9ccab40](https://github.com/libp2p/js-libp2p/commit/9ccab40))
|
||||
|
||||
|
||||
|
||||
<a name="0.28.5"></a>
|
||||
## [0.28.5](https://github.com/libp2p/js-libp2p/compare/v0.28.4...v0.28.5) (2020-07-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* pass libp2p to the dht ([#700](https://github.com/libp2p/js-libp2p/issues/700)) ([5a84dd5](https://github.com/libp2p/js-libp2p/commit/5a84dd5))
|
||||
|
||||
|
||||
|
||||
<a name="0.28.4"></a>
|
||||
## [0.28.4](https://github.com/libp2p/js-libp2p/compare/v0.28.3...v0.28.4) (2020-07-03)
|
||||
|
||||
|
||||
|
||||
<a name="0.28.3"></a>
|
||||
## [0.28.3](https://github.com/libp2p/js-libp2p/compare/v0.28.2...v0.28.3) (2020-06-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* catch pipe errors ([#678](https://github.com/libp2p/js-libp2p/issues/678)) ([a8219e6](https://github.com/libp2p/js-libp2p/commit/a8219e6))
|
||||
|
||||
|
||||
|
||||
<a name="0.28.2"></a>
|
||||
## [0.28.2](https://github.com/libp2p/js-libp2p/compare/v0.28.1...v0.28.2) (2020-06-15)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* "fix: throw if no conn encryption module provided ([#665](https://github.com/libp2p/js-libp2p/issues/665))" ([b621fbd](https://github.com/libp2p/js-libp2p/commit/b621fbd))
|
||||
|
||||
|
||||
|
||||
<a name="0.28.1"></a>
|
||||
## [0.28.1](https://github.com/libp2p/js-libp2p/compare/v0.28.0...v0.28.1) (2020-06-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* throw if no conn encryption module provided ([#665](https://github.com/libp2p/js-libp2p/issues/665)) ([c038550](https://github.com/libp2p/js-libp2p/commit/c038550))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ConnectionManager#getAll ([8f680e2](https://github.com/libp2p/js-libp2p/commit/8f680e2))
|
||||
|
||||
|
||||
|
||||
<a name="0.28.0"></a>
|
||||
# [0.28.0](https://github.com/libp2p/js-libp2p/compare/v0.28.0-rc.0...v0.28.0) (2020-06-05)
|
||||
|
||||
|
||||
|
||||
<a name="0.28.0-rc.0"></a>
|
||||
# [0.28.0-rc.0](https://github.com/libp2p/js-libp2p/compare/v0.27.8...v0.28.0-rc.0) (2020-05-28)
|
||||
|
||||
|
45
MIGRATION_TEMPLATE.md
Normal file
45
MIGRATION_TEMPLATE.md
Normal file
@@ -0,0 +1,45 @@
|
||||
<!--Specify versions for migration below-->
|
||||
# Migrating to libp2p@__
|
||||
|
||||
A migration guide for refactoring your application code from libp2p v__ to v__.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [API](#api)
|
||||
- [Module Updates](#module-updates)
|
||||
|
||||
## API
|
||||
|
||||
<!--Describe breaking APIs with examples for Before and After
|
||||
Example:
|
||||
|
||||
### Peer Discovery
|
||||
|
||||
__Describe__
|
||||
|
||||
**Before**
|
||||
|
||||
```js
|
||||
|
||||
```
|
||||
|
||||
**After**
|
||||
|
||||
```js
|
||||
|
||||
```
|
||||
|
||||
-->
|
||||
|
||||
## Module Updates
|
||||
|
||||
With this release you should update the following libp2p modules if you are relying on them:
|
||||
|
||||
<!--Specify module versions in JSON for migration below.
|
||||
It's recommended to check package.json changes for this:
|
||||
`git diff <release> <prev> -- package.json`
|
||||
-->
|
||||
|
||||
```json
|
||||
|
||||
```
|
@@ -23,8 +23,8 @@
|
||||
<a href="https://david-dm.org/libp2p/js-libp2p"><img src="https://david-dm.org/libp2p/js-libp2p.svg?style=flat-square" /></a>
|
||||
<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%3D3.0.0-orange.svg?style=flat-square" /></a>
|
||||
<a href=""><img src="https://img.shields.io/badge/Node.js-%3E%3D6.0.0-orange.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>
|
||||
<br>
|
||||
</p>
|
||||
|
||||
@@ -155,7 +155,7 @@ List of packages currently in existence for libp2p
|
||||
| [`libp2p-kad-dht`](//github.com/libp2p/js-libp2p-kad-dht) | [](//github.com/libp2p/js-libp2p-kad-dht/releases) | [](https://david-dm.org/libp2p/js-libp2p-kad-dht) | [](https://travis-ci.com/libp2p/js-libp2p-kad-dht) | [](https://codecov.io/gh/libp2p/js-libp2p-kad-dht) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
|
||||
| [`libp2p-mdns`](//github.com/libp2p/js-libp2p-mdns) | [](//github.com/libp2p/js-libp2p-mdns/releases) | [](https://david-dm.org/libp2p/js-libp2p-mdns) | [](https://travis-ci.com/libp2p/js-libp2p-mdns) | [](https://codecov.io/gh/libp2p/js-libp2p-mdns) | [Jacob Heun](mailto:jacobheun@gmail.com) |
|
||||
| [`libp2p-webrtc-star`](//github.com/libp2p/js-libp2p-webrtc-star) | [](//github.com/libp2p/js-libp2p-webrtc-star/releases) | [](https://david-dm.org/libp2p/js-libp2p-webrtc-star) | [](https://travis-ci.com/libp2p/js-libp2p-webrtc-star) | [](https://codecov.io/gh/libp2p/js-libp2p-webrtc-star) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
|
||||
| [`discv5`](//github.com/ChainSafe/discv5) | [](//github.com/ChainSafe/discv5/releases) | [](https://david-dm.org/ChainSafe/discv5) | [](https://travis-ci.com/ChainSafe/discv5) | [](https://codecov.io/gh/ChainSafe/discv5) | [Cayman Nava](mailto:caymannava@gmail.com) |
|
||||
| [`@chainsafe/discv5`](//github.com/ChainSafe/discv5) | [](//github.com/ChainSafe/discv5/releases) | [](https://david-dm.org/ChainSafe/discv5) | [](https://travis-ci.com/ChainSafe/discv5) | [](https://codecov.io/gh/ChainSafe/discv5) | [Cayman Nava](mailto:caymannava@gmail.com) |
|
||||
| **content routing** |
|
||||
| [`libp2p-delegated-content-routing`](//github.com/libp2p/js-libp2p-delegated-content-routing) | [](//github.com/libp2p/js-libp2p-delegated-content-routing/releases) | [](https://david-dm.org/libp2p/js-libp2p-delegated-content-routing) | [](https://travis-ci.com/libp2p/js-libp2p-delegated-content-routing) | [](https://codecov.io/gh/libp2p/js-libp2p-delegated-content-routing) | [Jacob Heun](mailto:jacobheun@gmail.com) |
|
||||
| [`libp2p-kad-dht`](//github.com/libp2p/js-libp2p-kad-dht) | [](//github.com/libp2p/js-libp2p-kad-dht/releases) | [](https://david-dm.org/libp2p/js-libp2p-kad-dht) | [](https://travis-ci.com/libp2p/js-libp2p-kad-dht) | [](https://codecov.io/gh/libp2p/js-libp2p-kad-dht) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
|
||||
|
@@ -26,6 +26,7 @@
|
||||
- Documentation
|
||||
- [ ] Ensure that README.md is up to date
|
||||
- [ ] Ensure that all the examples run
|
||||
- [ ] Ensure that [libp2p/docs](https://github.com/libp2p/docs) is updated
|
||||
- Communication
|
||||
- [ ] Create the release issue
|
||||
- [ ] Take a snapshot between of everyone that has contributed to this release (including its subdeps in IPFS, libp2p, IPLD and multiformats) using [`name-your-contributors`](https://www.npmjs.com/package/name-your-contributors). Generate a nice markdown list with [this script](https://gist.github.com/jacobheun/d2ff479ca991733c13cdcf688a1317e5)
|
||||
|
10
doc/API.md
10
doc/API.md
@@ -418,7 +418,7 @@ const latency = await libp2p.ping(otherPeerId)
|
||||
|
||||
## multiaddrs
|
||||
|
||||
Gets the multiaddrs the libp2p node announces to the network. This computes the advertising multiaddrs
|
||||
Gets the multiaddrs the libp2p node announces to the network. This computes the advertising multiaddrs
|
||||
of the peer by joining the multiaddrs that libp2p transports are listening on with the announce multiaddrs
|
||||
provided in the libp2p config. Configured no announce multiaddrs will be filtered out of the advertised addresses.
|
||||
|
||||
@@ -760,7 +760,7 @@ Get the known [`Addresses`][address] of a provided peer.
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `Array<Address>` | Array of peer's [`Addresses`][address] containing the multiaddr and its metadata |
|
||||
| `Array<Address>|undefined` | Array of peer's [`Addresses`][address] containing the multiaddr and its metadata if available, otherwise undefined |
|
||||
|
||||
#### Example
|
||||
|
||||
@@ -797,7 +797,7 @@ Get the known `Multiaddr` of a provided peer. All returned multiaddrs will inclu
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `Array<Multiaddr>` | Array of peer's multiaddr |
|
||||
| `Array<Multiaddr>|undefined` | Array of peer's multiaddr if available, otherwise undefined |
|
||||
|
||||
#### Example
|
||||
|
||||
@@ -1454,7 +1454,7 @@ Create a key in the keychain.
|
||||
|------|------|-------------|
|
||||
| name | `string` | The local key name. It cannot already exist. |
|
||||
| type | `string` | One of the key types; 'rsa' |
|
||||
| size | `number` | The key size in bits. |
|
||||
| [size] | `number` | The key size in bits. Must be provided for rsa keys. |
|
||||
|
||||
#### Returns
|
||||
|
||||
@@ -1801,7 +1801,7 @@ console.log(peerStats.toJSON())
|
||||
|
||||
## Events
|
||||
|
||||
Once you have a libp2p instance, you can listen to several events it emits, so that you can be notified of relevant network events.
|
||||
Once you have a libp2p instance, you can listen to several events it emits, so that you can be notified of relevant network events.
|
||||
|
||||
### libp2p
|
||||
|
||||
|
@@ -270,7 +270,7 @@ const node = await Libp2p.create({
|
||||
},
|
||||
config: {
|
||||
peerDiscovery: {
|
||||
autoDial: true, // Auto connect to discovered peers (limited by ConnectionManager minPeers)
|
||||
autoDial: true, // Auto connect to discovered peers (limited by ConnectionManager minConnections)
|
||||
// The `tag` property will be searched when creating the instance of your Peer Discovery service.
|
||||
// The associated object, will be passed to the service when it is instantiated.
|
||||
[MulticastDNS.tag]: {
|
||||
@@ -304,7 +304,7 @@ const node = await Libp2p.create({
|
||||
},
|
||||
config: {
|
||||
peerDiscovery: {
|
||||
webRTCStar: {
|
||||
[WebRTCStar.tag]: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
@@ -649,8 +649,8 @@ const node = await Libp2p.create({
|
||||
|
||||
As libp2p is designed to be a modular networking library, its usage will vary based on individual project needs. We've included links to some existing project configurations for your reference, in case you wish to replicate their configuration:
|
||||
|
||||
- [libp2p-ipfs-nodejs](https://github.com/ipfs/js-ipfs/tree/master/src/core/runtime/libp2p-nodejs.js) - libp2p configuration used by js-ipfs when running in Node.js
|
||||
- [libp2p-ipfs-browser](https://github.com/ipfs/js-ipfs/tree/master/src/core/runtime/libp2p-browser.js) - libp2p configuration used by js-ipfs when running in a Browser (that supports WebRTC)
|
||||
- [libp2p-ipfs-nodejs](https://github.com/ipfs/js-ipfs/blob/master/packages/ipfs/src/core/runtime/libp2p-nodejs.js) - libp2p configuration used by js-ipfs when running in Node.js
|
||||
- [libp2p-ipfs-browser](https://github.com/ipfs/js-ipfs/blob/master/packages/ipfs/src/core/runtime/libp2p-browser.js) - libp2p configuration used by js-ipfs when running in a Browser (that supports WebRTC)
|
||||
|
||||
If you have developed a project using `js-libp2p`, please consider submitting your configuration to this list so that it can be found easily by other users.
|
||||
|
||||
|
@@ -217,7 +217,7 @@ const node = await Libp2p.create({
|
||||
},
|
||||
config: {
|
||||
peerDiscovery: {
|
||||
autoDial: true, // Auto connect to discovered peers (limited by ConnectionManager minPeers)
|
||||
autoDial: true, // Auto connect to discovered peers (limited by ConnectionManager minConnections)
|
||||
// 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]: {
|
||||
|
@@ -161,4 +161,4 @@ const duplex = {
|
||||
[it-pipe]: https://github.com/alanshaw/it-pipe
|
||||
[it-pushable]: https://github.com/alanshaw/it-pushable
|
||||
[it-reader]: https://github.com/alanshaw/it-reader
|
||||
[streaming-iterables]: https://github.com/bustle/streaming-iterables
|
||||
[streaming-iterables]: https://github.com/reconbot/streaming-iterables
|
||||
|
@@ -10,6 +10,7 @@ A migration guide for refactoring your application code from libp2p v0.27.x to v
|
||||
- [API Implications](#api-implications)
|
||||
- [Connection Manager and Registrar](#connection-manager-and-registrar)
|
||||
- [Events](#events)
|
||||
- [Module Updates](#module-updates)
|
||||
|
||||
## PeerStore API
|
||||
|
||||
@@ -321,6 +322,21 @@ libp2p.on('peer:discovery', (peerId) => {
|
||||
})
|
||||
```
|
||||
|
||||
## Module Updates
|
||||
|
||||
With `libp2p@0.28` you should update the following libp2p modules if you are relying on them:
|
||||
|
||||
```json
|
||||
"libp2p-bootstrap": "^0.11.0",
|
||||
"libp2p-delegated-content-routing": "^0.5.0",
|
||||
"libp2p-delegated-peer-routing": "^0.5.0",
|
||||
"libp2p-floodsub": "^0.21.0",
|
||||
"libp2p-gossipsub": "^0.4.0",
|
||||
"libp2p-kad-dht": "^0.19.1",
|
||||
"libp2p-mdns": "^0.14.1",
|
||||
"libp2p-webrtc-star": "^0.18.0"
|
||||
```
|
||||
|
||||
[connection]: https://github.com/libp2p/js-interfaces/tree/master/src/connection
|
||||
[multiaddr]: https://github.com/multiformats/js-multiaddr
|
||||
[peer-id]: https://github.com/libp2p/js-peer-id
|
||||
|
@@ -170,5 +170,5 @@ Discovered: QmRcXXhtG8vTqwVBRonKWtV4ovDoC1Fe56WYtcrw694eiJ
|
||||
There are plenty more Peer Discovery Mechanisms out there, you can:
|
||||
|
||||
- Find one in [libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star). Yes, a transport with discovery capabilities! This happens because WebRTC requires a rendezvous point for peers to exchange [SDP](https://tools.ietf.org/html/rfc4317) offer, which means we have one or more points that can introduce peers to each other. Think of it as MulticastDNS for the Web, as in MulticastDNS only works in LAN.
|
||||
- Any DHT will offer you a discovery capability. You can simple _random-walk_ the routing tables to find other peers to connect to.
|
||||
- Any DHT will offer you a discovery capability. You can simple _random-walk_ the routing tables to find other peers to connect to. For example [libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) can be used for peer discovery. An example how to configure it to enable random walks can be found [here](https://github.com/libp2p/js-libp2p/blob/v0.28.4/doc/CONFIGURATION.md#customizing-dht).
|
||||
- You can create your own Discovery service, a registry, a list, a radio beacon, you name it!
|
||||
|
@@ -11,26 +11,9 @@ cd ./examples/libp2p-in-the-browser
|
||||
npm install
|
||||
```
|
||||
|
||||
## Signaling Server
|
||||
|
||||
This example uses the `libp2p-webrtc-star` module, which enables libp2p browser nodes to establish direct connections to one another via a central signaling server. For this example, we are using the signaling server that ships with `libp2p-webrtc-star`.
|
||||
|
||||
You can start the server by running `npm run server`. This will start a signaling server locally on port `9090`. If you'd like to run a signaling server outside of this example, you can see instructions on how to do so in the [`libp2p-webrtc-star` README](https://github.com/libp2p/js-libp2p-webrtc-star).
|
||||
|
||||
When you run the server, you should see output that looks something like this:
|
||||
|
||||
```log
|
||||
$ npm run server
|
||||
|
||||
> libp2p-in-browser@1.0.0 server
|
||||
> star-signal
|
||||
|
||||
Listening on: http://0.0.0.0:9090
|
||||
```
|
||||
|
||||
## Running the examples
|
||||
|
||||
Once you have started the signaling server, you can run the Parcel server.
|
||||
Start by running the Parcel server:
|
||||
|
||||
```
|
||||
npm start
|
||||
@@ -53,3 +36,11 @@ This will compile the code and start a server listening on port [http://localhos
|
||||
Now, if you open a second browser tab to `http://localhost:1234`, you should discover your node from the previous tab. This is due to the fact that the `libp2p-webrtc-star` transport also acts as a Peer Discovery interface. Your node will be notified of any peer that connects to the same signaling server you are connected to. Once libp2p discovers this new peer, it will attempt to establish a direct WebRTC connection.
|
||||
|
||||
**Note**: In the example we assign libp2p to `window.libp2p`, in case you would like to play around with the API directly in the browser. You can of course make changes to `index.js` and Parcel will automatically rebuild and reload the browser tabs.
|
||||
|
||||
## Going to production?
|
||||
|
||||
This example uses public `libp2p-webrtc-star` servers. These servers should be used for experimenting and demos, they **MUST** not be used in production as there is no guarantee on availability.
|
||||
|
||||
You can see how to deploy your own signaling server in [libp2p/js-libp2p-webrtc-star/DEPLOYMENT.md](https://github.com/libp2p/js-libp2p-webrtc-star/blob/master/DEPLOYMENT.md).
|
||||
|
||||
Once you have your own server running, you should add its listen address in your libp2p node configuration.
|
||||
|
@@ -14,7 +14,10 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
// Add the signaling server address, along with our PeerId to our multiaddrs list
|
||||
// libp2p will automatically attempt to dial to the signaling server so that it can
|
||||
// receive inbound connections from other peers
|
||||
listen: ['/ip4/0.0.0.0/tcp/9090/wss/p2p-webrtc-star']
|
||||
listen: [
|
||||
'/dns4/wrtc-star1.par.dwebops.pub/tcp/443/wss/p2p-webrtc-star',
|
||||
'/dns4/wrtc-star2.sjc.dwebops.pub/tcp/443/wss/p2p-webrtc-star'
|
||||
]
|
||||
},
|
||||
modules: {
|
||||
transport: [Websockets, WebRTCStar],
|
||||
|
@@ -8,8 +8,7 @@
|
||||
],
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "parcel index.html",
|
||||
"server": "star-signal"
|
||||
"start": "parcel index.html"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
82
examples/pubsub/message-filtering/1.js
Normal file
82
examples/pubsub/message-filtering/1.js
Normal file
@@ -0,0 +1,82 @@
|
||||
/* eslint-disable no-console */
|
||||
'use strict'
|
||||
|
||||
const { Buffer } = require('buffer')
|
||||
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 createNode = async () => {
|
||||
const node = await Libp2p.create({
|
||||
addresses: {
|
||||
listen: ['/ip4/0.0.0.0/tcp/0']
|
||||
},
|
||||
modules: {
|
||||
transport: [TCP],
|
||||
streamMuxer: [Mplex],
|
||||
connEncryption: [NOISE, SECIO],
|
||||
pubsub: Gossipsub
|
||||
}
|
||||
})
|
||||
|
||||
await node.start()
|
||||
return node
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const topic = 'fruit'
|
||||
|
||||
const [node1, node2, node3] = await Promise.all([
|
||||
createNode(),
|
||||
createNode(),
|
||||
createNode(),
|
||||
])
|
||||
|
||||
// node1 conect to node2 and node2 conect to node3
|
||||
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
|
||||
await node1.dial(node2.peerId)
|
||||
|
||||
node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
|
||||
await node2.dial(node3.peerId)
|
||||
|
||||
//subscribe
|
||||
await node1.pubsub.subscribe(topic, (msg) => {
|
||||
console.log(`node1 received: ${msg.data.toString()}`)
|
||||
})
|
||||
|
||||
await node2.pubsub.subscribe(topic, (msg) => {
|
||||
console.log(`node2 received: ${msg.data.toString()}`)
|
||||
})
|
||||
|
||||
await node3.pubsub.subscribe(topic, (msg) => {
|
||||
console.log(`node3 received: ${msg.data.toString()}`)
|
||||
})
|
||||
|
||||
const validateFruit = (msgTopic, peer, msg) => {
|
||||
const fruit = msg.data.toString();
|
||||
const validFruit = ['banana', 'apple', 'orange']
|
||||
const valid = validFruit.includes(fruit);
|
||||
return valid;
|
||||
}
|
||||
|
||||
//validate fruit
|
||||
node1.pubsub._pubsub.topicValidators.set(topic, validateFruit);
|
||||
node2.pubsub._pubsub.topicValidators.set(topic, validateFruit);
|
||||
node3.pubsub._pubsub.topicValidators.set(topic, validateFruit);
|
||||
|
||||
// node1 publishes "fruits" every five seconds
|
||||
var count = 0;
|
||||
const myFruits = ['banana', 'apple', 'car', 'orange'];
|
||||
// car is not a fruit !
|
||||
setInterval(() => {
|
||||
console.log('############## fruit ' + myFruits[count] + ' ##############')
|
||||
node1.pubsub.publish(topic, Buffer.from(myFruits[count]))
|
||||
count++
|
||||
if (count == myFruits.length) {
|
||||
count = 0
|
||||
}
|
||||
}, 5000)
|
||||
})()
|
108
examples/pubsub/message-filtering/README.md
Normal file
108
examples/pubsub/message-filtering/README.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# Filter Messages
|
||||
|
||||
To prevent undesired data from being propagated on the network, we can apply a filter to Gossipsub. Messages that fail validation in the filter will not be re-shared.
|
||||
|
||||
## 1. Setting up a PubSub network with three nodes
|
||||
|
||||
First, let's update our libp2p configuration with a pubsub implementation.
|
||||
|
||||
```JavaScript
|
||||
const Libp2p = require('libp2p')
|
||||
const Gossipsub = require('libp2p-gossipsub')
|
||||
|
||||
const node = await Libp2p.create({
|
||||
addresses: {
|
||||
listen: ['/ip4/0.0.0.0/tcp/0']
|
||||
},
|
||||
modules: {
|
||||
transport: [ TCP ],
|
||||
streamMuxer: [ Mplex ],
|
||||
connEncryption: [ NOISE, SECIO ],
|
||||
pubsub: Gossipsub
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Then, create three nodes and connect them together. In this example, we will connect the nodes in series. Node 1 connected with node 2 and node 2 connected with node 3.
|
||||
|
||||
```JavaScript
|
||||
const [node1, node2, node3] = await Promise.all([
|
||||
createNode(),
|
||||
createNode(),
|
||||
createNode(),
|
||||
])
|
||||
|
||||
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
|
||||
await node1.dial(node2.peerId)
|
||||
|
||||
node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
|
||||
await node2.dial(node3.peerId)
|
||||
```
|
||||
|
||||
Now we' can subscribe to the fruit topic and log incoming messages.
|
||||
|
||||
```JavaScript
|
||||
const topic = 'fruit'
|
||||
|
||||
await node1.pubsub.subscribe(topic, (msg) => {
|
||||
console.log(`node1 received: ${msg.data.toString()}`)
|
||||
})
|
||||
|
||||
await node2.pubsub.subscribe(topic, (msg) => {
|
||||
console.log(`node2 received: ${msg.data.toString()}`)
|
||||
})
|
||||
|
||||
await node3.pubsub.subscribe(topic, (msg) => {
|
||||
console.log(`node3 received: ${msg.data.toString()}`)
|
||||
})
|
||||
```
|
||||
Finally, let's define the additional filter in the fruit topic.
|
||||
|
||||
```JavaScript
|
||||
const validateFruit = (msgTopic, peer, msg) => {
|
||||
const fruit = msg.data.toString();
|
||||
const validFruit = ['banana', 'apple', 'orange']
|
||||
const valid = validFruit.includes(fruit);
|
||||
return valid;
|
||||
}
|
||||
|
||||
node1.pubsub._pubsub.topicValidators.set(topic, validateFruit);
|
||||
node2.pubsub._pubsub.topicValidators.set(topic, validateFruit);
|
||||
node3.pubsub._pubsub.topicValidators.set(topic, validateFruit);
|
||||
```
|
||||
|
||||
In this example, node one has an outdated version of the system, or is a malicious node. When it tries to publish fruit, the messages are re-shared and all the nodes share the message. However, when it tries to publish a vehicle the message is not re-shared.
|
||||
|
||||
```JavaScript
|
||||
var count = 0;
|
||||
const myFruits = ['banana', 'apple', 'car', 'orange'];
|
||||
|
||||
setInterval(() => {
|
||||
console.log('############## fruit ' + myFruits[count] + ' ##############')
|
||||
node1.pubsub.publish(topic, Buffer.from(myFruits[count]))
|
||||
count++
|
||||
if (count == myFruits.length) {
|
||||
count = 0
|
||||
}
|
||||
}, 5000)
|
||||
```
|
||||
|
||||
Result
|
||||
|
||||
```
|
||||
> node 1.js
|
||||
############## fruit banana ##############
|
||||
node1 received: banana
|
||||
node2 received: banana
|
||||
node3 received: banana
|
||||
############## fruit apple ##############
|
||||
node1 received: apple
|
||||
node2 received: apple
|
||||
node3 received: apple
|
||||
############## fruit car ##############
|
||||
node1 received: car
|
||||
############## fruit orange ##############
|
||||
node1 received: orange
|
||||
node2 received: orange
|
||||
node3 received: orange
|
||||
```
|
@@ -33,7 +33,7 @@
|
||||
["libp2p/js-libp2p-kad-dht", "libp2p-kad-dht"],
|
||||
["libp2p/js-libp2p-mdns", "libp2p-mdns"],
|
||||
["libp2p/js-libp2p-webrtc-star", "libp2p-webrtc-star"],
|
||||
["ChainSafe/discv5", "discv5"],
|
||||
["ChainSafe/discv5", "@chainsafe/discv5"],
|
||||
|
||||
"content routing",
|
||||
["libp2p/js-libp2p-delegated-content-routing", "libp2p-delegated-content-routing"],
|
||||
|
31
package.json
31
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "libp2p",
|
||||
"version": "0.28.0-rc.0",
|
||||
"version": "0.28.10",
|
||||
"description": "JavaScript implementation of libp2p, a modular peer to peer network stack",
|
||||
"leadMaintainer": "Jacob Heun <jacobheun@gmail.com>",
|
||||
"main": "src/index.js",
|
||||
@@ -50,7 +50,7 @@
|
||||
"err-code": "^2.0.0",
|
||||
"events": "^3.1.0",
|
||||
"hashlru": "^2.3.0",
|
||||
"interface-datastore": "^0.8.3",
|
||||
"interface-datastore": "^1.0.4",
|
||||
"ipfs-utils": "^2.2.0",
|
||||
"it-all": "^1.0.1",
|
||||
"it-buffer": "^0.1.2",
|
||||
@@ -58,8 +58,8 @@
|
||||
"it-length-prefixed": "^3.0.1",
|
||||
"it-pipe": "^1.1.0",
|
||||
"it-protocol-buffers": "^0.2.0",
|
||||
"libp2p-crypto": "^0.17.6",
|
||||
"libp2p-interfaces": "^0.3.0",
|
||||
"libp2p-crypto": "^0.17.9",
|
||||
"libp2p-interfaces": "^0.3.1",
|
||||
"libp2p-utils": "^0.1.2",
|
||||
"mafmt": "^7.0.0",
|
||||
"merge-options": "^2.0.0",
|
||||
@@ -92,7 +92,7 @@
|
||||
"datastore-level": "^1.1.0",
|
||||
"delay": "^4.3.0",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"interop-libp2p": "libp2p/interop#chore/update-libp2p-daemon-with-peerstore",
|
||||
"interop-libp2p": "^0.1.0",
|
||||
"ipfs-http-client": "^44.0.0",
|
||||
"it-concat": "^1.0.0",
|
||||
"it-pair": "^1.0.0",
|
||||
@@ -102,7 +102,7 @@
|
||||
"libp2p-delegated-content-routing": "^0.5.0",
|
||||
"libp2p-delegated-peer-routing": "^0.5.0",
|
||||
"libp2p-floodsub": "^0.21.0",
|
||||
"libp2p-gossipsub": "^0.4.0",
|
||||
"libp2p-gossipsub": "^0.4.6",
|
||||
"libp2p-kad-dht": "^0.19.1",
|
||||
"libp2p-mdns": "^0.14.1",
|
||||
"libp2p-mplex": "^0.9.5",
|
||||
@@ -125,42 +125,45 @@
|
||||
"Jacob Heun <jacobheun@gmail.com>",
|
||||
"Vasco Santos <vasco.santos@moxy.studio>",
|
||||
"Alan Shaw <alan@tableflip.io>",
|
||||
"Cayman <caymannava@gmail.com>",
|
||||
"Alex Potsides <alex@achingbrain.net>",
|
||||
"Pedro Teixeira <i@pgte.me>",
|
||||
"Friedel Ziegelmayer <dignifiedquire@gmail.com>",
|
||||
"Alex Potsides <alex@achingbrain.net>",
|
||||
"Maciej Krüger <mkg20001@gmail.com>",
|
||||
"Hugo Dias <mail@hugodias.me>",
|
||||
"Cayman <caymannava@gmail.com>",
|
||||
"dirkmc <dirkmdev@gmail.com>",
|
||||
"Volker Mische <volker.mische@gmail.com>",
|
||||
"Richard Littauer <richard.littauer@gmail.com>",
|
||||
"Elven <mon.samuel@qq.com>",
|
||||
"ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>",
|
||||
"Andrew Nesbitt <andrewnez@gmail.com>",
|
||||
"Elven <mon.samuel@qq.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>",
|
||||
"Joel Gustafson <joelg@mit.edu>",
|
||||
"Julien Bouquillon <contact@revolunet.com>",
|
||||
"Kevin Kwok <antimatter15@gmail.com>",
|
||||
"Florian-Merle <florian.david.merle@gmail.com>",
|
||||
"Felipe Martins <felipebrasil93@gmail.com>",
|
||||
"Nuno Nogueira <nunofmn@gmail.com>",
|
||||
"Fei Liu <liu.feiwood@gmail.com>",
|
||||
"RasmusErik Voel Jensen <github@solsort.com>",
|
||||
"robertkiel <robert.kiel@validitylabs.org>",
|
||||
"Dmitriy Ryajov <dryajov@gmail.com>",
|
||||
"Soeren <nikorpoulsen@gmail.com>",
|
||||
"Sönke Hahn <soenkehahn@gmail.com>",
|
||||
"Tiago Alves <alvesjtiago@gmail.com>",
|
||||
"Dmitriy Ryajov <dryajov@gmail.com>",
|
||||
"Diogo Silva <fsdiogo@gmail.com>",
|
||||
"Yusef Napora <yusef@napora.org>",
|
||||
"Zane Starr <zcstarr@gmail.com>",
|
||||
"Daijiro Wachi <daijiro.wachi@gmail.com>",
|
||||
"ebinks <elizabethjbinks@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>"
|
||||
]
|
||||
}
|
||||
|
@@ -1,12 +1,20 @@
|
||||
# js-libp2p-circuit
|
||||
|
||||
> Node.js implementation of the Circuit module that libp2p uses, which implements the [interface-connection](https://github.com/libp2p/js-interfaces/tree/master/src/connection) interface for dial/listen.
|
||||
> Node.js implementation of the Circuit module that libp2p uses, which implements the [interface-connection](https://github.com/libp2p/js-libp2p-interfaces/tree/master/src/connection) interface for dial/listen.
|
||||
|
||||
**Note**: git history prior to merging into js-libp2p can be found in the original repository, https://github.com/libp2p/js-libp2p-circuit.
|
||||
|
||||
`libp2p-circuit` implements the circuit-relay mechanism that allows nodes that don't speak the same protocol to communicate using a third _relay_ node.
|
||||
`libp2p-circuit` implements the circuit-relay mechanism that allows nodes that don't speak the same protocol to communicate using a third _relay_ node. You can read more about this in its [spec](https://github.com/libp2p/specs/tree/master/relay).
|
||||
|
||||
This module uses [pull-streams](https://pull-stream.github.io) for all stream based interfaces.
|
||||
## Table of Contents
|
||||
|
||||
- [js-libp2p-circuit](#js-libp2p-circuit)
|
||||
- [Why?](#why)
|
||||
- [libp2p-circuit and IPFS](#libp2p-circuit-and-ipfs)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Usage](#usage)
|
||||
- [API](#api)
|
||||
- [Implementation rational](#implementation-rational)
|
||||
|
||||
### Why?
|
||||
|
||||
@@ -16,77 +24,42 @@ The use of circuit-relaying is not limited to routing traffic between browser no
|
||||
- routing traffic between private nets and circumventing NAT layers
|
||||
- route mangling for better privacy (matreshka/shallot dialing).
|
||||
|
||||
It's also possible to use it for clients that implement exotic transports such as devices that only have bluetooth radios to be reachable over bluetooth enabled relays and become full p2p nodes.
|
||||
It's also possible to use it for clients that implement exotic transports such as devices that only have bluetooth radios to be reachable over bluetooth enabled relays and become full p2p nodes.
|
||||
|
||||
### libp2p-circuit and IPFS
|
||||
|
||||
Prior to `libp2p-circuit` there was a rift in the IPFS network, were IPFS nodes could only access content from nodes that speak the same protocol, for example TCP only nodes could only dial to other TCP only nodes, same for any other protocol combination. In practice, this limitation was most visible in JS-IPFS browser nodes, since they can only dial out but not be dialed in over WebRTC or WebSockets, hence any content that the browser node held was not reachable by the rest of the network even through it was announced on the DHT. Non browser IPFS nodes would usually speak more than one protocol such as TCP, WebSockets and/or WebRTC, this made the problem less severe outside of the browser. `libp2p-circuit` solves this problem completely, as long as there are `relay nodes` capable of routing traffic between those nodes their content should be available to the rest of the IPFS network.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [js-libp2p-circuit](#js-libp2p-circuit)
|
||||
- [Why?](#why)
|
||||
- [libp2p-circuit and IPFS](#libp2p-circuit-and-ipfs)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Usage](#usage)
|
||||
- [Example](#example)
|
||||
- [Create dialer/listener](#create-dialerlistener)
|
||||
- [Create `relay`](#create-relay)
|
||||
- [API](#api)
|
||||
- [Implementation rational](#implementation-rational)
|
||||
|
||||
## Usage
|
||||
|
||||
### Example
|
||||
Libp2p circuit configuration can be seen at [Setup with Relay](../../doc/CONFIGURATION.md#setup-with-relay).
|
||||
|
||||
#### Create dialer/listener
|
||||
Once you have a circuit relay node running, you can configure other nodes to use it as a relay as follows:
|
||||
|
||||
```js
|
||||
const Circuit = require('libp2p-circuit')
|
||||
const multiaddr = require('multiaddr')
|
||||
const pull = require('pull-stream')
|
||||
const Libp2p = require('libp2p')
|
||||
const TCP = require('libp2p-tcp')
|
||||
const MPLEX = require('libp2p-mplex')
|
||||
const SECIO = require('libp2p-secio')
|
||||
|
||||
const mh1 = multiaddr('/p2p-circuit/p2p/QmHash') // dial /p2p/QmHash over any circuit
|
||||
const relayAddr = ...
|
||||
|
||||
const circuit = new Circuit(swarmInstance, options) // pass swarm instance and options
|
||||
|
||||
const listener = circuit.createListener(mh1, (connection) => {
|
||||
console.log('new connection opened')
|
||||
pull(
|
||||
pull.values(['hello']),
|
||||
socket
|
||||
)
|
||||
const node = await Libp2p.create({
|
||||
addresses: {
|
||||
listen: [multiaddr(`${relayAddr}/p2p-circuit`)]
|
||||
},
|
||||
modules: {
|
||||
transport: [TCP],
|
||||
streamMuxer: [MPLEX],
|
||||
connEncryption: [SECIO]
|
||||
},
|
||||
config: {
|
||||
relay: { // Circuit Relay options (this config is part of libp2p core configurations)
|
||||
enabled: true // Allows you to dial and accept relayed connections. Does not make you a relay.
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
listener.listen(() => {
|
||||
console.log('listening')
|
||||
|
||||
pull(
|
||||
circuit.dial(mh1),
|
||||
pull.log,
|
||||
pull.onEnd(() => {
|
||||
circuit.close()
|
||||
})
|
||||
)
|
||||
})
|
||||
```
|
||||
|
||||
Outputs:
|
||||
|
||||
```sh
|
||||
listening
|
||||
new connection opened
|
||||
hello
|
||||
```
|
||||
|
||||
#### Create `relay`
|
||||
|
||||
```js
|
||||
const Relay = require('libp2p-circuit').Relay
|
||||
|
||||
const relay = new Relay(options)
|
||||
|
||||
relay.mount(swarmInstance) // start relaying traffic
|
||||
```
|
||||
|
||||
## API
|
||||
@@ -101,7 +74,7 @@ Both for dialing and listening.
|
||||
|
||||
### Implementation rational
|
||||
|
||||
This module is not a transport, however it implements `interface-transport` interface in order to allow circuit to be plugged with `libp2p-swarm`. The rational behind it is that, `libp2p-circuit` has a dial and listen flow, which fits nicely with other transports, moreover, it requires the _raw_ connection to be encrypted and muxed just as a regular transport's connection does. All in all, `interface-transport` ended up being the correct level of abstraction for circuit, as well as allowed us to reuse existing integration points in `libp2p-swarm` and `libp2p` without adding any ad-hoc logic. All parts of `interface-transport` are used, including `.getAddr` which returns a list of `/p2p-circuit` addresses that circuit is currently listening.
|
||||
This module is not a transport, however it implements `interface-transport` interface in order to allow circuit to be plugged with `libp2p`. The rational behind it is that, `libp2p-circuit` has a dial and listen flow, which fits nicely with other transports, moreover, it requires the _raw_ connection to be encrypted and muxed just as a regular transport's connection does. All in all, `interface-transport` ended up being the correct level of abstraction for circuit, as well as allowed us to reuse existing integration points in `libp2p` and `libp2p` without adding any ad-hoc logic. All parts of `interface-transport` are used, including `.getAddr` which returns a list of `/p2p-circuit` addresses that circuit is currently listening.
|
||||
|
||||
```
|
||||
libp2p libp2p-circuit (transport)
|
||||
@@ -109,13 +82,13 @@ libp2p
|
||||
| +---------------------------------+ | | |
|
||||
| | | | | +------------------+ |
|
||||
| | | | circuit-relay listens for the HOP | | | |
|
||||
| | libp2p-swarm <------------------------------------------------| circuit-relay | |
|
||||
| | libp2p <------------------------------------------------| circuit-relay | |
|
||||
| | | | message to handle incomming relay | | | |
|
||||
| | | | requests from other nodes | +------------------+ |
|
||||
| +---------------------------------+ | | |
|
||||
| ^ ^ ^ ^ ^ ^ | | +------------------+ |
|
||||
| | | | | | | | | | +-------------+ | |
|
||||
| | | | | | | | dialer uses libp2p-swarm to dial | | | | | |
|
||||
| | | | | | | | dialer uses libp2p to dial | | | | | |
|
||||
| | | | +----------------------------------------------------------------------> dialer | | |
|
||||
| | | transports | | to a circuit-relay node using the | | | | | |
|
||||
| | | | | | | HOP message | | +-------------+ | |
|
||||
|
@@ -12,7 +12,7 @@ const DefaultConfig = {
|
||||
noAnnounce: []
|
||||
},
|
||||
connectionManager: {
|
||||
minPeers: 25
|
||||
minConnections: 25
|
||||
},
|
||||
transportManager: {
|
||||
faultTolerance: FaultTolerance.FATAL_ALL
|
||||
|
@@ -1,9 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
const debug = require('debug')
|
||||
const log = debug('libp2p:connection-manager')
|
||||
log.error = debug('libp2p:connection-manager:error')
|
||||
|
||||
const errcode = require('err-code')
|
||||
const mergeOptions = require('merge-options')
|
||||
const LatencyMonitor = require('./latency-monitor')
|
||||
const debug = require('debug')('libp2p:connection-manager')
|
||||
const retimer = require('retimer')
|
||||
|
||||
const { EventEmitter } = require('events')
|
||||
@@ -22,6 +25,7 @@ const defaultOptions = {
|
||||
maxReceivedData: Infinity,
|
||||
maxEventLoopDelay: Infinity,
|
||||
pollInterval: 2000,
|
||||
autoDialInterval: 10000,
|
||||
movingAverageInterval: 60000,
|
||||
defaultPeerValue: 1
|
||||
}
|
||||
@@ -45,6 +49,8 @@ class ConnectionManager extends EventEmitter {
|
||||
* @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()
|
||||
@@ -57,7 +63,7 @@ class ConnectionManager extends EventEmitter {
|
||||
throw errcode(new Error('Connection Manager maxConnections must be greater than minConnections'), ERR_INVALID_PARAMETERS)
|
||||
}
|
||||
|
||||
debug('options: %j', this._options)
|
||||
log('options: %j', this._options)
|
||||
|
||||
this._libp2p = libp2p
|
||||
|
||||
@@ -73,8 +79,11 @@ class ConnectionManager extends EventEmitter {
|
||||
*/
|
||||
this.connections = new Map()
|
||||
|
||||
this._started = false
|
||||
this._timer = null
|
||||
this._autoDialTimeout = null
|
||||
this._checkMetrics = this._checkMetrics.bind(this)
|
||||
this._autoDial = this._autoDial.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,7 +110,11 @@ class ConnectionManager extends EventEmitter {
|
||||
})
|
||||
this._onLatencyMeasure = this._onLatencyMeasure.bind(this)
|
||||
this._latencyMonitor.on('data', this._onLatencyMeasure)
|
||||
debug('started')
|
||||
|
||||
this._started = true
|
||||
log('started')
|
||||
|
||||
this._options.autoDial && this._autoDial()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,11 +122,13 @@ class ConnectionManager extends EventEmitter {
|
||||
* @async
|
||||
*/
|
||||
async stop () {
|
||||
this._autoDialTimeout && this._autoDialTimeout.clear()
|
||||
this._timer && this._timer.clear()
|
||||
this._latencyMonitor && this._latencyMonitor.removeListener('data', this._onLatencyMeasure)
|
||||
|
||||
this._started = false
|
||||
await this._close()
|
||||
debug('stopped')
|
||||
log('stopped')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,13 +172,13 @@ class ConnectionManager extends EventEmitter {
|
||||
_checkMetrics () {
|
||||
const movingAverages = this._libp2p.metrics.global.movingAverages
|
||||
const received = movingAverages.dataReceived[this._options.movingAverageInterval].movingAverage()
|
||||
this._checkLimit('maxReceivedData', received)
|
||||
this._checkMaxLimit('maxReceivedData', received)
|
||||
const sent = movingAverages.dataSent[this._options.movingAverageInterval].movingAverage()
|
||||
this._checkLimit('maxSentData', sent)
|
||||
this._checkMaxLimit('maxSentData', sent)
|
||||
const total = received + sent
|
||||
this._checkLimit('maxData', total)
|
||||
debug('metrics update', total)
|
||||
this._timer.reschedule(this._options.pollInterval)
|
||||
this._checkMaxLimit('maxData', total)
|
||||
log('metrics update', total)
|
||||
this._timer = retimer(this._checkMetrics, this._options.pollInterval)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,7 +203,7 @@ class ConnectionManager extends EventEmitter {
|
||||
this._peerValues.set(peerIdStr, this._options.defaultPeerValue)
|
||||
}
|
||||
|
||||
this._checkLimit('maxConnections', this.size)
|
||||
this._checkMaxLimit('maxConnections', this.size)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,6 +230,19 @@ class ConnectionManager extends EventEmitter {
|
||||
* @returns {Connection}
|
||||
*/
|
||||
get (peerId) {
|
||||
const connections = this.getAll(peerId)
|
||||
if (connections.length) {
|
||||
return connections[0]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all open connections with a peer.
|
||||
* @param {PeerId} peerId
|
||||
* @returns {Array<Connection>}
|
||||
*/
|
||||
getAll (peerId) {
|
||||
if (!PeerId.isPeerId(peerId)) {
|
||||
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
|
||||
}
|
||||
@@ -222,11 +250,11 @@ class ConnectionManager extends EventEmitter {
|
||||
const id = peerId.toB58String()
|
||||
const connections = this.connections.get(id)
|
||||
|
||||
// Return the first, open connection
|
||||
// Return all open connections
|
||||
if (connections) {
|
||||
return connections.find(connection => connection.stat.status === 'open')
|
||||
return connections.filter(connection => connection.stat.status === 'open')
|
||||
}
|
||||
return null
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,7 +263,7 @@ class ConnectionManager extends EventEmitter {
|
||||
* @param {*} summary The LatencyMonitor summary
|
||||
*/
|
||||
_onLatencyMeasure (summary) {
|
||||
this._checkLimit('maxEventLoopDelay', summary.avgMs)
|
||||
this._checkMaxLimit('maxEventLoopDelay', summary.avgMs)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,15 +272,61 @@ class ConnectionManager extends EventEmitter {
|
||||
* @param {string} name The name of the field to check limits for
|
||||
* @param {number} value The current value of the field
|
||||
*/
|
||||
_checkLimit (name, value) {
|
||||
_checkMaxLimit (name, value) {
|
||||
const limit = this._options[name]
|
||||
debug('checking limit of %s. current value: %d of %d', name, value, limit)
|
||||
log('checking limit of %s. current value: %d of %d', name, value, limit)
|
||||
if (value > limit) {
|
||||
debug('%s: limit exceeded: %s, %d', this._peerId, name, value)
|
||||
log('%s: limit exceeded: %s, %d', this._peerId, name, value)
|
||||
this._maybeDisconnectOne()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
async _autoDial () {
|
||||
const minConnections = this._options.minConnections
|
||||
|
||||
// Already has enough connections
|
||||
if (this.size >= minConnections) {
|
||||
this._autoDialTimeout = retimer(this._autoDial, this._options.autoDialInterval)
|
||||
return
|
||||
}
|
||||
|
||||
// Sort peers on wether we know protocols of public keys for them
|
||||
const peers = Array.from(this._libp2p.peerStore.peers.values())
|
||||
.sort((a, b) => {
|
||||
if (b.protocols && b.protocols.length && (!a.protocols || !a.protocols.length)) {
|
||||
return 1
|
||||
} else if (b.id.pubKey && !a.id.pubKey) {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
})
|
||||
|
||||
for (let i = 0; i < peers.length && this.size < minConnections; i++) {
|
||||
if (!this.get(peers[i].id)) {
|
||||
log('connecting to a peerStore stored peer %s', peers[i].id.toB58String())
|
||||
try {
|
||||
await this._libp2p.dialer.connectToPeer(peers[i].id)
|
||||
|
||||
// Connection Manager was stopped
|
||||
if (!this._started) {
|
||||
return
|
||||
}
|
||||
} catch (err) {
|
||||
log.error('could not connect to peerStore stored peer', err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._autoDialTimeout = retimer(this._autoDial, this._options.autoDialInterval)
|
||||
}
|
||||
|
||||
/**
|
||||
* If we have more connections than our maximum, close a connection
|
||||
* to the lowest valued peer.
|
||||
@@ -261,12 +335,12 @@ class ConnectionManager extends EventEmitter {
|
||||
_maybeDisconnectOne () {
|
||||
if (this._options.minConnections < this.connections.size) {
|
||||
const peerValues = Array.from(this._peerValues).sort(byPeerValue)
|
||||
debug('%s: sorted peer values: %j', this._peerId, peerValues)
|
||||
log('%s: sorted peer values: %j', this._peerId, peerValues)
|
||||
const disconnectPeer = peerValues[0]
|
||||
if (disconnectPeer) {
|
||||
const peerId = disconnectPeer[0]
|
||||
debug('%s: lowest value peer is %s', this._peerId, peerId)
|
||||
debug('%s: closing a connection to %j', this._peerId, peerId)
|
||||
log('%s: lowest value peer is %s', this._peerId, peerId)
|
||||
log('%s: closing a connection to %j', this._peerId, peerId)
|
||||
for (const connections of this.connections.values()) {
|
||||
if (connections[0].remotePeer.toB58String() === peerId) {
|
||||
connections[0].close()
|
||||
|
@@ -112,7 +112,7 @@ class Dialer {
|
||||
this.peerStore.addressBook.add(id, multiaddrs)
|
||||
}
|
||||
|
||||
let addrs = this.peerStore.addressBook.getMultiaddrsForPeer(id)
|
||||
let addrs = this.peerStore.addressBook.getMultiaddrsForPeer(id) || []
|
||||
|
||||
// If received a multiaddr to dial, it should be the first to use
|
||||
// But, if we know other multiaddrs for the peer, we should try them too.
|
||||
|
@@ -1,6 +1,8 @@
|
||||
'use strict'
|
||||
|
||||
const libp2pVersion = require('../../package.json').version
|
||||
|
||||
module.exports.PROTOCOL_VERSION = 'ipfs/0.1.0'
|
||||
module.exports.AGENT_VERSION = 'js-libp2p/0.1.0'
|
||||
module.exports.AGENT_VERSION = `js-libp2p/${libp2pVersion}`
|
||||
module.exports.MULTICODEC_IDENTIFY = '/ipfs/id/1.0.0'
|
||||
module.exports.MULTICODEC_IDENTIFY_PUSH = '/ipfs/id/push/1.0.0'
|
||||
|
@@ -175,6 +175,7 @@ class IdentifyService {
|
||||
// Update peers data in PeerStore
|
||||
this.peerStore.addressBook.set(id, listenAddrs.map((addr) => multiaddr(addr)))
|
||||
this.peerStore.protoBook.set(id, protocols)
|
||||
this.peerStore.metadataBook.set(id, 'AgentVersion', Buffer.from(message.agentVersion))
|
||||
|
||||
// TODO: Track our observed address so that we can score it
|
||||
log('received observed address of %s', observedAddr)
|
||||
@@ -208,7 +209,7 @@ class IdentifyService {
|
||||
* @param {*} options.stream
|
||||
* @param {Connection} options.connection
|
||||
*/
|
||||
_handleIdentify ({ connection, stream }) {
|
||||
async _handleIdentify ({ connection, stream }) {
|
||||
let publicKey = Buffer.alloc(0)
|
||||
if (this.peerId.pubKey) {
|
||||
publicKey = this.peerId.pubKey.bytes
|
||||
@@ -223,12 +224,16 @@ class IdentifyService {
|
||||
protocols: Array.from(this._protocols.keys())
|
||||
})
|
||||
|
||||
pipe(
|
||||
[message],
|
||||
lp.encode(),
|
||||
stream,
|
||||
consume
|
||||
)
|
||||
try {
|
||||
await pipe(
|
||||
[message],
|
||||
lp.encode(),
|
||||
stream,
|
||||
consume
|
||||
)
|
||||
} catch (err) {
|
||||
log.error('could not respond to identify request', err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,17 +244,16 @@ class IdentifyService {
|
||||
* @param {Connection} options.connection
|
||||
*/
|
||||
async _handlePush ({ connection, stream }) {
|
||||
const [data] = await pipe(
|
||||
[],
|
||||
stream,
|
||||
lp.decode(),
|
||||
take(1),
|
||||
toBuffer,
|
||||
collect
|
||||
)
|
||||
|
||||
let message
|
||||
try {
|
||||
const [data] = await pipe(
|
||||
[],
|
||||
stream,
|
||||
lp.decode(),
|
||||
take(1),
|
||||
toBuffer,
|
||||
collect
|
||||
)
|
||||
message = Message.decode(data)
|
||||
} catch (err) {
|
||||
return log.error('received invalid message', err)
|
||||
|
34
src/index.js
34
src/index.js
@@ -65,7 +65,13 @@ class Libp2p extends EventEmitter {
|
||||
this._discovery = new Map() // Discovery service instances/references
|
||||
|
||||
// Create the Connection Manager
|
||||
this.connectionManager = new ConnectionManager(this, this._options.connectionManager)
|
||||
if (this._options.connectionManager.minPeers) { // Remove in 0.29
|
||||
this._options.connectionManager.minConnections = this._options.connectionManager.minPeers
|
||||
}
|
||||
this.connectionManager = new ConnectionManager(this, {
|
||||
autoDial: this._config.peerDiscovery.autoDial,
|
||||
...this._options.connectionManager
|
||||
})
|
||||
|
||||
// Create Metrics
|
||||
if (this._options.metrics.enabled) {
|
||||
@@ -76,7 +82,7 @@ class Libp2p extends EventEmitter {
|
||||
}
|
||||
|
||||
// Create keychain
|
||||
if (this._options.keychain && this._options.keychain.pass && this._options.keychain.datastore) {
|
||||
if (this._options.keychain && this._options.keychain.datastore) {
|
||||
log('creating keychain')
|
||||
|
||||
const keychainOpts = Keychain.generateOptions()
|
||||
@@ -166,6 +172,7 @@ class Libp2p extends EventEmitter {
|
||||
if (this._modules.dht) {
|
||||
const DHT = this._modules.dht
|
||||
this._dht = new DHT({
|
||||
libp2p: this,
|
||||
dialer: this.dialer,
|
||||
peerId: this.peerId,
|
||||
peerStore: this.peerStore,
|
||||
@@ -317,7 +324,7 @@ class Libp2p extends EventEmitter {
|
||||
* @returns {Promise<Connection|*>}
|
||||
*/
|
||||
async dialProtocol (peer, protocols, options) {
|
||||
const { id, multiaddrs } = getPeer(peer, this.peerStore)
|
||||
const { id, multiaddrs } = getPeer(peer)
|
||||
let connection = this.connectionManager.get(id)
|
||||
|
||||
if (!connection) {
|
||||
@@ -389,7 +396,12 @@ class Libp2p extends EventEmitter {
|
||||
* @returns {Promise<number>}
|
||||
*/
|
||||
ping (peer) {
|
||||
const { id } = getPeer(peer)
|
||||
const { id, multiaddrs } = getPeer(peer)
|
||||
|
||||
// If received multiaddr, ping it
|
||||
if (multiaddrs) {
|
||||
return ping(this, multiaddrs[0])
|
||||
}
|
||||
|
||||
return ping(this, id)
|
||||
}
|
||||
@@ -459,19 +471,19 @@ class Libp2p extends EventEmitter {
|
||||
async _onDidStart () {
|
||||
this._isStarted = true
|
||||
|
||||
this.connectionManager.start()
|
||||
|
||||
this.peerStore.on('peer', peerId => {
|
||||
this.emit('peer:discovery', peerId)
|
||||
this._maybeConnect(peerId)
|
||||
})
|
||||
|
||||
// Once we start, emit and dial any peers we may have already discovered
|
||||
// Once we start, emit any peers we may have already discovered
|
||||
// TODO: this should be removed, as we already discovered these peers in the past
|
||||
for (const peer of this.peerStore.peers.values()) {
|
||||
this.emit('peer:discovery', peer.id)
|
||||
this._maybeConnect(peer.id)
|
||||
}
|
||||
|
||||
this.connectionManager.start()
|
||||
|
||||
// Peer discovery
|
||||
await this._setupPeerDiscovery()
|
||||
}
|
||||
@@ -495,15 +507,15 @@ class Libp2p extends EventEmitter {
|
||||
/**
|
||||
* Will dial to the given `peerId` if the current number of
|
||||
* connected peers is less than the configured `ConnectionManager`
|
||||
* minPeers.
|
||||
* minConnections.
|
||||
* @private
|
||||
* @param {PeerId} peerId
|
||||
*/
|
||||
async _maybeConnect (peerId) {
|
||||
// If auto dialing is on and we have no connection to the peer, check if we should dial
|
||||
if (this._config.peerDiscovery.autoDial === true && !this.connectionManager.get(peerId)) {
|
||||
const minPeers = this._options.connectionManager.minPeers || 0
|
||||
if (minPeers > this.connectionManager.size) {
|
||||
const minConnections = this._options.connectionManager.minConnections || 0
|
||||
if (minConnections > this.connectionManager.size) {
|
||||
log('connecting to discovered peer %s', peerId.toB58String())
|
||||
try {
|
||||
await this.dialer.connectToPeer(peerId)
|
||||
|
@@ -7,6 +7,9 @@ const crypto = require('libp2p-crypto')
|
||||
const DS = require('interface-datastore')
|
||||
const CMS = require('./cms')
|
||||
const errcode = require('err-code')
|
||||
const { Number } = require('ipfs-utils/src/globalthis')
|
||||
|
||||
require('node-forge/lib/sha512')
|
||||
|
||||
const keyPrefix = '/pkcs8/'
|
||||
const infoPrefix = '/info/'
|
||||
@@ -107,7 +110,7 @@ class Keychain {
|
||||
this.opts = mergeOptions(defaultOptions, options)
|
||||
|
||||
// Enforce NIST SP 800-132
|
||||
if (!this.opts.passPhrase || this.opts.passPhrase.length < 20) {
|
||||
if (this.opts.passPhrase && this.opts.passPhrase.length < 20) {
|
||||
throw new Error('passPhrase must be least 20 characters')
|
||||
}
|
||||
if (this.opts.dek.keyLength < NIST.minKeyLength) {
|
||||
@@ -120,13 +123,13 @@ class Keychain {
|
||||
throw new Error(`dek.iterationCount must be least ${NIST.minIterationCount}`)
|
||||
}
|
||||
|
||||
// Create the derived encrypting key
|
||||
const dek = crypto.pbkdf2(
|
||||
const dek = this.opts.passPhrase ? crypto.pbkdf2(
|
||||
this.opts.passPhrase,
|
||||
this.opts.dek.salt,
|
||||
this.opts.dek.iterationCount,
|
||||
this.opts.dek.keyLength,
|
||||
this.opts.dek.hash)
|
||||
this.opts.dek.hash) : ''
|
||||
|
||||
Object.defineProperty(this, '_', { value: () => dek })
|
||||
}
|
||||
|
||||
@@ -171,8 +174,8 @@ class Keychain {
|
||||
*
|
||||
* @param {string} name - The local key name; cannot already exist.
|
||||
* @param {string} type - One of the key types; 'rsa'.
|
||||
* @param {int} size - The key size in bits.
|
||||
* @returns {KeyInfo}
|
||||
* @param {int} [size] - The key size in bits. Used for rsa keys only.
|
||||
* @returns {KeyInfo}
|
||||
*/
|
||||
async createKey (name, type, size) {
|
||||
const self = this
|
||||
@@ -185,17 +188,13 @@ class Keychain {
|
||||
return throwDelayed(errcode(new Error(`Invalid key type '${type}'`), 'ERR_INVALID_KEY_TYPE'))
|
||||
}
|
||||
|
||||
if (!Number.isSafeInteger(size)) {
|
||||
return throwDelayed(errcode(new Error(`Invalid key size '${size}'`), 'ERR_INVALID_KEY_SIZE'))
|
||||
}
|
||||
|
||||
const dsname = DsName(name)
|
||||
const exists = await self.store.has(dsname)
|
||||
if (exists) return throwDelayed(errcode(new Error(`Key '${name}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
|
||||
|
||||
switch (type.toLowerCase()) {
|
||||
case 'rsa':
|
||||
if (size < 2048) {
|
||||
if (!Number.isSafeInteger(size) || size < 2048) {
|
||||
return throwDelayed(errcode(new Error(`Invalid RSA key size ${size}`), 'ERR_INVALID_KEY_SIZE'))
|
||||
}
|
||||
break
|
||||
|
@@ -215,7 +215,7 @@ class Metrics {
|
||||
|
||||
const _sink = stream.sink
|
||||
stream.sink = source => {
|
||||
pipe(
|
||||
return pipe(
|
||||
source,
|
||||
tap(chunk => metrics._onMessage({
|
||||
remotePeer,
|
||||
|
@@ -128,11 +128,7 @@ class Stats extends EventEmitter {
|
||||
* @returns {void}
|
||||
*/
|
||||
_resetComputeTimeout () {
|
||||
if (this._timeout) {
|
||||
this._timeout.reschedule(this._nextTimeout())
|
||||
} else {
|
||||
this._timeout = retimer(this._update, this._nextTimeout())
|
||||
}
|
||||
this._timeout = retimer(this._update, this._nextTimeout())
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -168,8 +168,9 @@ 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>}
|
||||
* @returns {Array<Multiaddr>|undefined}
|
||||
*/
|
||||
getMultiaddrsForPeer (peerId) {
|
||||
if (!PeerId.isPeerId(peerId)) {
|
||||
|
@@ -77,8 +77,9 @@ 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>}
|
||||
* @returns {Array<Data>|undefined}
|
||||
*/
|
||||
get (peerId) {
|
||||
if (!PeerId.isPeerId(peerId)) {
|
||||
|
@@ -96,7 +96,9 @@ class PersistentPeerStore extends PeerStore {
|
||||
|
||||
if (this._dirtyPeers.size >= this.threshold) {
|
||||
// Commit current data
|
||||
this._commitData()
|
||||
this._commitData().catch(err => {
|
||||
log.error('error committing data', err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +122,9 @@ class PersistentPeerStore extends PeerStore {
|
||||
|
||||
if (this._dirtyPeers.size >= this.threshold) {
|
||||
// Commit current data
|
||||
this._commitData()
|
||||
this._commitData().catch(err => {
|
||||
log.error('error committing data', err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -15,11 +15,11 @@ const { PROTOCOL, PING_LENGTH } = require('./constants')
|
||||
/**
|
||||
* Ping a given peer and wait for its response, getting the operation latency.
|
||||
* @param {Libp2p} node
|
||||
* @param {PeerId} peer
|
||||
* @param {PeerId|multiaddr} peer
|
||||
* @returns {Promise<Number>}
|
||||
*/
|
||||
async function ping (node, peer) {
|
||||
log('dialing %s to %s', PROTOCOL, peer.toB58String())
|
||||
log('dialing %s to %s', PROTOCOL, peer.toB58String ? peer.toB58String() : peer)
|
||||
|
||||
const { stream } = await node.dialProtocol(peer, PROTOCOL)
|
||||
|
||||
|
@@ -17,7 +17,7 @@ const handshake = require('it-handshake')
|
||||
const { NONCE_LENGTH } = require('./key-generator')
|
||||
const debug = require('debug')
|
||||
const log = debug('libp2p:pnet')
|
||||
log.err = debug('libp2p:pnet:err')
|
||||
log.error = debug('libp2p:pnet:err')
|
||||
|
||||
/**
|
||||
* Takes a Private Shared Key (psk) and provides a `protect` method
|
||||
@@ -69,7 +69,7 @@ class Protector {
|
||||
// Decrypt all inbound traffic
|
||||
createUnboxStream(remoteNonce, this.psk),
|
||||
external
|
||||
)
|
||||
).catch(log.error)
|
||||
|
||||
return internal
|
||||
}
|
||||
|
@@ -258,7 +258,7 @@ class Upgrader {
|
||||
}
|
||||
|
||||
// Pipe all data through the muxer
|
||||
pipe(upgradedConn, muxer, upgradedConn)
|
||||
pipe(upgradedConn, muxer, upgradedConn).catch(log.error)
|
||||
}
|
||||
|
||||
const _timeline = maConn.timeline
|
||||
|
@@ -7,6 +7,9 @@ chai.use(require('chai-as-promised'))
|
||||
const { expect } = chai
|
||||
const sinon = require('sinon')
|
||||
|
||||
const delay = require('delay')
|
||||
const pWaitFor = require('p-wait-for')
|
||||
|
||||
const peerUtils = require('../utils/creators/peer')
|
||||
const mockConnection = require('../utils/mockConnection')
|
||||
const baseOptions = require('../utils/base-options.browser')
|
||||
@@ -112,4 +115,148 @@ describe('libp2p.connections', () => {
|
||||
await libp2p.stop()
|
||||
await remoteLibp2p.stop()
|
||||
})
|
||||
|
||||
describe('proactive connections', () => {
|
||||
let nodes = []
|
||||
|
||||
beforeEach(async () => {
|
||||
nodes = await peerUtils.createPeer({
|
||||
number: 2,
|
||||
config: {
|
||||
addresses: {
|
||||
listen: ['/ip4/127.0.0.1/tcp/0/ws']
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(nodes.map((node) => node.stop()))
|
||||
sinon.reset()
|
||||
})
|
||||
|
||||
it('should connect to all the peers stored in the PeerStore, if their number is below minConnections', async () => {
|
||||
const [libp2p] = await peerUtils.createPeer({
|
||||
fixture: false,
|
||||
started: false,
|
||||
config: {
|
||||
addresses: {
|
||||
listen: ['/ip4/127.0.0.1/tcp/0/ws']
|
||||
},
|
||||
connectionManager: {
|
||||
minConnections: 3
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Populate PeerStore before starting
|
||||
libp2p.peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs)
|
||||
libp2p.peerStore.addressBook.set(nodes[1].peerId, nodes[1].multiaddrs)
|
||||
|
||||
await libp2p.start()
|
||||
|
||||
// Wait for peers to connect
|
||||
await pWaitFor(() => libp2p.connectionManager.size === 2)
|
||||
|
||||
await libp2p.stop()
|
||||
})
|
||||
|
||||
it('should connect to all the peers stored in the PeerStore until reaching the minConnections', async () => {
|
||||
const minConnections = 1
|
||||
const [libp2p] = await peerUtils.createPeer({
|
||||
fixture: false,
|
||||
started: false,
|
||||
config: {
|
||||
addresses: {
|
||||
listen: ['/ip4/127.0.0.1/tcp/0/ws']
|
||||
},
|
||||
connectionManager: {
|
||||
minConnections
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Populate PeerStore before starting
|
||||
libp2p.peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs)
|
||||
libp2p.peerStore.addressBook.set(nodes[1].peerId, nodes[1].multiaddrs)
|
||||
|
||||
await libp2p.start()
|
||||
|
||||
// Wait for peer to connect
|
||||
await pWaitFor(() => libp2p.connectionManager.size === minConnections)
|
||||
|
||||
// Wait more time to guarantee no other connection happened
|
||||
await delay(200)
|
||||
expect(libp2p.connectionManager.size).to.eql(minConnections)
|
||||
|
||||
await libp2p.stop()
|
||||
})
|
||||
|
||||
it('should connect to all the peers stored in the PeerStore until reaching the minConnections sorted', async () => {
|
||||
const minConnections = 1
|
||||
const [libp2p] = await peerUtils.createPeer({
|
||||
fixture: false,
|
||||
started: false,
|
||||
config: {
|
||||
addresses: {
|
||||
listen: ['/ip4/127.0.0.1/tcp/0/ws']
|
||||
},
|
||||
connectionManager: {
|
||||
minConnections
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Populate PeerStore before starting
|
||||
libp2p.peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs)
|
||||
libp2p.peerStore.addressBook.set(nodes[1].peerId, nodes[1].multiaddrs)
|
||||
libp2p.peerStore.protoBook.set(nodes[1].peerId, ['/protocol-min-conns'])
|
||||
|
||||
await libp2p.start()
|
||||
|
||||
// Wait for peer to connect
|
||||
await pWaitFor(() => libp2p.connectionManager.size === minConnections)
|
||||
|
||||
// Should have connected to the peer with protocols
|
||||
expect(libp2p.connectionManager.get(nodes[0].peerId)).to.not.exist()
|
||||
expect(libp2p.connectionManager.get(nodes[1].peerId)).to.exist()
|
||||
|
||||
await libp2p.stop()
|
||||
})
|
||||
|
||||
it('should connect to peers in the PeerStore when a peer disconnected', async () => {
|
||||
const minConnections = 1
|
||||
const autoDialInterval = 1000
|
||||
|
||||
const [libp2p] = await peerUtils.createPeer({
|
||||
fixture: false,
|
||||
config: {
|
||||
addresses: {
|
||||
listen: ['/ip4/127.0.0.1/tcp/0/ws']
|
||||
},
|
||||
connectionManager: {
|
||||
minConnections,
|
||||
autoDialInterval
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Populate PeerStore after starting (discovery)
|
||||
libp2p.peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs)
|
||||
|
||||
// Wait for peer to connect
|
||||
const conn = await libp2p.dial(nodes[0].peerId)
|
||||
expect(libp2p.connectionManager.get(nodes[0].peerId)).to.exist()
|
||||
|
||||
await conn.close()
|
||||
// Closed
|
||||
await pWaitFor(() => libp2p.connectionManager.size === 0)
|
||||
// Connected
|
||||
await pWaitFor(() => libp2p.connectionManager.size === 1)
|
||||
|
||||
expect(libp2p.connectionManager.get(nodes[0].peerId)).to.exist()
|
||||
|
||||
await libp2p.stop()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@@ -58,7 +58,8 @@ describe('Connection Manager', () => {
|
||||
config: {
|
||||
modules: baseOptions.modules,
|
||||
connectionManager: {
|
||||
maxConnections: max
|
||||
maxConnections: max,
|
||||
minConnections: 2
|
||||
}
|
||||
},
|
||||
started: false
|
||||
@@ -96,7 +97,8 @@ describe('Connection Manager', () => {
|
||||
config: {
|
||||
modules: baseOptions.modules,
|
||||
connectionManager: {
|
||||
maxConnections: max
|
||||
maxConnections: max,
|
||||
minConnections: 0
|
||||
}
|
||||
},
|
||||
started: false
|
||||
|
@@ -17,7 +17,7 @@ describe('ping', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
nodes = await peerUtils.createPeer({
|
||||
number: 2,
|
||||
number: 3,
|
||||
config: baseOptions
|
||||
})
|
||||
|
||||
@@ -25,7 +25,14 @@ describe('ping', () => {
|
||||
nodes[1].peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs)
|
||||
})
|
||||
|
||||
it('ping once from peer0 to peer1', async () => {
|
||||
it('ping once from peer0 to peer1 using a multiaddr', async () => {
|
||||
const ma = `${nodes[2].multiaddrs[0]}/p2p/${nodes[2].peerId.toB58String()}`
|
||||
const latency = await nodes[0].ping(ma)
|
||||
|
||||
expect(latency).to.be.a('Number')
|
||||
})
|
||||
|
||||
it('ping once from peer0 to peer1 using a peerId', async () => {
|
||||
const latency = await nodes[0].ping(nodes[1].peerId)
|
||||
|
||||
expect(latency).to.be.a('Number')
|
||||
|
@@ -96,6 +96,15 @@ describe('Dialing (direct, TCP)', () => {
|
||||
.and.to.have.nested.property('._errors[0].code', ErrorCodes.ERR_TRANSPORT_UNAVAILABLE)
|
||||
})
|
||||
|
||||
it('should fail to connect if peer has no known addresses', async () => {
|
||||
const dialer = new Dialer({ transportManager: localTM, peerStore })
|
||||
const peerId = await PeerId.createFromJSON(Peers[1])
|
||||
|
||||
await expect(dialer.connectToPeer(peerId))
|
||||
.to.eventually.be.rejectedWith(Error)
|
||||
.and.to.have.nested.property('.code', ErrorCodes.ERR_NO_VALID_ADDRESSES)
|
||||
})
|
||||
|
||||
it('should be able to connect to a given peer id', async () => {
|
||||
const peerStore = new PeerStore()
|
||||
const dialer = new Dialer({
|
||||
|
@@ -19,6 +19,7 @@ const { IdentifyService, multicodecs } = require('../../src/identify')
|
||||
const Peers = require('../fixtures/peers')
|
||||
const Libp2p = require('../../src')
|
||||
const baseOptions = require('../utils/base-options.browser')
|
||||
const pkg = require('../../package.json')
|
||||
|
||||
const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser')
|
||||
const remoteAddr = MULTIADDRS_WEBSOCKETS[0]
|
||||
@@ -53,6 +54,9 @@ describe('Identify', () => {
|
||||
},
|
||||
protoBook: {
|
||||
set: () => { }
|
||||
},
|
||||
metadataBook: {
|
||||
set: () => { }
|
||||
}
|
||||
},
|
||||
multiaddrs: []
|
||||
@@ -77,6 +81,7 @@ describe('Identify', () => {
|
||||
|
||||
sinon.spy(localIdentify.peerStore.addressBook, 'set')
|
||||
sinon.spy(localIdentify.peerStore.protoBook, 'set')
|
||||
sinon.spy(localIdentify.peerStore.metadataBook, 'set')
|
||||
|
||||
// Run identify
|
||||
await Promise.all([
|
||||
@@ -90,6 +95,12 @@ describe('Identify', () => {
|
||||
|
||||
expect(localIdentify.peerStore.addressBook.set.callCount).to.equal(1)
|
||||
expect(localIdentify.peerStore.protoBook.set.callCount).to.equal(1)
|
||||
|
||||
const metadataArgs = localIdentify.peerStore.metadataBook.set.firstCall.args
|
||||
expect(metadataArgs[0].id.bytes).to.equal(remotePeer.bytes)
|
||||
expect(metadataArgs[1]).to.equal('AgentVersion')
|
||||
expect(metadataArgs[2].toString()).to.equal(`js-libp2p/${pkg.version}`)
|
||||
|
||||
// Validate the remote peer gets updated in the peer store
|
||||
const call = localIdentify.peerStore.addressBook.set.firstCall
|
||||
expect(call.args[0].id.bytes).to.equal(remotePeer.bytes)
|
||||
|
@@ -2,10 +2,8 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const { expect } = chai
|
||||
const { chai, expect } = require('aegir/utils/chai')
|
||||
const fail = expect.fail
|
||||
chai.use(require('dirty-chai'))
|
||||
chai.use(require('chai-string'))
|
||||
|
||||
const peerUtils = require('../utils/creators/peer')
|
||||
@@ -40,8 +38,8 @@ describe('keychain', () => {
|
||||
emptyKeystore = new Keychain(datastore1, { passPhrase: passPhrase })
|
||||
})
|
||||
|
||||
it('needs a pass phrase to encrypt a key', () => {
|
||||
expect(() => new Keychain(datastore2)).to.throw()
|
||||
it('can start without a password', () => {
|
||||
expect(() => new Keychain(datastore2)).to.not.throw()
|
||||
})
|
||||
|
||||
it('needs a NIST SP 800-132 non-weak pass phrase', () => {
|
||||
@@ -56,12 +54,48 @@ describe('keychain', () => {
|
||||
expect(Keychain.options).to.exist()
|
||||
})
|
||||
|
||||
it('needs a supported hashing alorithm', () => {
|
||||
it('supports supported hashing alorithms', () => {
|
||||
const ok = new Keychain(datastore2, { passPhrase: passPhrase, dek: { hash: 'sha2-256' } })
|
||||
expect(ok).to.exist()
|
||||
})
|
||||
|
||||
it('does not support unsupported hashing alorithms', () => {
|
||||
expect(() => new Keychain(datastore2, { passPhrase: passPhrase, dek: { hash: 'my-hash' } })).to.throw()
|
||||
})
|
||||
|
||||
it('can list keys without a password', async () => {
|
||||
const keychain = new Keychain(datastore2)
|
||||
|
||||
expect(await keychain.listKeys()).to.have.lengthOf(0)
|
||||
})
|
||||
|
||||
it('can find a key without a password', async () => {
|
||||
const keychain = new Keychain(datastore2)
|
||||
const keychainWithPassword = new Keychain(datastore2, { passPhrase: `hello-${Date.now()}-${Date.now()}` })
|
||||
const id = `key-${Math.random()}`
|
||||
|
||||
await keychainWithPassword.createKey(id, 'rsa', 2048)
|
||||
|
||||
await expect(keychain.findKeyById(id)).to.eventually.be.ok()
|
||||
})
|
||||
|
||||
it('can remove a key without a password', async () => {
|
||||
const keychainWithoutPassword = new Keychain(datastore2)
|
||||
const keychainWithPassword = new Keychain(datastore2, { passPhrase: `hello-${Date.now()}-${Date.now()}` })
|
||||
const name = `key-${Math.random()}`
|
||||
|
||||
expect(await keychainWithPassword.createKey(name, 'rsa', 2048)).to.have.property('name', name)
|
||||
expect(await keychainWithoutPassword.findKeyByName(name)).to.have.property('name', name)
|
||||
await keychainWithoutPassword.removeKey(name)
|
||||
await expect(keychainWithoutPassword.findKeyByName(name)).to.be.rejectedWith(/does not exist/)
|
||||
})
|
||||
|
||||
it('requires a key to create a password', async () => {
|
||||
const keychain = new Keychain(datastore2)
|
||||
|
||||
await expect(keychain.createKey('derp')).to.be.rejected()
|
||||
})
|
||||
|
||||
it('can generate options', () => {
|
||||
const options = Keychain.generateOptions()
|
||||
options.passPhrase = passPhrase
|
||||
@@ -149,6 +183,70 @@ describe('keychain', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('ed25519 keys', () => {
|
||||
const keyName = 'my custom key'
|
||||
it('can be an ed25519 key', async () => {
|
||||
const keyInfo = await ks.createKey(keyName, 'ed25519')
|
||||
expect(keyInfo).to.exist()
|
||||
expect(keyInfo).to.have.property('name', keyName)
|
||||
expect(keyInfo).to.have.property('id')
|
||||
})
|
||||
|
||||
it('does not overwrite existing key', async () => {
|
||||
const err = await ks.createKey(keyName, 'ed25519').then(fail, err => err)
|
||||
expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS')
|
||||
})
|
||||
|
||||
it('can export/import a key', async () => {
|
||||
const keyName = 'a new key'
|
||||
const password = 'my sneaky password'
|
||||
const keyInfo = await ks.createKey(keyName, 'ed25519')
|
||||
const exportedKey = await ks.exportKey(keyName, password)
|
||||
// remove it so we can import it
|
||||
await ks.removeKey(keyName)
|
||||
const importedKey = await ks.importKey(keyName, exportedKey, password)
|
||||
expect(importedKey.id).to.eql(keyInfo.id)
|
||||
})
|
||||
|
||||
it('cannot create the "self" key', async () => {
|
||||
const err = await ks.createKey('self', 'ed25519').then(fail, err => err)
|
||||
expect(err).to.exist()
|
||||
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
|
||||
})
|
||||
})
|
||||
|
||||
describe('secp256k1 keys', () => {
|
||||
const keyName = 'my secp256k1 key'
|
||||
it('can be an secp256k1 key', async () => {
|
||||
const keyInfo = await ks.createKey(keyName, 'secp256k1')
|
||||
expect(keyInfo).to.exist()
|
||||
expect(keyInfo).to.have.property('name', keyName)
|
||||
expect(keyInfo).to.have.property('id')
|
||||
})
|
||||
|
||||
it('does not overwrite existing key', async () => {
|
||||
const err = await ks.createKey(keyName, 'secp256k1').then(fail, err => err)
|
||||
expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS')
|
||||
})
|
||||
|
||||
it('can export/import a key', async () => {
|
||||
const keyName = 'a new secp256k1 key'
|
||||
const password = 'my sneaky password'
|
||||
const keyInfo = await ks.createKey(keyName, 'secp256k1')
|
||||
const exportedKey = await ks.exportKey(keyName, password)
|
||||
// remove it so we can import it
|
||||
await ks.removeKey(keyName)
|
||||
const importedKey = await ks.importKey(keyName, exportedKey, password)
|
||||
expect(importedKey.id).to.eql(keyInfo.id)
|
||||
})
|
||||
|
||||
it('cannot create the "self" key', async () => {
|
||||
const err = await ks.createKey('self', 'secp256k1').then(fail, err => err)
|
||||
expect(err).to.exist()
|
||||
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
|
||||
})
|
||||
})
|
||||
|
||||
describe('query', () => {
|
||||
it('finds all existing keys', async () => {
|
||||
const keys = await ks.listKeys()
|
||||
@@ -411,7 +509,7 @@ describe('libp2p.keychain', () => {
|
||||
throw new Error('should throw an error using the keychain if no passphrase provided')
|
||||
})
|
||||
|
||||
it('can be used if a passphrase is provided', async () => {
|
||||
it('can be used when a passphrase is provided', async () => {
|
||||
const [libp2p] = await peerUtils.createPeer({
|
||||
started: false,
|
||||
config: {
|
||||
@@ -428,6 +526,22 @@ describe('libp2p.keychain', () => {
|
||||
expect(kInfo).to.exist()
|
||||
})
|
||||
|
||||
it('does not require a keychain passphrase', async () => {
|
||||
const [libp2p] = await peerUtils.createPeer({
|
||||
started: false,
|
||||
config: {
|
||||
keychain: {
|
||||
datastore: new MemoryDatastore()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await libp2p.loadKeychain()
|
||||
|
||||
const kInfo = await libp2p.keychain.createKey('keyName', 'ed25519')
|
||||
expect(kInfo).to.exist()
|
||||
})
|
||||
|
||||
it('can reload keys', async () => {
|
||||
const datastore = new MemoryDatastore()
|
||||
const [libp2p] = await peerUtils.createPeer({
|
||||
|
@@ -31,10 +31,13 @@ describe('peer discovery', () => {
|
||||
sinon.reset()
|
||||
})
|
||||
|
||||
it('should dial know peers on startup', async () => {
|
||||
it('should dial know peers on startup below the minConnections watermark', async () => {
|
||||
libp2p = new Libp2p({
|
||||
...baseOptions,
|
||||
peerId
|
||||
peerId,
|
||||
connectionManager: {
|
||||
minConnections: 2
|
||||
}
|
||||
})
|
||||
|
||||
libp2p.peerStore.addressBook.set(remotePeerId, [multiaddr('/ip4/165.1.1.1/tcp/80')])
|
||||
|
@@ -5,7 +5,6 @@ const chai = require('chai')
|
||||
chai.use(require('dirty-chai'))
|
||||
const { expect } = chai
|
||||
const sinon = require('sinon')
|
||||
|
||||
const PeerStore = require('../../src/peer-store/persistent')
|
||||
const multiaddr = require('multiaddr')
|
||||
const { MemoryDatastore } = require('interface-datastore')
|
||||
@@ -62,6 +61,7 @@ describe('Persisted PeerStore', () => {
|
||||
const protocols = ['/ping/1.0.0']
|
||||
const spyDirty = sinon.spy(peerStore, '_addDirtyPeer')
|
||||
const spyDs = sinon.spy(datastore, 'batch')
|
||||
const commitSpy = sinon.spy(peerStore, '_commitData')
|
||||
|
||||
await peerStore.start()
|
||||
|
||||
@@ -71,12 +71,18 @@ describe('Persisted PeerStore', () => {
|
||||
expect(spyDirty).to.have.property('callCount', 1) // Address
|
||||
expect(spyDs).to.have.property('callCount', 1)
|
||||
|
||||
// let batch commit complete
|
||||
await Promise.all(commitSpy.returnValues)
|
||||
|
||||
// ProtoBook
|
||||
peerStore.protoBook.set(peer, protocols)
|
||||
|
||||
expect(spyDirty).to.have.property('callCount', 2) // Protocol
|
||||
expect(spyDs).to.have.property('callCount', 2)
|
||||
|
||||
// let batch commit complete
|
||||
await Promise.all(commitSpy.returnValues)
|
||||
|
||||
// Should have three peer records stored in the datastore
|
||||
const queryParams = {
|
||||
prefix: '/peers/'
|
||||
@@ -98,6 +104,7 @@ describe('Persisted PeerStore', () => {
|
||||
it('should load content to the peerStore when restart but not put in datastore again', async () => {
|
||||
const spyDs = sinon.spy(datastore, 'batch')
|
||||
const peers = await peerUtils.createPeerId({ number: 2 })
|
||||
const commitSpy = sinon.spy(peerStore, '_commitData')
|
||||
const multiaddrs = [
|
||||
multiaddr('/ip4/156.10.1.22/tcp/1000'),
|
||||
multiaddr('/ip4/156.10.1.23/tcp/1000')
|
||||
@@ -110,17 +117,29 @@ describe('Persisted PeerStore', () => {
|
||||
peerStore.addressBook.set(peers[0], [multiaddrs[0]])
|
||||
peerStore.addressBook.set(peers[1], [multiaddrs[1]])
|
||||
|
||||
// let batch commit complete
|
||||
await Promise.all(commitSpy.returnValues)
|
||||
|
||||
// KeyBook
|
||||
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)
|
||||
|
||||
// ProtoBook
|
||||
peerStore.protoBook.set(peers[0], protocols)
|
||||
peerStore.protoBook.set(peers[1], protocols)
|
||||
|
||||
// let batch commit complete
|
||||
await Promise.all(commitSpy.returnValues)
|
||||
|
||||
// MetadataBook
|
||||
peerStore.metadataBook.set(peers[0], 'location', Buffer.from('earth'))
|
||||
|
||||
// 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(peerStore.peers.size).to.equal(2)
|
||||
|
||||
@@ -148,6 +167,7 @@ describe('Persisted PeerStore', () => {
|
||||
const [peer] = await peerUtils.createPeerId()
|
||||
const multiaddrs = [multiaddr('/ip4/156.10.1.22/tcp/1000')]
|
||||
const protocols = ['/ping/1.0.0']
|
||||
const commitSpy = sinon.spy(peerStore, '_commitData')
|
||||
|
||||
await peerStore.start()
|
||||
|
||||
@@ -158,6 +178,9 @@ describe('Persisted PeerStore', () => {
|
||||
// MetadataBook
|
||||
peerStore.metadataBook.set(peer, 'location', Buffer.from('earth'))
|
||||
|
||||
// let batch commit complete
|
||||
await Promise.all(commitSpy.returnValues)
|
||||
|
||||
const spyDs = sinon.spy(datastore, 'batch')
|
||||
const spyAddressBook = sinon.spy(peerStore.addressBook, 'delete')
|
||||
const spyKeyBook = sinon.spy(peerStore.keyBook, 'delete')
|
||||
@@ -166,6 +189,10 @@ describe('Persisted PeerStore', () => {
|
||||
|
||||
// Delete from PeerStore
|
||||
peerStore.delete(peer)
|
||||
|
||||
// let batch commit complete
|
||||
await Promise.all(commitSpy.returnValues)
|
||||
|
||||
await peerStore.stop()
|
||||
|
||||
expect(spyAddressBook).to.have.property('callCount', 1)
|
||||
@@ -197,6 +224,7 @@ describe('Persisted PeerStore', () => {
|
||||
const spyDirty = sinon.spy(peerStore, '_addDirtyPeer')
|
||||
const spyDirtyMetadata = sinon.spy(peerStore, '_addDirtyPeerMetadata')
|
||||
const spyDs = sinon.spy(datastore, 'batch')
|
||||
const commitSpy = sinon.spy(peerStore, '_commitData')
|
||||
|
||||
const peers = await peerUtils.createPeerId({ number: 2 })
|
||||
|
||||
@@ -213,9 +241,15 @@ describe('Persisted PeerStore', () => {
|
||||
peerStore.protoBook.set(peers[0], protocols)
|
||||
peerStore.metadataBook.set(peers[0], 'location', Buffer.from('earth'))
|
||||
|
||||
// let batch commit complete
|
||||
await Promise.all(commitSpy.returnValues)
|
||||
|
||||
// Remove data from the same Peer
|
||||
peerStore.addressBook.delete(peers[0])
|
||||
|
||||
// let batch commit complete
|
||||
await Promise.all(commitSpy.returnValues)
|
||||
|
||||
expect(spyDirty).to.have.property('callCount', 3) // 2 AddrBook ops, 1 ProtoBook op
|
||||
expect(spyDirtyMetadata).to.have.property('callCount', 1) // 1 MetadataBook op
|
||||
expect(peerStore._dirtyPeers.size).to.equal(1)
|
||||
@@ -231,6 +265,9 @@ describe('Persisted PeerStore', () => {
|
||||
// Add data for second book
|
||||
peerStore.addressBook.set(peers[1], multiaddrs)
|
||||
|
||||
// let batch commit complete
|
||||
await Promise.all(commitSpy.returnValues)
|
||||
|
||||
expect(spyDirty).to.have.property('callCount', 4)
|
||||
expect(spyDirtyMetadata).to.have.property('callCount', 1)
|
||||
expect(spyDs).to.have.property('callCount', 1)
|
||||
@@ -333,6 +370,7 @@ describe('libp2p.peerStore (Persisted)', () => {
|
||||
})
|
||||
|
||||
it('should load content to the peerStore when a new node is started with the same datastore', async () => {
|
||||
const commitSpy = sinon.spy(libp2p.peerStore, '_commitData')
|
||||
const peers = await peerUtils.createPeerId({ number: 2 })
|
||||
const multiaddrs = [
|
||||
multiaddr('/ip4/156.10.1.22/tcp/1000'),
|
||||
@@ -346,10 +384,16 @@ describe('libp2p.peerStore (Persisted)', () => {
|
||||
libp2p.peerStore.addressBook.set(peers[0], [multiaddrs[0]])
|
||||
libp2p.peerStore.addressBook.set(peers[1], [multiaddrs[1]])
|
||||
|
||||
// let batch commit complete
|
||||
await Promise.all(commitSpy.returnValues)
|
||||
|
||||
// ProtoBook
|
||||
libp2p.peerStore.protoBook.set(peers[0], protocols)
|
||||
libp2p.peerStore.protoBook.set(peers[1], protocols)
|
||||
|
||||
// let batch commit complete
|
||||
await Promise.all(commitSpy.returnValues)
|
||||
|
||||
expect(libp2p.peerStore.peers.size).to.equal(2)
|
||||
|
||||
await libp2p.stop()
|
||||
|
@@ -189,4 +189,135 @@ describe('Pubsub subsystem operates correctly', () => {
|
||||
await defer.promise
|
||||
})
|
||||
})
|
||||
|
||||
describe('pubsub with intermittent connections', () => {
|
||||
beforeEach(async () => {
|
||||
libp2p = await create(mergeOptions(subsystemOptions, {
|
||||
peerId,
|
||||
addresses: {
|
||||
listen: [listenAddr]
|
||||
},
|
||||
config: {
|
||||
pubsub: {
|
||||
enabled: true,
|
||||
emitSelf: false
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
remoteLibp2p = await create(mergeOptions(subsystemOptions, {
|
||||
peerId: remotePeerId,
|
||||
addresses: {
|
||||
listen: [remoteListenAddr]
|
||||
},
|
||||
config: {
|
||||
pubsub: {
|
||||
enabled: true,
|
||||
emitSelf: false
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
await libp2p.start()
|
||||
await remoteLibp2p.start()
|
||||
|
||||
libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs)
|
||||
})
|
||||
|
||||
afterEach(() => Promise.all([
|
||||
libp2p && libp2p.stop(),
|
||||
remoteLibp2p && remoteLibp2p.stop()
|
||||
]))
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
it('should receive pubsub messages after a node restart', async () => {
|
||||
const topic = 'test-topic'
|
||||
const data = 'hey!'
|
||||
const libp2pId = libp2p.peerId.toB58String()
|
||||
|
||||
let counter = 0
|
||||
const defer1 = pDefer()
|
||||
const defer2 = pDefer()
|
||||
const handler = (msg) => {
|
||||
expect(msg.data.toString()).to.equal(data)
|
||||
counter++
|
||||
counter === 1 ? defer1.resolve() : defer2.resolve()
|
||||
}
|
||||
|
||||
await libp2p.dial(remotePeerId)
|
||||
|
||||
let subscribedTopics = libp2p.pubsub.getTopics()
|
||||
expect(subscribedTopics).to.not.include(topic)
|
||||
|
||||
libp2p.pubsub.subscribe(topic, handler)
|
||||
|
||||
subscribedTopics = libp2p.pubsub.getTopics()
|
||||
expect(subscribedTopics).to.include(topic)
|
||||
|
||||
// wait for remoteLibp2p to know about libp2p subscription
|
||||
await pWaitFor(() => {
|
||||
const subscribedPeers = remoteLibp2p.pubsub.getSubscribers(topic)
|
||||
return subscribedPeers.includes(libp2pId)
|
||||
})
|
||||
remoteLibp2p.pubsub.publish(topic, data)
|
||||
|
||||
await defer1.promise
|
||||
|
||||
await remoteLibp2p.stop()
|
||||
await remoteLibp2p.start()
|
||||
|
||||
libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs)
|
||||
await libp2p.dial(remotePeerId)
|
||||
|
||||
// wait for remoteLibp2p to know about libp2p subscription
|
||||
await pWaitFor(() => {
|
||||
const subscribedPeers = remoteLibp2p.pubsub.getSubscribers(topic)
|
||||
return subscribedPeers.includes(libp2pId)
|
||||
})
|
||||
|
||||
remoteLibp2p.pubsub.publish(topic, data)
|
||||
|
||||
await defer2.promise
|
||||
})
|
||||
|
||||
it('should handle quick reconnects with a delayed disconnect', async () => {
|
||||
// Subscribe on both
|
||||
const handlerSpy = sinon.spy()
|
||||
const topic = 'reconnect-channel'
|
||||
await Promise.all([
|
||||
libp2p.pubsub.subscribe(topic, handlerSpy),
|
||||
remoteLibp2p.pubsub.subscribe(topic, handlerSpy)
|
||||
])
|
||||
// Create two connections to the remote peer
|
||||
const originalConnection = await libp2p.dialer.connectToPeer(remoteLibp2p.peerId)
|
||||
// second connection
|
||||
await libp2p.dialer.connectToPeer(remoteLibp2p.peerId)
|
||||
expect(libp2p.connections.get(remoteLibp2p.peerId.toB58String())).to.have.length(2)
|
||||
|
||||
// Wait for subscriptions to occur
|
||||
await pWaitFor(() => {
|
||||
return libp2p.pubsub.getSubscribers(topic).includes(remoteLibp2p.peerId.toB58String()) &&
|
||||
remoteLibp2p.pubsub.getSubscribers(topic).includes(libp2p.peerId.toB58String())
|
||||
})
|
||||
|
||||
// Verify messages go both ways
|
||||
libp2p.pubsub.publish(topic, 'message1')
|
||||
remoteLibp2p.pubsub.publish(topic, 'message2')
|
||||
await pWaitFor(() => handlerSpy.callCount === 2)
|
||||
expect(handlerSpy.args.map(([message]) => message.data.toString())).to.include.members(['message1', 'message2'])
|
||||
|
||||
// Disconnect the first connection (this acts as a delayed reconnect)
|
||||
await originalConnection.close()
|
||||
|
||||
// Verify messages go both ways after the disconnect
|
||||
handlerSpy.resetHistory()
|
||||
libp2p.pubsub.publish(topic, 'message3')
|
||||
remoteLibp2p.pubsub.publish(topic, 'message4')
|
||||
await pWaitFor(() => handlerSpy.callCount === 2)
|
||||
expect(handlerSpy.args.map(([message]) => message.data.toString())).to.include.members(['message3', 'message4'])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
Reference in New Issue
Block a user